| // 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 |