blob: a288fa55f0e9d590001e116a6550e6c93e9f4d23 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include "kudu/fs/block_id.h"
#include "kudu/gutil/macros.h"
#include "kudu/gutil/port.h"
#include "kudu/gutil/singleton.h"
#include "kudu/util/cache.h"
#include "kudu/util/slice.h"
template <class T> class scoped_refptr;
namespace kudu {
class MetricEntity;
namespace cfile {
class BlockCacheHandle;
// Wrapper around kudu::Cache specifically for caching blocks of CFiles.
// Provides a singleton and LRU cache for CFile blocks.
class BlockCache {
public:
// Parse the gflag which configures the block cache. FATALs if the flag is
// invalid.
static Cache::MemoryType GetConfiguredCacheMemoryTypeOrDie();
// BlockId refers to the unique identifier for a Kudu block, that is, for an
// entire CFile. This is different than the block cache's notion of a block,
// which is just a portion of a CFile.
typedef BlockId FileId;
// The unique key identifying entries in the block cache.
// Each cached block corresponds to a specific offset within
// a file (called a "block" in other parts of Kudu).
//
// This structure's in-memory representation is internally memcpyed
// and treated as a string. It may also be persisted across restarts
// and upgrades of Kudu in persistent cache implementations. So, it's
// important that the layout be fixed and kept compatible for all
// future releases.
struct CacheKey {
CacheKey(BlockCache::FileId file_id, uint64_t offset) :
file_id_(file_id.id()),
offset_(offset)
{}
uint64_t file_id_;
uint64_t offset_;
} PACKED;
// An entry that is in the process of being inserted into the block
// cache. See the documentation above 'Allocate' below on the block
// cache insertion path.
class PendingEntry {
public:
PendingEntry()
: handle_(Cache::UniquePendingHandle(nullptr,
Cache::PendingHandleDeleter(nullptr))) {
}
explicit PendingEntry(Cache::UniquePendingHandle handle)
: handle_(std::move(handle)) {
}
PendingEntry(PendingEntry&& other) noexcept : PendingEntry() {
*this = std::move(other);
}
~PendingEntry() {
reset();
}
PendingEntry& operator=(PendingEntry&& other) noexcept;
PendingEntry& operator=(const PendingEntry& other) = delete;
// Free the pending entry back to the block cache.
// This is safe to call multiple times.
void reset();
// Return true if this is a valid pending entry.
bool valid() const {
return static_cast<bool>(handle_);
}
// Return the pointer into which the value should be written.
uint8_t* val_ptr() {
return handle_.get_deleter().cache()->MutableValue(&handle_);
}
private:
friend class BlockCache;
Cache::UniquePendingHandle handle_;
};
static BlockCache* GetSingleton() {
return Singleton<BlockCache>::get();
}
explicit BlockCache(size_t capacity);
// Lookup the given block in the cache.
//
// If the entry is found, then sets *handle to refer to the entry.
// This object's destructor will release the cache entry so it may be freed again.
// Alternatively, handle->Release() may be used to explicitly release it.
//
// Returns true to indicate that the entry was found, false otherwise.
bool Lookup(const CacheKey& key, Cache::CacheBehavior behavior,
BlockCacheHandle* handle);
// Pass a metric entity to the cache to start recording metrics.
// This should be called before the block cache starts serving blocks.
// Not calling StartInstrumentation will simply result in no block cache-related metrics.
// Calling StartInstrumentation multiple times will reset the metrics each time.
void StartInstrumentation(
const scoped_refptr<MetricEntity>& metric_entity,
Cache::ExistingMetricsPolicy metrics_policy = Cache::ExistingMetricsPolicy::kKeep);
// Insertion path
// --------------------
// Block cache entries are written in two phases. First, a pending entry must be
// constructed. The data to be cached then must be copied directly into
// this pending entry before being inserted. For example:
//
// // Allocate space in the cache for a block of 'data_size' bytes.
// PendingEntry entry = cache->Allocate(my_cache_key, data_size);
// // Check for allocation failure.
// if (!entry.valid()) {
// // if there is no space left in the cache, handle the error.
// }
// // Read the actual block into the allocated buffer.
// RETURN_NOT_OK(ReadDataFromDiskIntoBuffer(entry.val_ptr()));
// // "Commit" the entry to the cache
// BlockCacheHandle bch;
// cache->Insert(&entry, &bch);
// Allocate a new entry to be inserted into the cache.
PendingEntry Allocate(const CacheKey& key, size_t block_size);
// Insert the given block into the cache. 'inserted' is set to refer to the
// entry in the cache.
void Insert(PendingEntry* entry, BlockCacheHandle* inserted);
private:
friend class Singleton<BlockCache>;
BlockCache();
DISALLOW_COPY_AND_ASSIGN(BlockCache);
std::unique_ptr<Cache> cache_;
};
// Scoped reference to a block from the block cache.
class BlockCacheHandle {
public:
BlockCacheHandle()
: handle_(Cache::UniqueHandle(nullptr, Cache::HandleDeleter(nullptr))) {
}
BlockCacheHandle(BlockCacheHandle&& other) noexcept : handle_(std::move(other.handle_)) {}
BlockCacheHandle& operator=(BlockCacheHandle&& other) noexcept {
handle_ = std::move(other.handle_);
return *this;
}
~BlockCacheHandle() = default;
// Swap this handle with another handle.
// This can be useful to transfer ownership of a handle by swapping
// with an empty BlockCacheHandle.
void swap(BlockCacheHandle* dst) {
std::swap(handle_, dst->handle_);
}
// Return the data in the cached block.
//
// NOTE: this slice is only valid until the block cache handle is
// destructed or explicitly Released().
Slice data() const {
return handle_.get_deleter().cache()->Value(handle_);
}
bool valid() const {
return static_cast<bool>(handle_);
}
private:
DISALLOW_COPY_AND_ASSIGN(BlockCacheHandle);
friend class BlockCache;
void SetHandle(Cache::UniqueHandle handle) {
handle_ = std::move(handle);
}
Cache::UniqueHandle handle_;
};
inline BlockCache::PendingEntry& BlockCache::PendingEntry::operator=(
BlockCache::PendingEntry&& other) noexcept {
reset();
handle_ = std::move(other.handle_);
return *this;
}
inline void BlockCache::PendingEntry::reset() {
handle_.reset();
}
// Validates the block cache capacity. Won't permit the cache to grow large
// enough to cause pernicious flushing behavior. See KUDU-2318.
bool ValidateBlockCacheCapacity();
} // namespace cfile
} // namespace kudu