Repository navigation
Charge readahead buffer memory to block cache (#15607) - #15607
Open
xingbowang wants to merge 1 commit into
Open
xingbowang wants to merge 1 commit into
xingbowang wants to merge 1 commit into
Conversation
|
@xingbowang has exported this pull request. If you are a Meta employee, you can view the originating Diff in D129901393. |
✅ clang-tidy: No findings on changed linesCompleted in 489.8s. |
Summary: Why the change is required: Readahead (prefetch) buffers can hold a lot of memory: up to `readahead_size` or `max_auto_readahead_size` for each open iterator per table file, up to `compaction_readahead_size` for each compaction input file, and the open-time tail prefetch buffer. Users who limit total memory by charging other memory users to the block cache (`cache_usage_options`) had no way to count this memory, so it was outside the charged budget. Previous behavior: `FilePrefetchBuffer` memory was never charged to the block cache. No `CacheEntryRole` existed for it, so it could not be enabled. New behavior: A new `CacheEntryRole::kReadaheadBuffer` can be set in `BlockBasedTableOptions::cache_usage_options`. When charging is enabled for it, the readahead buffers used to read block-based table files are charged to the block cache: iterator readahead (explicit and automatic), compaction input readahead, partitioned index/filter prefetch, open-time tail prefetch, and the checksum verification scan. The charge follows the total allocated capacity of each buffer and is released when the buffer is freed. It is disabled by default, so existing behavior does not change. Charging is best effort. If the reservation for a buffer's growth fails (a full cache with `strict_capacity_limit`), that growth is left uncharged, the part of it that was reserved is given back, and the read continues; a later buffer allocation retries the charge. This differs from table reader charging, which fails table open. Readahead is an optimization on the read path, and failing a user read or a compaction because a charge did not fit is worse than going over the budget for a short time. Giving back a failed reservation matters: otherwise the dummy entries that did fit stay pinned, and with a strict cache the next data block insert can fail although the read would fit without charging. Implementation: - `FilePrefetchBuffer::SetCacheReservationManager()` attaches an optional reservation manager. After every buffer allocation (aligned buffer allocation, overlap buffer allocation, or adoption of a file system allocated buffer) the buffer recomputes the total capacity of its buffers and updates the reservation by the difference. If an increase fails, it decreases the reservation by the same amount, which releases the dummy entries that were inserted for it, and keeps its charged amount unchanged. The destructor releases the rest. The total includes free buffers, because `ClearBuffer()` keeps the memory. - `BlockBasedTableFactory` creates one shared `ConcurrentCacheReservationManager` for the role when it is enabled. It does not use delayed decrease, so that giving back a failed reservation really releases its dummy entries. It is passed through `BlockBasedTable::Open()` into `Rep`, and `Rep::CreateFilePrefetchBuffer()`, the tail prefetch and the checksum verification scan attach it to each buffer. - `BlockBasedTable::Open()` takes the new manager as an explicit argument. `table_reader_cache_res_mgr` is no longer defaulted, so no caller can silently skip it. - Buffers allocated by a file system that supports `kFSBuffer` are charged the same way, using the size of the data they return. - New `charge_readahead_buffer` flag in db_bench and db_stress; the crash test picks it at random. Safety, compatibility, and limitations: - Off by default. With charging off, the only added cost is a null check at buffer allocation, not on buffer hits. - With charging on, the shared manager's mutex is taken only when a buffer's capacity changes (buffer growth, creation, destruction), not on every read. One exception: with a file system that supports `kFSBuffer`, capacity follows each read's returned size, so the update can run on each readahead read. - No DB mutex is affected: these updates run on the read paths (iterator, compaction input, table open), and the extra work is a cache placeholder insert or release each time the total crosses a 256 KB step. - Public API: new enum value before `kMisc` (like earlier roles); `kMisc` moves up by one. Roles are not persisted. - With `strict_capacity_limit`, charged space is not available for blocks, like other charged memory. When the charge fits but leaves no room (e.g. a 1 MB readahead buffer in a 1.25 MB strict cache), inserting the data block that the read needs fails with `MemoryLimit`, as it does today when other charged or pinned memory fills a strict cache. The public documentation says so. - For file system allocated buffers the charged size is the returned data size. The real allocation of the file system may be larger. - Not covered: blob file readahead during compaction (`PrefetchBufferCollection`), `sst_dump` and table formats other than block-based. These keep the old uncharged behavior. - No db_bench results are included; this change makes no performance claim. Differential Revision: D129901393
xingbowang
force-pushed
the
export-D129901393
branch
from
November 6, 2026 13:11
81bfb48 to
17bb763
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Why the change is required:
Readahead (prefetch) buffers can hold a lot of memory: up to
readahead_sizeormax_auto_readahead_sizefor each open iterator per table file, up tocompaction_readahead_sizefor each compaction input file, and the open-time tail prefetch buffer. Users who limit total memory by charging other memory users to the block cache (cache_usage_options) had no way to count this memory, so it was outside the charged budget.Previous behavior:
FilePrefetchBuffermemory was never charged to the block cache. NoCacheEntryRoleexisted for it, so it could not be enabled.New behavior:
A new
CacheEntryRole::kReadaheadBuffercan be set inBlockBasedTableOptions::cache_usage_options. When charging is enabled for it, the readahead buffers used to read block-based table files are charged to the block cache: iterator readahead (explicit and automatic), compaction input readahead, partitioned index/filter prefetch, open-time tail prefetch, and the checksum verification scan. The charge follows the total allocated capacity of each buffer and is released when the buffer is freed. It is disabled by default, so existing behavior does not change.Charging is best effort. If the reservation for a buffer's growth fails (a full cache with
strict_capacity_limit), that growth is left uncharged, the part of it that was reserved is given back, and the read continues; a later buffer allocation retries the charge. This differs from table reader charging, which fails table open. Readahead is an optimization on the read path, and failing a user read or a compaction because a charge did not fit is worse than going over the budget for a short time. Giving back a failed reservation matters: otherwise the dummy entries that did fit stay pinned, and with a strict cache the next data block insert can fail although the read would fit without charging.Implementation:
FilePrefetchBuffer::SetCacheReservationManager()attaches an optional reservation manager. After every buffer allocation (aligned buffer allocation, overlap buffer allocation, or adoption of a file system allocated buffer) the buffer recomputes the total capacity of its buffers and updates the reservation by the difference. If an increase fails, it decreases the reservation by the same amount, which releases the dummy entries that were inserted for it, and keeps its charged amount unchanged. The destructor releases the rest. The total includes free buffers, becauseClearBuffer()keeps the memory.BlockBasedTableFactorycreates one sharedConcurrentCacheReservationManagerfor the role when it is enabled. It does not use delayed decrease, so that giving back a failed reservation really releases its dummy entries. It is passed throughBlockBasedTable::Open()intoRep, andRep::CreateFilePrefetchBuffer(), the tail prefetch and the checksum verification scan attach it to each buffer.BlockBasedTable::Open()takes the new manager as an explicit argument.table_reader_cache_res_mgris no longer defaulted, so no caller can silently skip it.kFSBufferare charged the same way, using the size of the data they return.charge_readahead_bufferflag in db_bench and db_stress; the crash test picks it at random.Safety, compatibility, and limitations:
kFSBuffer, capacity follows each read's returned size, so the update can run on each readahead read.kMisc(like earlier roles);kMiscmoves up by one. Roles are not persisted.strict_capacity_limit, charged space is not available for blocks, like other charged memory. When the charge fits but leaves no room (e.g. a 1 MB readahead buffer in a 1.25 MB strict cache), inserting the data block that the read needs fails withMemoryLimit, as it does today when other charged or pinned memory fills a strict cache. The public documentation says so.PrefetchBufferCollection),sst_dumpand table formats other than block-based. These keep the old uncharged behavior.Differential Revision: D129901393