Repository navigation
Read table properties via TableCache if max_open_files=-1 - #15472
pdillinger wants to merge 1 commit into
Conversation
|
@pdillinger has exported this pull request. If you are a Meta employee, you can view the originating Diff in D127244681. |
✅ clang-tidy: No findings on changed linesCompleted in 626.8s. |
Claude Code Review - OBSOLETESuperseded by a newer AI review. Expand to see the original review.✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 1c73137 SummaryWell-designed optimization that eliminates redundant I/O for table properties when High-severity findings (0): Full review (click to expand)Findings🟡 MEDIUMM1. Stale "REQUIRES: lock is held" comment —
|
| Context | Affected? | Analysis |
|---|---|---|
| WritePreparedTxnDB | No | GetTableProperties doesn't involve read_callback or transaction visibility |
| ReadOnly DB | Safe | Same path; table cache is in-memory, no writes to disk |
| ReactiveVersionSet (secondary) | Handled | Override returns false for trust_manifest_recovery_ (OpenAndCompact worker). Non-trust-manifest secondary with max_open_files=-1 correctly uses the cache path. |
| Dynamic max_open_files change | Safe | Brief inconsistency is benign: at worst, a file is opened into cache that might later be evicted, or a direct read is done when cache is now unbounded. |
| Async background opener | Compatible | Both paths pass prefetch_index_and_filter_in_cache=false (confirmed at db_impl_open.cc:3214). The async opener pins via FindTable; if properties path opened first, the opener finds it cached and pins it (table_cache.cc:337-351). |
| Compaction | Correct | Compaction::level(i) returns inputs_[i].level (actual LSM level). CompactionJob::Prepare passes each_level.level. |
Assumption stress-test results:
- "Each file's metadata is read once" — Verified via FindTable's per-file
loader_mutex_coalescing. - "prefetch=false matches the async opener" — Verified at
db_impl_open.cc:3214. - "No behavior change with max_open_files > 0" — Verified:
OpensAllTableFiles()returns false,no_io=true, direct-read fallback preserved. - "ReactiveVersionSet returns false for OpenAndCompact" — Verified:
trust_manifest_recovery_is set only for that path.
Positive Observations
- Removing
no_iodefault follows CLAUDE.md guidance ("Avoid new defaulted parameters — Miss Spelling in README #1 trap on refactoring!"). All three callers now explicitly state intent. OpensAllTableFiles()virtual method is clean design — encapsulates the configuration question, allows ReactiveVersionSet override, avoids duplicating capacity checks.- Test coverage is well-structured: parameterized over max_open_files=-1/10, tests before/after async open, verifies both counter values, uses sync points (no sleeps).
- Passing full open parameters (level, histogram, temperature, L0 meta pin size) ensures cached readers are identical to what the async opener would create.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/ci_review_prompt.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
Summary: X-link: meta#15472 **Why:** With `max_open_files = -1` and `open_files_async = true`, calling `GetPropertiesOfAllTables()` (or similar) soon after `DB::Open` reads each not-yet-opened SST's footer, metaindex and properties block, and then the background opener reads that metadata again when it opens the file. Concurrent callers each repeat the read too, because this path does not coalesce. In this mode every live SST gets opened anyway, so the separate read is pure overhead. **Previous behavior:** `Version::GetTableProperties()` called `TableCache::GetTableProperties()` with `no_io = true` and, on a miss, opened a fresh file reader to read only the properties block (counted in `NUMBER_DIRECT_LOAD_TABLE_PROPERTIES`). Everything that asks a `Version` for table properties goes through it: `GetPropertiesOfAllTables()`, `GetPropertiesOfTablesInRange()`, `GetPropertiesOfTablesByLevel()`, the aggregated-table-properties DB properties, `Compaction::InitInputTableProperties()` (compaction and `CompactionJobInfo`), the seqno-to-time collection in `CompactionJob`, and `MaybeInitializeFileMetaData()`. With synchronous open and `max_open_files = -1` the lookup always hits, because readers are pinned during recovery and `LogAndApply`. So in practice the miss happens with `open_files_async`, before the background opener reaches a file (primary, read-only, or async secondary). **New behavior:** When the `VersionSet` opens all table files (`max_open_files = -1`), `Version::GetTableProperties()` calls `TableCache::GetTableProperties()` with `no_io = false`, which opens a not-yet-open file into the table cache. The open uses the background opener's parameters (the file's level, read histogram and temperature, `MaxFileSizeForL0MetaPin()`, no index/filter prefetch), because with -1 that reader stays in the cache for the life of the DB. `FindTable()`'s per-file loader mutex coalesces this with a concurrent open by the background opener or a reader. The background opener then finds the reader in the cache and only pins it, so each file's metadata is read once. With `max_open_files > 0`, nothing changes: a cached reader is used if present, otherwise the properties block is read directly, so these calls do not use up table cache capacity. **Implementation:** - `TableCache::GetTableProperties()` takes the open parameters that vary by caller: `level`, `file_read_hist` and `prefetch_index_and_filter_in_cache`, all non-defaulted. `no_io` loses its default, so every caller states all four. It also passes the file's own temperature and `MaxFileSizeForL0MetaPin(mutable_cf_options)` to `FindTable()`, as the read paths do. - `Version::GetTableProperties()` uses it with `no_io = !OpensAllTableFiles()` and no index/filter prefetch. It falls back to the direct properties read only on an `Incomplete` cache miss, as before. - New `VersionSet::OpensAllTableFiles()`: true when the table cache is unbounded. `ReactiveVersionSet` returns false with trusted-MANIFEST recovery, i.e. the `DB::OpenAndCompact` worker (the default with `remote_compaction_manifest_floor`). That worker runs with an unbounded table cache but opens only the files its compaction reads, so opening a file just for its properties would not save a later open. It would also move the open's reads out of the remote job's measured `bytes_read`, which `CompactionServiceTest.BasicCompactions` checks. - `Version::GetTableProperties()` and `Version::MaybeInitializeFileMetaData()` take the file's level as a new non-defaulted parameter. Every caller already knows it. - The other two `TableCache::GetTableProperties()` callers keep index/filter prefetch on, as before. `DBImpl::GetPreparedFileInfoForExternalSstIngestion()` now passes its live file's level and read histogram. The repairer passes level -1 and no histogram, and its `FileMetaData` has no temperature set at that point, so repair's behavior is unchanged. **Safety, compatibility, limitations:** - No public API, option, or file format change. Behavior with `max_open_files > 0`, and in the `OpenAndCompact` worker, is unchanged. - With `max_open_files = -1`, a properties request for a not-yet-open file now does the full table open on the caller's thread (e.g. index/filter blocks not cached in block cache, unique ID verification) instead of reading only the properties block. Total I/O goes down, but one `GetPropertiesOfAllTables()` call right after an async open can take longer, since it opens serially files that the background opener (using `max_file_opening_threads`) has not reached yet. Errors that fail a table open (e.g. corruption outside the properties block) now come back from these calls. With synchronous open the same error would have failed `DB::Open`, and with `open_files_async` it is also reported as a background error. - `NUMBER_DIRECT_LOAD_TABLE_PROPERTIES` no longer counts these reads when `max_open_files = -1`. - If `GetPreparedFileInfoForExternalSstIngestion()` is what opens a file, the reader now gets the file's level, histogram, temperature and L0 pinning size, matching how reads open it. Requested by: Peter Dillinger (peterd) Differential Revision: D127244681
1c73137 to
69976b4
Compare
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 69976b4 SummaryWell-designed optimization that eliminates redundant I/O for table properties when High-severity findings (0): No high-severity findings. Full review (click to expand)Findings🟡 MEDIUMM1. Header comment "REQUIRES: lock is held" is now misleading —
|
| Context | Affected? | Analysis |
|---|---|---|
| WritePreparedTxnDB | No | Properties queries don't interact with read_callback visibility |
| ReadOnly DB | Yes (safe) | Uses same OpensAllTableFiles() gate; read-only DBs with max_open_files=-1 will also open through cache |
| CompactionService / Remote compaction | Yes (safe) | ReactiveVersionSet::OpensAllTableFiles() returns false when trust_manifest_recovery_=true, preserving existing behavior for OpenAndCompact workers |
| Repair | Yes (safe) | Repair calls TableCache::GetTableProperties directly, not Version::GetTableProperties. The diff correctly passes level=-1, file_read_hist=nullptr |
| User-defined timestamps | No | Properties access is independent of key comparison |
| Old snapshots | No | Properties access doesn't interact with snapshot visibility |
| FIFO / Universal compaction | No | Same GetTableProperties path, level values are valid |
Positive Observations
- Eliminated defaulted parameters: Removing the
no_iodefault and requiring all 4 parameters forces every caller to explicitly state their intent. This is a refactoring improvement per CLAUDE.md guidelines. OpensAllTableFiles()is cleanly virtual: The override inReactiveVersionSetcorrectly handles the remote compaction worker case where opening files for properties would waste I/O and break byte-counting tests.- Test coverage: The new
OpenFilesAsyncTest::TablePropertiestest uses sync points to verify both the "before async open" and "after async open" scenarios, checking bothNO_FILE_OPENSandNUMBER_DIRECT_LOAD_TABLE_PROPERTIEScounters. It covers bothmax_open_files=-1andmax_open_files=10via the parameterized test. - Coalescing via
loader_mutex_: The PR correctly leveragesFindTable()'s existing per-file loader mutex to coalesce concurrent opens from properties queries and the background opener, avoiding duplicate I/O. prefetch_index_and_filter_in_cache=false: When opening a table just for properties, index/filter blocks are not prefetched into the block cache, which is appropriate since the background opener may use different prefetch settings and will handle this later.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/ci_review_prompt.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
Summary:
X-link: #15472
Why: With
max_open_files = -1andopen_files_async = true, callingGetPropertiesOfAllTables()(or similar) soon afterDB::Openreads each not-yet-opened SST's footer, metaindex and properties block, and then the background opener reads that metadata again when it opens the file. Concurrent callers each repeat the read too, because this path does not coalesce. In this mode every live SST gets opened anyway, so the separate read is pure overhead.Previous behavior:
Version::GetTableProperties()calledTableCache::GetTableProperties()withno_io = trueand, on a miss, opened a fresh file reader to read only the properties block (counted inNUMBER_DIRECT_LOAD_TABLE_PROPERTIES). Everything that asks aVersionfor table properties goes through it:GetPropertiesOfAllTables(),GetPropertiesOfTablesInRange(),GetPropertiesOfTablesByLevel(), the aggregated-table-properties DB properties,Compaction::InitInputTableProperties()(compaction andCompactionJobInfo), the seqno-to-time collection inCompactionJob, andMaybeInitializeFileMetaData(). With synchronous open andmax_open_files = -1the lookup always hits, because readers are pinned during recovery andLogAndApply. So in practice the miss happens withopen_files_async, before the background opener reaches a file (primary, read-only, or async secondary).New behavior: When the
VersionSetopens all table files (max_open_files = -1),Version::GetTableProperties()callsTableCache::GetTableProperties()withno_io = false, which opens a not-yet-open file into the table cache. The open uses the background opener's parameters (the file's level, read histogram and temperature,MaxFileSizeForL0MetaPin(), no index/filter prefetch), because with -1 that reader stays in the cache for the life of the DB.FindTable()'s per-file loader mutex coalesces this with a concurrent open by the background opener or a reader. The background opener then finds the reader in the cache and only pins it, so each file's metadata is read once.With
max_open_files > 0, nothing changes: a cached reader is used if present, otherwise the properties block is read directly, so these calls do not use up table cache capacity.Implementation:
TableCache::GetTableProperties()takes the open parameters that vary by caller:level,file_read_histandprefetch_index_and_filter_in_cache, all non-defaulted.no_ioloses its default, so every caller states all four. It also passes the file's own temperature andMaxFileSizeForL0MetaPin(mutable_cf_options)toFindTable(), as the read paths do.Version::GetTableProperties()uses it withno_io = !OpensAllTableFiles()and no index/filter prefetch. It falls back to the direct properties read only on anIncompletecache miss, as before.VersionSet::OpensAllTableFiles(): true when the table cache is unbounded.ReactiveVersionSetreturns false with trusted-MANIFEST recovery, i.e. theDB::OpenAndCompactworker (the default withremote_compaction_manifest_floor). That worker runs with an unbounded table cache but opens only the files its compaction reads, so opening a file just for its properties would not save a later open. It would also move the open's reads out of the remote job's measuredbytes_read, whichCompactionServiceTest.BasicCompactionschecks.Version::GetTableProperties()andVersion::MaybeInitializeFileMetaData()take the file's level as a new non-defaulted parameter. Every caller already knows it.TableCache::GetTableProperties()callers keep index/filter prefetch on, as before.DBImpl::GetPreparedFileInfoForExternalSstIngestion()now passes its live file's level and read histogram. The repairer passes level -1 and no histogram, and itsFileMetaDatahas no temperature set at that point, so repair's behavior is unchanged.Safety, compatibility, limitations:
max_open_files > 0, and in theOpenAndCompactworker, is unchanged.max_open_files = -1, a properties request for a not-yet-open file now does the full table open on the caller's thread (e.g. index/filter blocks not cached in block cache, unique ID verification) instead of reading only the properties block. Total I/O goes down, but oneGetPropertiesOfAllTables()call right after an async open can take longer, since it opens serially files that the background opener (usingmax_file_opening_threads) has not reached yet. Errors that fail a table open (e.g. corruption outside the properties block) now come back from these calls. With synchronous open the same error would have failedDB::Open, and withopen_files_asyncit is also reported as a background error.NUMBER_DIRECT_LOAD_TABLE_PROPERTIESno longer counts these reads whenmax_open_files = -1.GetPreparedFileInfoForExternalSstIngestion()is what opens a file, the reader now gets the file's level, histogram, temperature and L0 pinning size, matching how reads open it.Requested by: Peter Dillinger (peterd)
Differential Revision: D127244681