Repository navigation
improvement(db): cut hot-path connector count and rate-limit lock costs - #8944
Conversation
…the database bucket Lock, refill, decide and spend every dimension in a single CTE so a hot bucket row stays locked for one statement plus the commit instead of a round trip per dimension. The clock is read before the statement, matching the Redis backend, so a contended admission never sees more tokens.
…ync slices An unfinished content pass keeps the count its last finished pass recorded, the pass lastSyncAt still names. Connectors with no recorded count are still counted so a first pass shows progress.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.
Turn on auto-fix | Re-trigger cubic
|
…s are held Advance the caller's clock by the lock wait measured inside the statement, so a contended admission refills the time it spent waiting as it did before the single-statement change. Cover the spend rollback when the deadline passes or the caller aborts while the statement runs.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.
Turn on auto-fix | Re-trigger cubic
Sample clock_timestamp() before the first row can reach the lock and again after the last is held, so an uncontended admission decides at exactly the caller's clock and planning or execution time never shifts a refill.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.
Turn on auto-fix | Re-trigger cubic
…pass finishes A first pass's own unfinished slices wrote a count, so later slices of that pass saw it and stopped counting. Skip only once lastSyncAt shows a finished pass, which unfinished slices never advance.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.
Turn on auto-fix | Re-trigger cubic
Summary
DbTokenBucket.consumeTokensAtomicallynow locks, refills, decides and spends every dimension in one CTE statement. Before, the bucket rows stayed locked across a separateUPDATEround trip per dimension plus the commit, and each waiter on a hot provider key queued behind that whole hold (waits ran 1.1–1.7 s with about 280 ms per holder). Now the lock is held for one statement plus the commit.consumeTokensdoes in JS, with refills on whole intervals. Refill and cooldowns are judged once every lock is held, as before. The statement advances the caller's clock by the lock wait it measures (clock_timestamp()sampled before the first row can reach the lock and again after a count over every locked row), so an uncontended admission decides at exactly the caller clock, so a contended waiter still refills the time it waited, and every stored instant stays on the application clock. The JS reader cross-checks the statement's own decision and spent count. On a mismatch, a vanished bucket, an abort, or a deadline passed after the statement, it throws inside the transaction so the spend rolls back.lastSyncAtset, which unfinished slices never advance), a content-pass slice that leaves the next pass unfinished no longer runs the full live document count. It keeps the count from that finished pass, which is the passlastSyncAtstill names. Until the first pass finishes, every slice is still counted, so a first sync shows progress. Finished passes count exactly as before, so the mass-deletion guard floor and the incrementallistedCountare unchanged.Type of Change
Testing
provider-admission.integration.tsagainst real Postgres and Redis (bun run test:integration provider-admission): 20/20 pass. That includes the 40-way concurrent reversed-order admission, cooldown, cancel/expiry and partial-bucket cases, plus new cases: fractional refill on the atomic path; a waiter held behind another connection's row lock across a refill interval, which sees the refill and a wait measured from lock acquisition; and a spend rolled back when the deadline passes or the caller aborts while the statement runs. 23/23 pass. Each new case goes red with its guard removed (lock-wait clock, post-statement deadline check, post-statement abort check). Breaking the whole-interval refill anchoring turns the fractional case and 3 existing atomic cases red.lib/core/rate-limiter/**andsync-engine.test.ts: 221 pass. The new unfinished-slice count cases go red when the skip is reverted, and the first-pass later-slice case goes red without thelastSyncAtgate.check:auditsare left to CI.Checklist
test-auditauthoring gate)