Skip to content

Conversation

@flemzord
Copy link
Member

@flemzord flemzord commented Feb 6, 2026

Summary

  • scope ledger reads with a cached isAloneInBucket hint set at store open/create time
  • add CountLedgersInBucket to system store and wire it in driver create/open flows
  • document the cache invalidation constraint to prevent cross-ledger data exposure when caching stores

Validation

  • go test ./internal/storage/ledger ./internal/storage/driver

…coping

The per-query sub-select on _system.ledgers to determine if a ledger is
alone in its bucket is replaced by a boolean flag set at store open/create
time. This avoids repeated cross-schema queries and prepares the ground
for safe store caching (the flag must be invalidated when bucket
composition changes).

Backport of #1250.
@flemzord flemzord requested a review from a team as a code owner February 6, 2026 10:42
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Walkthrough

The changes introduce an optimization for ledger query filtering by computing and caching a flag that indicates whether a ledger is the sole ledger in its bucket. This flag is set during ledger creation and opening, and subsequently used to conditionally skip ledger filters in query construction, replacing prior dynamic bucket-level count operations.

Changes

Cohort / File(s) Summary
System Store Interface & Implementation
internal/storage/system/store.go, internal/storage/driver/store.go
Added new CountLedgersInBucket(ctx context.Context, bucket string) method to count ledgers in a specified bucket via SQL COUNT query.
Driver Ledger Lifecycle
internal/storage/driver/driver.go
Modified CreateLedger and OpenLedger to count ledgers in the target bucket after ledger store creation/opening and set the alone-in-bucket flag accordingly. Added system store caching and refresh logic.
Ledger Store Query Optimization
internal/storage/ledger/store.go
Added isAloneInBucket boolean field to Store struct with public setter. Simplified newScopedSelect to conditionally apply ledger filter based on the flag, replacing previous complex bucket-level counting logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A ledger hops into its bucket, alone or not,
No longer counting siblings at query time, what a plot!
A flag set once, reused with glee,
Faster filters, efficiency decree! 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(storage): scope ledger queries with bucket hint' directly describes the main change: introducing a bucket hint (isAloneInBucket flag) to scope ledger queries instead of using dynamic subqueries.
Description check ✅ Passed The description is directly related to the changeset, explaining the key changes: scoping reads with isAloneInBucket hint, adding CountLedgersInBucket method, and documenting cache invalidation constraints.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch flemzord/backport-bucket-flag

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

@flemzord flemzord changed the title fix: replace dynamic sub-query with isAloneInBucket flag fix(storage): scope ledger queries with bucket hint Feb 6, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@internal/storage/driver/driver.go`:
- Around line 80-85: The current CreateLedger flow sets ret.SetAloneInBucket
based on systemStore.CountLedgersInBucket which can race under concurrent
CreateLedger calls (TOCTOU); fix by acquiring a pg_advisory_xact_lock on a
bucket-derived key before counting to serialize bucket membership changes (call
e.g. pg_advisory_xact_lock(hashtext(bucket))) in the same transaction, then
perform systemStore.CountLedgersInBucket and SetAloneInBucket; if acquiring an
advisory lock is not feasible, add a clear comment next to ret.SetAloneInBucket
(and note in OpenLedger) describing the race and why the flag can be stale so
maintainers are aware.

Comment on lines +80 to +85
count, err := systemStore.CountLedgersInBucket(ctx, l.Bucket)
if err != nil {
return fmt.Errorf("counting ledgers in bucket: %w", err)
}
ret.SetAloneInBucket(count == 1)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

TOCTOU: concurrent CreateLedger calls in the same bucket can both see count == 1.

With READ COMMITTED isolation, two concurrent CreateLedger transactions for the same bucket will each see only their own uncommitted insert, so both may set isAloneInBucket = true. The returned store instances will then skip the ledger filter, potentially exposing cross-ledger data for the lifetime of those store objects.

The previous dynamic sub-query was evaluated per-query (after commit), so it was always up to date. This new flag is a point-in-time snapshot that can go stale.

Consider either:

  1. Taking a pg_advisory_xact_lock on the bucket name before counting, to serialize bucket membership changes.
  2. Adding a comment (like in OpenLedger) acknowledging the race, so future maintainers are aware.

At minimum, option 2:

Suggested comment
+		// WARNING: with concurrent CreateLedger calls in the same bucket,
+		// each transaction may see count==1 (READ COMMITTED). The caller
+		// should re-check via OpenLedger if the store is long-lived.
 		count, err := systemStore.CountLedgersInBucket(ctx, l.Bucket)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
count, err := systemStore.CountLedgersInBucket(ctx, l.Bucket)
if err != nil {
return fmt.Errorf("counting ledgers in bucket: %w", err)
}
ret.SetAloneInBucket(count == 1)
// WARNING: with concurrent CreateLedger calls in the same bucket,
// each transaction may see count==1 (READ COMMITTED). The caller
// should re-check via OpenLedger if the store is long-lived.
count, err := systemStore.CountLedgersInBucket(ctx, l.Bucket)
if err != nil {
return fmt.Errorf("counting ledgers in bucket: %w", err)
}
ret.SetAloneInBucket(count == 1)
🤖 Prompt for AI Agents
In `@internal/storage/driver/driver.go` around lines 80 - 85, The current
CreateLedger flow sets ret.SetAloneInBucket based on
systemStore.CountLedgersInBucket which can race under concurrent CreateLedger
calls (TOCTOU); fix by acquiring a pg_advisory_xact_lock on a bucket-derived key
before counting to serialize bucket membership changes (call e.g.
pg_advisory_xact_lock(hashtext(bucket))) in the same transaction, then perform
systemStore.CountLedgersInBucket and SetAloneInBucket; if acquiring an advisory
lock is not feasible, add a clear comment next to ret.SetAloneInBucket (and note
in OpenLedger) describing the race and why the flag can be stale so maintainers
are aware.

@flemzord flemzord closed this Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant