Uh oh!
There was an error while loading. Please reload this page.
PHOENIX-7961 Secondary index diverges from data table after TTL expiry on partial-touch upserts - #2602
Open
sanjeet006py wants to merge 1 commit into
Open
Conversation
…y on partial-touch upserts
sanjeet006pyforce-pushed
the
fix-index-data-table-sync-5.3
branch
from
August 18, 2026 06:18
1affc10 to
e599099Comparesanjeet006py
commented
Aug 18, 2026
ContributorAuthor
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 freeto 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.
Cherry-pick of #2574 onto the 5.3 branch.
What changes were proposed in this pull request?
This PR makes Phoenix's internal current-row read during secondary-index maintenance TTL-mask exactly like an ordinary client read, so a data table and its secondary index stay consistent after TTL expiry.
Index maintenance in IndexRegionObserver.preBatchMutateWithExceptions reads the current on-disk row via getCurrentRowStates to rebuild the correct index entry. That read was opened directly through region.getScanner(scan), which bypasses the postScannerOpen coprocessor hook — the only place a scan is normally wrapped in TTLRegionScanner. As a result the internal read was not TTL-masked, so on a "partial touch" upsert (an UPSERT that does not re-write any index-referenced column) the index was rebuilt from logically-expired-but-not-yet-compacted cells, while the data table correctly expired them on read.
Changes:
Production files touched: IndexRegionObserver, ScanUtil, new ServerScanUtil, MutationState, and a doc-comment update in MetaDataClient.
Why are the changes needed?
This is a silent data-integrity/correctness bug. On a table or view with a literal TTL and a secondary index, after TTL expiry the secondary index can return a column value the data table no longer returns — the two diverge with no error raised. The divergence surfaces after logical TTL expiry and before major compaction physically purges the expired cells.
The root cause is that the internal current-row read bypassed TTLRegionScanner, so index rebuild saw expired cells the data-side read masks. The affected paths are: secondary indexes (global covered, global uncovered, and immutable indexes whose data/index storage schemes differ) and the no-index current-row reads on a literal-TTL table (atomic / ON DUPLICATE KEY upserts, returnResult upserts, and row deletes). Conditional-TTL, non-TTL, and non-strict-TTL tables are unaffected — masking is a no-op there and the read is byte-identical to before.
Does this PR introduce any user-facing change?
Yes — a bug fix (behavior change) relative to 5.3 and released versions, for tables/views with a literal TTL and a secondary index.
No API, syntax, or configuration change. No new config flag is introduced.
How was this patch tested?
New integration test IndexDataTableSyncIT (parameterized over column-encoded on/off), covering:
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Anthropic Claude Opus 4.8)