Skip to content

Charge readahead buffer memory to block cache (#15607) - #15607

Open
xingbowang wants to merge 1 commit into
meta:mainfrom
xingbowang:export-D129901393
Open

xingbowang wants to merge 1 commit into
meta:mainfrom
xingbowang:export-D129901393

Conversation

@xingbowang

@xingbowang xingbowang commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

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

@meta-cla meta-cla Bot added the CLA Signed label Nov 6, 2026
@meta-codesync

meta-codesync Bot commented Nov 6, 2026

Copy link
Copy Markdown

@xingbowang has exported this pull request. If you are a Meta employee, you can view the originating Diff in D129901393.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

✅ clang-tidy: No findings on changed lines

Completed 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
@meta-codesync meta-codesync Bot changed the title Charge readahead buffer memory to block cache Charge readahead buffer memory to block cache (#15607) Nov 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant