Visitar URL original
fix(#8817, #8814, #8813): tx index range after compaction, UPDATE Halloween problem, TS oldest sample after COMPACT by lvca · Pull Request #8825 · ArcadeData/arcadedb · GitHub
Skip to content

fix(#8817, #8814, #8813): tx index range after compaction, UPDATE Halloween problem, TS oldest sample after COMPACT - #8825

Merged
lvca merged 3 commits into
mainfrom
fix/8817-8814-8813-tx-index-range-halloween-ts-compact
Oct 1, 2026
Merged

lvca merged 3 commits into
mainfrom
fix/8817-8814-8813-tx-index-range-halloween-ts-compact

Conversation

@lvca

@lvca lvca commented Oct 1, 2026

Copy link
Copy Markdown
Member

Fixes #8817
Fixes #8814
Fixes #8813

Tests: Issue8817TxRangeOverCompactedIndexTest, Issue8814UpdateHalloweenTest, Issue8813CompactedOldestSampleTest; engine index, sql, timeseries and tx suites green (4684 tests).

…loween problem, TS directory order after COMPACT

- #8817: the LSM range cursor looked the transaction's pending entries up by the mutable file's name, which a compaction changes; it now uses the owning index's logical name, like get() does
- #8814: an UPDATE that reads an index reads the record addresses before updating any (new MaterializeRecordsStep), so it cannot meet its own moved keys
- #8813: the sealed store directory is kept in minTimestamp order after compaction appends late-arriving blocks and on load; the lower-bound search uses a running max of maxTimestamp, truncate and CHECK follow file order
@lvca lvca added this to the 26.10.1 milestone Oct 1, 2026
@lvca lvca self-assigned this Oct 1, 2026
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 34 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: aa5dcb93-2467-497a-a683-8e7c229fa607

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2c770 and e5a4ec2.

📒 Files selected for processing (8)
  • engine/src/main/java/com/arcadedb/engine/timeseries/TimeSeriesSealedStore.java
  • engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexCursor.java
  • engine/src/main/java/com/arcadedb/query/sql/executor/MaterializeRecordsStep.java
  • engine/src/main/java/com/arcadedb/query/sql/executor/UpdateExecutionPlanner.java
  • engine/src/test/java/com/arcadedb/engine/timeseries/Issue8813CompactedOldestSampleTest.java
  • engine/src/test/java/com/arcadedb/engine/timeseries/Issue8813SealedStoreDirectoryOrderTest.java
  • engine/src/test/java/com/arcadedb/index/Issue8817TxRangeOverCompactedIndexTest.java
  • engine/src/test/java/com/arcadedb/query/sql/executor/Issue8814UpdateHalloweenTest.java
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@codacy-production

codacy-production Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 56 complexity

Metric Results
Complexity 56

View in Codacy

🟢 Coverage 93.90% diff coverage · -5.92% coverage variation

Metric Results
Coverage variation ✅ -5.92% coverage variation
Diff coverage ✅ 93.90% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (7f2c770) 204408 172771 84.52%
Head commit (e5a4ec2) 237121 (+32713) 186375 (+13604) 78.60% (-5.92%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#8825) 164 154 93.90%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@claude

claude Bot commented Oct 1, 2026

Copy link
Copy Markdown

Review

Three well-diagnosed fixes, each with a regression test that reproduces the report. Compared with the PR description, the root causes look right. Notes below, roughly by importance.

#8814 MaterializeRecordsStep / UpdateExecutionPlanner

  1. Cost on every index-backed UPDATE. readsIndex(sourcePlan) is true for any index read, including the common UPDATE T SET x = ? WHERE id = ?. Those statements now go through a barrier, a sort and a second getRecord() by address, even though they cannot hit the Halloween problem. The problem only exists when the SET targets a property of the index being walked. Gating on that would keep the hot point-update path as it was. At minimum, a short comment or a benchmark number showing the overhead is negligible would help. CLAUDE.md asks us to protect performance.

  2. LIMIT can under-deliver. drain counts raw buffered addresses (buffer.size()), but syncPull later drops duplicates (multi-value index returning one RID per key) and deleted records. UPDATE ... LIMIT 5 can then update fewer than 5 records even when more match. This is an edge case, but the step comment says "up to N", so I would either dedupe while draining or document it.

  3. Limit evaluated twice. limit.getValue(context) is called once for UpdateExecutionPlan and again for handleTarget. Compute it once.

  4. Semantics change. The records are now re-read after the address pass, so a row changed by an earlier row of the same statement is re-evaluated against its current state, not the WHERE snapshot. The Javadoc documents this, which is good. Update order also changes to physical order. Both are probably fine, but worth a line in the PR description.

  5. Timeout. drain pulls the whole upstream with no timeout check of its own. It relies on upstream steps honoring it, which is probably OK. Please confirm UPDATE ... TIMEOUT still fires during the drain.

  6. Tests. The Halloween test only covers a non-unique LSM index with plain range spellings. Missing cases:

    • compound index
    • several buckets, since the code has per-bucket slices and a dedup reset
    • a multi-value (BY ITEM) index for the dedup path
    • a deleted-since-read record
    • an UPSERT regression check
    • UPDATE ... RETURN AFTER

    Also, limitStillStopsEarly asserts the count only, not that the plan stopped reading early.

  7. Minor: the comment in handleTarget is missing a period after "addresses are read first". The error-path comment in syncPull is in ALL CAPS, unlike the rest of the code.

#8817 LSMTreeIndexCursor

The fix is minimal and consistent with get()/addIndexKeyLock(). Good. getMainIndex() is null-guarded. Please check whether getClosestEntryInTx has sibling lookups by index.getName() elsewhere in the cursor (for example a descending path or a TxChanges helper) that need the same change. The test compacts only after loading, so it does not cover a second compaction, where the mutable file name changes again.

#8813 TimeSeriesSealedStore

  • The running-max prefix is the right fix for overlapping blocks. The version-plus-length validation is sound: directoryVersion is written under the write lock and read under the read lock, and the racy publication of the volatile cache only costs a redundant rebuild.
  • restoreMinTimestampOrder() in appendBlock is a full scan of the directory on every append, so appends become O(blocks). The directory is probably small, but a cheap check on the last element (entry.minTimestamp >= previous last) before scanning would keep the common case O(1).
  • directoryVersion is incremented inside the loadDirectory loop once per block. One bump after the loop would do.
  • truncateBefore and the other rewrite paths bump the version twice (clear, then addAll). That is harmless, but a single bump after the final state is clearer.
  • checkIntegrity and truncateToBlockCount allocate and sort a copy by blockStartOffset. That is fine for rare operations, but the sort could be skipped when the directory is already in file order.
  • Line ~3483 blockDirectory.getFirst().minTimestamp stays valid because the directory is sorted by minTimestamp. Worth confirming that all other places that assume "last entry = last written block" (for example anything deriving a block count or next offset from the directory tail) were audited. I found none in a quick grep of TimeSeriesSealedStore.
  • Issue8813CompactedOldestSampleTest inserts 200k rows with an SQL command per row. It is tagged slow, so it is acceptable. It is also seeded and deterministic, which is good. The checks are strong: oldest sample plus window counts, both before and after reopen.
  • Style: the new Comparator.comparingLong(...) calls need java.util.Comparator imported (check it is). Several new comments are long multi-sentence blocks. CLAUDE.md prefers short comments.

Style and conventions

  • Tests use assertThat(...) style, extend TestHelper, and carry license headers. Good.
  • The @author Luca Garulli Javadoc on new files follows the repo habit, but please confirm that is intended, given the "don't add Claude as author" rule.
  • Issue8813...Test fields are misaligned (COMPACT_EVERY). That is cosmetic.

Verdict

No blocking correctness problems found. The main thing I would change before merging is point 1 for #8814: restrict the barrier to UPDATEs that modify a property of the index being read, or show the overhead is negligible. Points 2 and 6 are worth addressing too.

…rty, dedupe under LIMIT, cheaper directory order checks, more tests
@claude

claude Bot commented Oct 1, 2026

Copy link
Copy Markdown

Review of PR 8825 (tx index range after compaction, UPDATE Halloween, TS oldest sample after COMPACT)

Overall this is a well-reasoned set of three fixes. The root causes are correctly identified and the comments explain the "why". I did not run the tests locally; this is a read-through of the diff plus a few greps.

8817 - LSMTreeIndexCursor

  • The fix is correct and consistent with LSMTreeIndex.get() (LSMTreeIndex.java:570 uses the logical name). The mainIndex != null ? ... : index.getName() fallback matches the existing describeIndexForRebuild() pattern. The remaining getName() uses in the cursor are only messages.
  • Test is good: unique/non-unique, below/above/inside range, tx delete + update, min/max, ORDER BY, openCypher, and the raw Index.range() API.

8814 - MaterializeRecordsStep / UpdateExecutionPlanner

  • Nice use of PhysicalOrderRidBuffer (one long per record). The targeted trigger (readsIndex + modifiedProperties, conservative null for non-SET ops and paths) keeps the barrier off the common path, and the EXPLAIN-based test pinning "barrier only when needed" is a good touch.
  • Behaviour change: rows now reach the UPDATE in physical order rather than index order. Visible for RETURN AFTER/RETURN BEFORE over an index range. With LIMIT n the set is still the first n in index order, but output order changes. Probably fine, worth a line in the PR description.
  • Timeout: drain() has no explicit timeout check and relies on the upstream select timeout. handleTimeout is chained after this step, so UPDATE ... TIMEOUT over a very large range may only be enforced after the barrier drained. Consider checking the context timeout inside the drain loop.
  • Retry after failure: if drain() throws, drained is already true and slices stays null, so a later syncPull NPEs. Minor: set drained = true only after success, or initialise slices to an empty list.
  • Tests: limitStillStopsEarly only checks the count; it could also assert each updated row has b == 1. A test for a non-SET op (e.g. INCREMENT a) hitting the barrier, and one confirming UPSERT is left untouched, would cover the remaining modifiedProperties() branches.

8813 - TimeSeriesSealedStore

  • The running-max prefix for the lower-bound search is the right fix for overlapping blocks. The directoryVersion cache guard is sound (written under the write lock, validated under the read lock, volatile publish; concurrent duplicate rebuilds are benign). rewriteWithBlocks and truncateBefore already produce a minTimestamp-ordered directory, so no re-sort is missed there.
  • appendBlock sorts the whole directory under the write lock on each out-of-order append. TimSort on nearly sorted data is close to O(n), but inserting the single entry at its binary-searched position would avoid the comparator sort. Optional.
  • directoryVersion is bumped twice around every clear/addAll (and again in restoreMinTimestampOrder). Harmless, but one bump after the final state is easier to reason about.
  • truncateToBlockCount and checkIntegrity now follow file order via blockStartOffset, which is correct.
  • Coverage gap: Issue8813CompactedOldestSampleTest is end-to-end (200k rows, @Tag("slow"), appropriate). There is no focused store-level test for truncateToBlockCount / checkIntegrity with an out-of-order directory, which are behaviour changes in this PR. A small test (append blocks with decreasing minTs, truncate/check, reopen) would pin those paths cheaply.

Style (CLAUDE.md)
Conventions look followed (finals, imports, braceless single-statement ifs, assertj style). Nit: constants in Issue8813CompactedOldestSampleTest are misaligned.

No blocking issues found. Before merge I would address the drain timeout and NPE-on-retry robustness, and add a focused test for the truncateToBlockCount/checkIntegrity ordering changes.

…after a failed drain, store-level test for directory order
@tae898

tae898 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Pre-verified at the head e5a4ec28cf (engine built from it) against 3d5e1a514c, with each issue's repro. Temurin 25 with compact object headers and Temurin 21 give the same answers:

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.14634% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.28%. Comparing base (7f2c770) to head (e5a4ec2).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...edb/query/sql/executor/MaterializeRecordsStep.java 78.46% 7 Missing and 7 partials ⚠️
...edb/query/sql/executor/UpdateExecutionPlanner.java 74.35% 3 Missing and 7 partials ⚠️
...adedb/engine/timeseries/TimeSeriesSealedStore.java 98.27% 0 Missing and 1 partial ⚠️
...ava/com/arcadedb/index/lsm/LSMTreeIndexCursor.java 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8825      +/-   ##
============================================
+ Coverage     76.27%   76.28%   +0.01%     
+ Complexity     3743     3742       -1     
============================================
  Files          2097     2098       +1     
  Lines        204408   204544     +136     
  Branches      43014    43047      +33     
============================================
+ Hits         155911   156036     +125     
  Misses        31679    31679              
- Partials      16818    16829      +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

robfrank pushed a commit that referenced this pull request Oct 6, 2026
…rty, dedupe under LIMIT, cheaper directory order checks, more tests

(cherry picked from commit 71e346f)
robfrank pushed a commit that referenced this pull request Oct 6, 2026
…after a failed drain, store-level test for directory order

(cherry picked from commit e5a4ec2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment