Repository navigation
Support merge-before-filter compaction (#15517) - #15517
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 D128313536. |
|
| Check | Count |
|---|---|
cppcoreguidelines-special-member-functions |
1 |
| Total | 1 |
Details
db/flush_job_test.cc (1 warning(s))
db/flush_job_test.cc:190:9: warning: class 'Filter' defines a non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator [cppcoreguidelines-special-member-functions]
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 00:09
38171fe to
2ddc584
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 30, 2026
Summary: Pull Request resolved: meta#15517 ## Why Applications need to filter the logical merge result. Previously, compaction filters ran before merging and could remove individual operands before the merge operator evaluated them. ## Behavior The default remains filter-before-merge. Filters and factories can opt into merge-before-filter through `GetMergeOrder()`. The existing `FilterV4` receives full values/entities, partial merge operands, or individual untouched operands when partial merge declines. Successful aggregates are offered once; changed operands are not merged again in the same pass. Snapshot eligibility covers every retained operand. Stored values retain normal filtering rules, including sequence zero; aggregates containing ineligible operands are not filtered. New-order value/entity `kPurge` uses ordinary Delete semantics for input records, merge results, and later compactions. Unsupported operand decisions return errors. ## Implementation `MergeHelper` stages outcomes and eligibility; `CompactionIterator` handles filtering, removal and range skips, including the advanced cursor after a full merge. Production factory/order checks run outside the DB mutex. Open and CF creation reject new-order factories selecting non-compaction creation; flush/recovery/mempurge retain runtime guards. Repair explicitly checks the table builder's `IOStatus` while preserving earlier error precedence. Purge normalization has a ticker. Compatible services advertise support, transport core request format 2, and acknowledge the worker's actual filter order. Missing capability, service `kUseLocal`, and validated compatibility declines fall back locally before output import. Old core workers reject format 2 with `NotSupported`; integrator decline policy determines whether that becomes local fallback. Real errors remain errors. Service integration is supplied separately and activated by the normal RocksDB release. ## Safety and limitations `FilterV4` and value types remain unchanged; there is no persisted SST-format change, new CF option, or C/Java binding. New virtual methods and job metadata require rebuilding applications, filters, factories, services and workers against matching headers/libraries. Binary rollback requires a compatible rebuild. Normalized purge cannot be mixed with SingleDelete for the same keys. Integrated BlobDB is supported. SnapshotChecker, timestamps and stacked BlobDB are unsupported; SnapshotChecker and timestamps remain follow-ups. Merge grouping can change callback inputs and policy outcomes; existing callback lifetime/exception rules apply. Focused native tests cover merge/filter and remote routing behavior. Full-suite, current sanitizer and benchmark validation are unavailable. Five established immutable-baseline checked-status failures remain unresolved and unwaived. No performance or production rollout claim is made. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 11:22
2ddc584 to
83cbe3e
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 30, 2026
Summary: Pull Request resolved: meta#15517 ## Why Applications need to filter the logical merge result. Previously, compaction filters ran before merging and could remove individual operands before the merge operator evaluated them. ## Behavior The default remains filter-before-merge. Filters and factories can opt into merge-before-filter through `GetMergeOrder()`. The existing `FilterV4` receives full values/entities, partial merge operands, or individual untouched operands when partial merge declines. Successful aggregates are offered once; changed operands are not merged again in the same pass. Snapshot eligibility covers every retained operand. Stored values retain normal filtering rules, including sequence zero; aggregates containing ineligible operands are not filtered. New-order value/entity `kPurge` uses ordinary Delete semantics for input records, merge results, and later compactions. Unsupported operand decisions return errors. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12) and `FilterV4` sees `kValue` "12" and keeps it. If the base is not in the compaction, partial merge gives `+8` and `FilterV4` sees it as `kMergeOperand`. ## Implementation `MergeHelper` stages outcomes and eligibility; `CompactionIterator` handles filtering, removal and range skips, including the advanced cursor after a full merge. Production factory/order checks run outside the DB mutex. Open and CF creation reject new-order factories selecting non-compaction creation; flush/recovery/mempurge retain runtime guards. Repair explicitly checks the table builder's `IOStatus` while preserving earlier error precedence. Purge normalization has a ticker. Compatible services advertise support and the worker acknowledges its actual filter order. Missing capability, service `kUseLocal`, and results without a matching acknowledgment fall back locally before output import; old workers ignore the new request field and return no acknowledgment, so they fall back too. Real errors remain errors. Service integration is supplied separately and activated by the normal RocksDB release. ## Safety and limitations `FilterV4` and value types remain unchanged; there is no persisted SST-format change, new CF option, or C/Java binding. New virtual methods and job metadata require rebuilding applications, filters, factories, services and workers against matching headers/libraries. Binary rollback requires a compatible rebuild. Normalized purge cannot be mixed with SingleDelete for the same keys. Integrated BlobDB is supported. SnapshotChecker, timestamps and stacked BlobDB are unsupported; SnapshotChecker and timestamps remain follow-ups. Merge grouping can change callback inputs and policy outcomes; existing callback lifetime/exception rules apply. Focused native tests cover merge/filter and remote routing behavior. Full-suite, current sanitizer and benchmark validation are unavailable. Five established immutable-baseline checked-status failures remain unresolved and unwaived. No performance or production rollout claim is made. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 12:53
83cbe3e to
bcf6863
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 30, 2026
Summary: Pull Request resolved: meta#15517 ## Why Applications need to filter the logical merge result. Previously, compaction filters ran before merging and could remove individual operands before the merge operator evaluated them. ## Behavior The default remains filter-before-merge. Filters and factories can opt into merge-before-filter through `GetMergeOrder()`. The existing `FilterV4` receives full values/entities, partial merge operands, or individual untouched operands when partial merge declines. Successful aggregates are offered once; changed operands are not merged again in the same pass. Snapshot eligibility covers every retained operand. Stored values retain normal filtering rules, including sequence zero; aggregates containing ineligible operands are not filtered. New-order value/entity `kPurge` uses ordinary Delete semantics for input records, merge results, and later compactions. Unsupported operand decisions return errors. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12) and `FilterV4` sees `kValue` "12" and keeps it. If the base is not in the compaction, partial merge gives `+8` and `FilterV4` sees it as `kMergeOperand`. ## Implementation `MergeHelper` stages outcomes and eligibility; `CompactionIterator` handles filtering, removal and range skips, including the advanced cursor after a full merge. Production factory/order checks run outside the DB mutex. Open and CF creation reject new-order factories selecting non-compaction creation; flush/recovery/mempurge retain runtime guards. Repair explicitly checks the table builder's `IOStatus` while preserving earlier error precedence. Purge normalization has a ticker. Compatible services advertise support and the worker acknowledges its actual filter order. Missing capability, service `kUseLocal`, and results without a matching acknowledgment fall back locally before output import; old workers ignore the new request field and return no acknowledgment, so they fall back too. Real errors remain errors. Service integration is supplied separately and activated by the normal RocksDB release. ## Safety and limitations `FilterV4` and value types remain unchanged; there is no persisted SST-format change, new CF option, or C/Java binding. New virtual methods and job metadata require rebuilding applications, filters, factories, services and workers against matching headers/libraries. Binary rollback requires a compatible rebuild. Normalized purge cannot be mixed with SingleDelete for the same keys. Integrated BlobDB is supported. SnapshotChecker, timestamps and stacked BlobDB are unsupported; SnapshotChecker and timestamps remain follow-ups. Merge grouping can change callback inputs and policy outcomes; existing callback lifetime/exception rules apply. Focused native tests cover merge/filter and remote routing behavior. Full-suite, current sanitizer and benchmark validation are unavailable. Five established immutable-baseline checked-status failures remain unresolved and unwaived. No performance or production rollout claim is made. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 12:57
bcf6863 to
3de49f0
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 30, 2026
Summary: Pull Request resolved: meta#15517 ## Why Applications need to filter the logical merge result. Previously, compaction filters ran before merging and could remove individual operands before the merge operator evaluated them. ## Behavior The default remains filter-before-merge. Filters and factories can opt into merge-before-filter through `GetMergeOrder()`. The existing `FilterV4` receives full values/entities, partial merge operands, or individual untouched operands when partial merge declines. Successful aggregates are offered once; changed operands are not merged again in the same pass. Snapshot eligibility covers every retained operand. Stored values retain normal filtering rules, including sequence zero; aggregates containing ineligible operands are not filtered. New-order value/entity `kPurge` uses ordinary Delete semantics for input records, merge results, and later compactions. Unsupported operand decisions return errors. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12) and `FilterV4` sees `kValue` "12" and keeps it. If the base is not in the compaction, partial merge gives `+8` and `FilterV4` sees it as `kMergeOperand`. ## Implementation `MergeHelper` stages outcomes and eligibility; `CompactionIterator` handles filtering, removal and range skips, including the advanced cursor after a full merge. Production factory/order checks run outside the DB mutex. Open and CF creation reject new-order factories selecting non-compaction creation; flush/recovery/mempurge retain runtime guards. Repair explicitly checks the table builder's `IOStatus` while preserving earlier error precedence. Purge normalization has a ticker. Compatible services advertise support and the worker acknowledges its actual filter order. Missing capability, service `kUseLocal`, and results without a matching acknowledgment fall back locally before output import; old workers ignore the new request field and return no acknowledgment, so they fall back too. Real errors remain errors. Service integration is supplied separately and activated by the normal RocksDB release. ## Safety and limitations `FilterV4` and value types remain unchanged; there is no persisted SST-format change, new CF option, or C/Java binding. New virtual methods and job metadata require rebuilding applications, filters, factories, services and workers against matching headers/libraries. Binary rollback requires a compatible rebuild. Normalized purge cannot be mixed with SingleDelete for the same keys. Integrated BlobDB is supported. SnapshotChecker, timestamps and stacked BlobDB are unsupported; SnapshotChecker and timestamps remain follow-ups. Merge grouping can change callback inputs and policy outcomes; existing callback lifetime/exception rules apply. Focused native tests cover merge/filter and remote routing behavior. Full-suite, current sanitizer and benchmark validation are unavailable. Five established immutable-baseline checked-status failures remain unresolved and unwaived. No performance or production rollout claim is made. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 13:51
3de49f0 to
d22d3f7
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 30, 2026
Summary: Pull Request resolved: meta#15517 ## Why Applications need to filter the logical merge result. Previously, compaction filters ran before merging and could remove individual operands before the merge operator evaluated them. ## Behavior The default remains filter-before-merge. Filters and factories can opt into merge-before-filter through `GetMergeOrder()`. The existing `FilterV4` receives full values/entities, partial merge operands, or individual untouched operands when partial merge declines. Successful aggregates are offered once; changed operands are not merged again in the same pass. Snapshot eligibility covers every retained operand. Stored values retain normal filtering rules, including sequence zero; aggregates containing ineligible operands are not filtered. New-order value/entity `kPurge` uses ordinary Delete semantics for input records, merge results, and later compactions. Unsupported operand decisions return errors. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12) and `FilterV4` sees `kValue` "12" and keeps it. If the base is not in the compaction, partial merge gives `+8` and `FilterV4` sees it as `kMergeOperand`. ## Implementation `MergeHelper` stages outcomes and eligibility; `CompactionIterator` handles filtering, removal and range skips, including the advanced cursor after a full merge. Production factory/order checks run outside the DB mutex. Open and CF creation reject new-order factories selecting non-compaction creation; flush/recovery/mempurge retain runtime guards. Repair explicitly checks the table builder's `IOStatus` while preserving earlier error precedence. Purge normalization has a ticker. Compatible services advertise support and the worker acknowledges its actual filter order. Missing capability, service `kUseLocal`, and results without a matching acknowledgment fall back locally before output import; old workers ignore the new request field and return no acknowledgment, so they fall back too. Real errors remain errors. Service integration is supplied separately and activated by the normal RocksDB release. ## Safety and limitations `FilterV4` and value types remain unchanged; there is no persisted SST-format change, new CF option, or C/Java binding. New virtual methods and job metadata require rebuilding applications, filters, factories, services and workers against matching headers/libraries. Binary rollback requires a compatible rebuild. Normalized purge cannot be mixed with SingleDelete for the same keys. Integrated BlobDB is supported. SnapshotChecker, timestamps and stacked BlobDB are unsupported; SnapshotChecker and timestamps remain follow-ups. Merge grouping can change callback inputs and policy outcomes; existing callback lifetime/exception rules apply. Focused native tests cover merge/filter and remote routing behavior. Full-suite, current sanitizer and benchmark validation are unavailable. Five established immutable-baseline checked-status failures remain unresolved and unwaived. No performance or production rollout claim is made. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 30, 2026 13:55
d22d3f7 to
484feb5
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 31, 2026
Summary: Pull Request resolved: meta#15517 ## Why Some useful compaction filters only make sense on merged results. For example, a filter that removes counters below a threshold must see the counter's total, not each `+N` operand. With the current order, the compaction filter runs before the merge operator, so it sees (and can remove) individual operands and never sees the merge result. Important limit: compaction can only merge what is in its own inputs. If a key's base value or older operands are in files outside the compaction, the filter sees a partial merge result (or single operands), not the logical value of the key. ## Behavior - **Default (unchanged):** `CompactionFilter::MergeOrder::kFilterBeforeMerge`. Operands are filtered one by one before merging. - **New opt-in:** a filter or factory returns `kMergeBeforeFilter` from the new `GetMergeOrder()`. During compaction the merge operator runs first, then the filter sees: - the full merge result as `kValue` / `kWideColumnEntity`, if the inputs include the base (Put or tombstone), or the compaction is bottommost with no older entries; - otherwise the operands as `kMergeOperand`: one partial result from `PartialMergeMulti`, or each operand by itself if partial merge declines. - A filter result is not merged again in the same compaction. Filters that only implement FilterV2/V3 also work. - A merge result is offered only if no snapshot can see any of its operands. Plain values are filtered as before, regardless of snapshots. - `kPurge` on a value is treated as `kRemove` (ordinary Delete), because SingleDelete does not mix with Merge history. `kPurge` and `kChangeWideColumnEntity` on an operand fail the compaction with NotSupported. - `kRemoveAndSkipUntil` on any operand of an uncombined batch drops the whole batch, the same result as the default order. - `COMPACTION_KEY_DROP_USER` keeps the same meaning in both orders (skipped ranges are not counted). - The new order applies only to compaction. Flush, recovery, MemPurge, and repair keep the default order. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12), the filter sees `kValue` "12", and keeps it. If the base is not in the compaction, partial merge gives `+8` and the filter sees it as `kMergeOperand`. ## Implementation - `MergeHelper` carries the merge order and records each merge outcome (full, partial, uncombined) and whether it can be filtered under snapshots. - `CompactionIterator` reads the order from `MergeHelper` and filters after the merge. A full result changed to Delete goes through the normal `ProcessDeletion()` path. Uncombined operand batches are filtered as a whole before any of them is output, so a skip decision can drop the full batch. - Order checks (`GetCompactionFilterMergeOrder()`) run outside the DB mutex. A factory's non-null filters must declare the same order; otherwise the compaction fails with InvalidArgument. A factory that returns a null filter means no filtering, which is valid with either order. - **Remote compaction:** `CompactionService::SupportsMergeBeforeFilter()` (default false) lets a service receive new-order jobs; otherwise they run locally. The request carries `merge_before_filter`, and the worker acknowledges the order it actually used. A missing or wrong acknowledgment (for example, from an older worker) falls back to local compaction before any output is imported. Real compaction errors stay errors. A worker with no filter accepts either order. - **Early rejection:** DB open and CF creation reject a new-order declaration with user-defined timestamps, WritePrepared/WriteUnprepared transactions, stacked BlobDB, or a factory that selects non-compaction table file creation. This avoids a background error that would stop writes at the first compaction. - `ShouldFilterTableFileCreation()` checks use an exhaustive switch over `TableFileCreationReason`, so a new reason must be classified. - **Bug fix included:** `MemPurge()` returned with the DB mutex unlocked when the flush filter failed a check (for example, `IgnoreSnapshots() == false`). The new order check adds another early return on the same path, so the re-lock is fixed here, with a `bug_fixes` release note. - **Stress test:** with `merge_before_compaction_filter`, the db_stress filter now makes real decisions that never change what a reader sees: `kChangeValue` with the same bytes for about 1 in N records (`merge_before_filter_change_one_in`); with `merge_before_filter_remove_deleted`, `kRemove` for keys deleted in the expected state, and `kRemoveAndSkipUntil` over just that key in full compactions (where every version of the key is an input). It also aborts if a value it sees is not a well-formed stress value for its key, or if the new-order factory is asked to filter a non-compaction file. It prints per-type decision counts at the end of a run. The crash test picks these modes randomly and uses merges in about 3/4 of new-order runs; the remove/skip mode uses the same no-snapshot settings as the default-order filter. - `Repairer` only marks `BuildTable()`'s `IOStatus` as checked, because `BuildTable()` can now return early before reading it. Repair behavior does not change. ## Safety and limitations - The default order and existing filters behave as before. There is no SST format change, no new CF option, and no C/Java binding. - Not supported in the new order: user-defined timestamps, WritePrepared/WriteUnprepared transactions (SnapshotChecker), and stacked BlobDB. Integrated BlobDB is supported. - Do not use SingleDelete on keys handled by a new-order filter, even after switching back to the default order. - How operands are grouped into partial results can differ between compactions, and SST files keep no record of earlier groupings. Filter decisions must be correct for any grouping. - **Stress test limits:** the stress merge operators keep only the newest operand, and every stress filter decision is chosen so that reads do not change. So both orders give the same visible result, and the oracle checks robustness of the new paths (wrong output key or sequence, lost or returning data, buffer and pinning errors, crash recovery, remote fallback), not whether the filter saw the right input. A same-bytes rewrite also cannot detect a change that RocksDB ignores. Skips only run in full compactions, which are rare in leveled runs. The decision counters print only when db_stress exits normally (not in blackbox kills). - **Unit tests cover the rest:** which input the filter sees (full, partial, uncombined), the snapshot rule, changed values in the output (`MergeBeforeFilterFullReplacementAndFailure`, `MergeBeforeFilterPartialAndDecline`, remote full/partial tests), skip and removal results, kPurge handling, unsupported decisions, early rejection, remote routing and fallback, and BlobDB. - Validation: the full suites of the touched test binaries pass, and local db_stress and crash-test runs with the new modes pass. See the Test Plan for details and the benchmark results. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 31, 2026 18:25
484feb5 to
15003bc
Compare
xingbowang
added a commit
to xingbowang/rocksdb
that referenced
this pull request
Oct 31, 2026
Summary: Pull Request resolved: meta#15517 ## Why Some useful compaction filters only make sense on merged results. For example, a filter that removes counters below a threshold must see the counter's total, not each `+N` operand. With the current order, the compaction filter runs before the merge operator, so it sees (and can remove) individual operands and never sees the merge result. Important limit: compaction can only merge what is in its own inputs. If a key's base value or older operands are in files outside the compaction, the filter sees a partial merge result (or single operands), not the logical value of the key. ## Behavior - **Default (unchanged):** `CompactionFilter::MergeOrder::kFilterBeforeMerge`. Operands are filtered one by one before merging. - **New opt-in:** a filter or factory returns `kMergeBeforeFilter` from the new `GetMergeOrder()`. During compaction the merge operator runs first, then the filter sees: - the full merge result as `kValue` / `kWideColumnEntity`, if the inputs include the base (Put or tombstone), or the compaction is bottommost with no older entries; - otherwise the operands as `kMergeOperand`: one partial result from `PartialMergeMulti`, or each operand by itself if partial merge declines. - A filter result is not merged again in the same compaction. Filters that only implement FilterV2/V3 also work. - A merge result is offered only if no snapshot can see any of its operands. Plain values are filtered as before, regardless of snapshots. - `kPurge` on a value is treated as `kRemove` (ordinary Delete), because SingleDelete does not mix with Merge history. `kPurge` and `kChangeWideColumnEntity` on an operand fail the compaction with NotSupported. - `kRemoveAndSkipUntil` on any operand of an uncombined batch drops the whole batch, the same result as the default order. - `COMPACTION_KEY_DROP_USER` keeps the same meaning in both orders (skipped ranges are not counted). - The new order applies only to compaction. Flush, recovery, MemPurge, and repair keep the default order. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12), the filter sees `kValue` "12", and keeps it. If the base is not in the compaction, partial merge gives `+8` and the filter sees it as `kMergeOperand`. ## Implementation - `MergeHelper` carries the merge order and records each merge outcome (full, partial, uncombined) and whether it can be filtered under snapshots. - `CompactionIterator` reads the order from `MergeHelper` and filters after the merge. A full result changed to Delete goes through the normal `ProcessDeletion()` path. Uncombined operand batches are filtered as a whole before any of them is output, so a skip decision can drop the full batch. - Order checks (`GetCompactionFilterMergeOrder()`) run outside the DB mutex. A factory's non-null filters must declare the same order; otherwise the compaction fails with InvalidArgument. A factory that returns a null filter means no filtering, which is valid with either order. - **Remote compaction:** `CompactionService::SupportsMergeBeforeFilter()` (default false) lets a service receive new-order jobs; otherwise they run locally. The request carries `merge_before_filter`, and the worker acknowledges the order it actually used. A missing or wrong acknowledgment (for example, from an older worker) falls back to local compaction before any output is imported. Real compaction errors stay errors. A worker with no filter accepts either order. - **Early rejection:** DB open and CF creation reject a new-order declaration with user-defined timestamps, WritePrepared/WriteUnprepared transactions, stacked BlobDB, or a factory that selects non-compaction table file creation. This avoids a background error that would stop writes at the first compaction. - `ShouldFilterTableFileCreation()` checks use an exhaustive switch over `TableFileCreationReason`, so a new reason must be classified. - **Bug fix included:** `MemPurge()` returned with the DB mutex unlocked when the flush filter failed a check (for example, `IgnoreSnapshots() == false`). The new order check adds another early return on the same path, so the re-lock is fixed here, with a `bug_fixes` release note. - **Stress test:** with `merge_before_compaction_filter`, the db_stress filter now makes real decisions that never change what a reader sees: `kChangeValue` with the same bytes for about 1 in N records (`merge_before_filter_change_one_in`); with `merge_before_filter_remove_deleted`, `kRemove` for keys deleted in the expected state, and `kRemoveAndSkipUntil` over just that key in full compactions (where every version of the key is an input). It also aborts if a value it sees is not a well-formed stress value for its key, or if the new-order factory is asked to filter a non-compaction file. It prints per-type decision counts at the end of a run. The crash test picks these modes randomly and uses merges in about 3/4 of new-order runs; the remove/skip mode uses the same no-snapshot settings as the default-order filter. - `Repairer` only marks `BuildTable()`'s `IOStatus` as checked, because `BuildTable()` can now return early before reading it. Repair behavior does not change. ## Safety and limitations - The default order and existing filters behave as before. There is no SST format change, no new CF option, and no C/Java binding. - Not supported in the new order: user-defined timestamps, WritePrepared/WriteUnprepared transactions (SnapshotChecker), and stacked BlobDB. Integrated BlobDB is supported. - Do not use SingleDelete on keys handled by a new-order filter, even after switching back to the default order. - How operands are grouped into partial results can differ between compactions, and SST files keep no record of earlier groupings. Filter decisions must be correct for any grouping. - **Stress test limits:** the stress merge operators keep only the newest operand, and every stress filter decision is chosen so that reads do not change. So both orders give the same visible result, and the oracle checks robustness of the new paths (wrong output key or sequence, lost or returning data, buffer and pinning errors, crash recovery, remote fallback), not whether the filter saw the right input. A same-bytes rewrite also cannot detect a change that RocksDB ignores. Skips only run in full compactions, which are rare in leveled runs. The decision counters print only when db_stress exits normally (not in blackbox kills). - **Unit tests cover the rest:** which input the filter sees (full, partial, uncombined), the snapshot rule, changed values in the output (`MergeBeforeFilterFullReplacementAndFailure`, `MergeBeforeFilterPartialAndDecline`, remote full/partial tests), skip and removal results, kPurge handling, unsupported decisions, early rejection, remote routing and fallback, and BlobDB. - Validation: the full suites of the touched test binaries pass, and local db_stress and crash-test runs with the new modes pass. See the Test Plan for details and the benchmark results. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 31, 2026 19:17
15003bc to
2701126
Compare
Summary: Pull Request resolved: meta#15517 ## Why Some useful compaction filters only make sense on merged results. For example, a filter that removes counters below a threshold must see the counter's total, not each `+N` operand. With the current order, the compaction filter runs before the merge operator, so it sees (and can remove) individual operands and never sees the merge result. Important limit: compaction can only merge what is in its own inputs. If a key's base value or older operands are in files outside the compaction, the filter sees a partial merge result (or single operands), not the logical value of the key. ## Behavior - **Default (unchanged):** `CompactionFilter::MergeOrder::kFilterBeforeMerge`. Operands are filtered one by one before merging. - **New opt-in:** a filter or factory returns `kMergeBeforeFilter` from the new `GetMergeOrder()`. During compaction the merge operator runs first, then the filter sees: - the full merge result as `kValue` / `kWideColumnEntity`, if the inputs include the base (Put or tombstone), or the compaction is bottommost with no older entries; - otherwise the operands as `kMergeOperand`: one partial result from `PartialMergeMulti`, or each operand by itself if partial merge declines. - A filter result is not merged again in the same compaction. Filters that only implement FilterV2/V3 also work. - A merge result is offered only if no snapshot can see any of its operands. Plain values are filtered as before, regardless of snapshots. - `kPurge` on a value is treated as `kRemove` (ordinary Delete), because SingleDelete does not mix with Merge history. `kPurge` and `kChangeWideColumnEntity` on an operand fail the compaction with NotSupported. - `kRemoveAndSkipUntil` on any operand of an uncombined batch drops the whole batch, the same result as the default order. - `COMPACTION_KEY_DROP_USER` keeps the same meaning in both orders (skipped ranges are not counted). - The new order applies only to compaction. Flush, recovery, MemPurge, and repair keep the default order. ### Example Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is `Merge(+3)`, `Merge(+5)`, `Put(4)`. Before: the filter sees the raw operands `+3` and `+5`, removes both, and the output is `Put(4)`; it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12), the filter sees `kValue` "12", and keeps it. If the base is not in the compaction, partial merge gives `+8` and the filter sees it as `kMergeOperand`. ## Implementation - `MergeHelper` carries the merge order and records each merge outcome (full, partial, uncombined) and whether it can be filtered under snapshots. - `CompactionIterator` reads the order from `MergeHelper` and filters after the merge. A full result changed to Delete goes through the normal `ProcessDeletion()` path. Uncombined operand batches are filtered as a whole before any of them is output, so a skip decision can drop the full batch. - Order checks (`GetCompactionFilterMergeOrder()`) run outside the DB mutex. A factory's non-null filters must declare the same order; otherwise the compaction fails with InvalidArgument. A factory that returns a null filter means no filtering, which is valid with either order. - **Remote compaction:** `CompactionService::SupportsMergeBeforeFilter()` (default false) lets a service receive new-order jobs; otherwise they run locally. The request carries `merge_before_filter`, and the worker acknowledges the order it actually used. A missing or wrong acknowledgment (for example, from an older worker) falls back to local compaction before any output is imported. Real compaction errors stay errors. A worker with no filter accepts either order. - **Early rejection:** DB open and CF creation reject a new-order declaration with user-defined timestamps, WritePrepared/WriteUnprepared transactions, stacked BlobDB, or a factory that selects non-compaction table file creation. This avoids a background error that would stop writes at the first compaction. - `ShouldFilterTableFileCreation()` checks use an exhaustive switch over `TableFileCreationReason`, so a new reason must be classified. - **Bug fix included:** `MemPurge()` returned with the DB mutex unlocked when the flush filter failed a check (for example, `IgnoreSnapshots() == false`). The new order check adds another early return on the same path, so the re-lock is fixed here, with a `bug_fixes` release note. - **Stress test:** with `merge_before_compaction_filter`, the db_stress filter now makes real decisions that never change what a reader sees: `kChangeValue` with the same bytes for about 1 in N records (`merge_before_filter_change_one_in`); with `merge_before_filter_remove_deleted`, `kRemove` for keys deleted in the expected state, and `kRemoveAndSkipUntil` over just that key in full compactions (where every version of the key is an input). It also aborts if a value it sees is not a well-formed stress value for its key, or if the new-order factory is asked to filter a non-compaction file. It prints per-type decision counts at the end of a run. The crash test picks these modes randomly and uses merges in about 3/4 of new-order runs; the remove/skip mode uses the same no-snapshot settings as the default-order filter. - `Repairer` only marks `BuildTable()`'s `IOStatus` as checked, because `BuildTable()` can now return early before reading it. Repair behavior does not change. ## Safety and limitations - The default order and existing filters behave as before. There is no SST format change, no new CF option, and no C/Java binding. - Not supported in the new order: user-defined timestamps, WritePrepared/WriteUnprepared transactions (SnapshotChecker), and stacked BlobDB. Integrated BlobDB is supported. - Do not use SingleDelete on keys handled by a new-order filter, even after switching back to the default order. - How operands are grouped into partial results can differ between compactions, and SST files keep no record of earlier groupings. Filter decisions must be correct for any grouping. - **Stress test limits:** the stress merge operators keep only the newest operand, and every stress filter decision is chosen so that reads do not change. So both orders give the same visible result, and the oracle checks robustness of the new paths (wrong output key or sequence, lost or returning data, buffer and pinning errors, crash recovery, remote fallback), not whether the filter saw the right input. A same-bytes rewrite also cannot detect a change that RocksDB ignores. Skips only run in full compactions, which are rare in leveled runs. The decision counters print only when db_stress exits normally (not in blackbox kills). - **Unit tests cover the rest:** which input the filter sees (full, partial, uncombined), the snapshot rule, changed values in the output (`MergeBeforeFilterFullReplacementAndFailure`, `MergeBeforeFilterPartialAndDecline`, remote full/partial tests), skip and removal results, kPurge handling, unsupported decisions, early rejection, remote routing and fallback, and BlobDB. - Validation: the full suites of the touched test binaries pass, and local db_stress and crash-test runs with the new modes pass. See the Test Plan for details and the benchmark results. Differential Revision: D128313536
xingbowang
force-pushed
the
export-D128313536
branch
from
October 31, 2026 19:20
2701126 to
d02459e
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
Some useful compaction filters only make sense on merged results. For example, a filter that removes counters below a threshold must see the counter's total, not each
+Noperand. With the current order, the compaction filter runs before the merge operator, so it sees (and can remove) individual operands and never sees the merge result.Important limit: compaction can only merge what is in its own inputs. If a key's base value or older operands are in files outside the compaction, the filter sees a partial merge result (or single operands), not the logical value of the key.
Behavior
CompactionFilter::MergeOrder::kFilterBeforeMerge. Operands are filtered one by one before merging.kMergeBeforeFilterfrom the newGetMergeOrder(). During compaction the merge operator runs first, then the filter sees:kValue/kWideColumnEntity, if the inputs include the base (Put or tombstone), or the compaction is bottommost with no older entries;kMergeOperand: one partial result fromPartialMergeMulti, or each operand by itself if partial merge declines.kPurgeon a value is treated askRemove(ordinary Delete), because SingleDelete does not mix with Merge history.kPurgeandkChangeWideColumnEntityon an operand fail the compaction with NotSupported.kRemoveAndSkipUntilon any operand of an uncombined batch drops the whole batch, the same result as the default order.COMPACTION_KEY_DROP_USERkeeps the same meaning in both orders (skipped ranges are not counted).Example
Counter merge operator (add) with a filter that removes counters below 10. The compaction input for one key is
Merge(+3),Merge(+5),Put(4). Before: the filter sees the raw operands+3and+5, removes both, and the output isPut(4); it never sees the merged value. After: the merge runs first (4 + 5 + 3 = 12), the filter seeskValue"12", and keeps it. If the base is not in the compaction, partial merge gives+8and the filter sees it askMergeOperand.Implementation
MergeHelpercarries the merge order and records each merge outcome (full, partial, uncombined) and whether it can be filtered under snapshots.CompactionIteratorreads the order fromMergeHelperand filters after the merge. A full result changed to Delete goes through the normalProcessDeletion()path. Uncombined operand batches are filtered as a whole before any of them is output, so a skip decision can drop the full batch.GetCompactionFilterMergeOrder()) run outside the DB mutex. A factory's non-null filters must declare the same order; otherwise the compaction fails with InvalidArgument. A factory that returns a null filter means no filtering, which is valid with either order.CompactionService::SupportsMergeBeforeFilter()(default false) lets a service receive new-order jobs; otherwise they run locally. The request carriesmerge_before_filter, and the worker acknowledges the order it actually used. A missing or wrong acknowledgment (for example, from an older worker) falls back to local compaction before any output is imported. Real compaction errors stay errors. A worker with no filter accepts either order.ShouldFilterTableFileCreation()checks use an exhaustive switch overTableFileCreationReason, so a new reason must be classified.MemPurge()returned with the DB mutex unlocked when the flush filter failed a check (for example,IgnoreSnapshots() == false). The new order check adds another early return on the same path, so the re-lock is fixed here, with abug_fixesrelease note.merge_before_compaction_filter, the db_stress filter now makes real decisions that never change what a reader sees:kChangeValuewith the same bytes for about 1 in N records (merge_before_filter_change_one_in); withmerge_before_filter_remove_deleted,kRemovefor keys deleted in the expected state, andkRemoveAndSkipUntilover just that key in full compactions (where every version of the key is an input). It also aborts if a value it sees is not a well-formed stress value for its key, or if the new-order factory is asked to filter a non-compaction file. It prints per-type decision counts at the end of a run. The crash test picks these modes randomly and uses merges in about 3/4 of new-order runs; the remove/skip mode uses the same no-snapshot settings as the default-order filter.Repaireronly marksBuildTable()'sIOStatusas checked, becauseBuildTable()can now return early before reading it. Repair behavior does not change.Safety and limitations
MergeBeforeFilterFullReplacementAndFailure,MergeBeforeFilterPartialAndDecline, remote full/partial tests), skip and removal results, kPurge handling, unsupported decisions, early rejection, remote routing and fallback, and BlobDB.Differential Revision: D128313536