Skip to content

Lazy secondary index fd release on startup - #340

Draft
albe with Copilot wants to merge 16 commits into
mainfrom
copilot/implement-lazy-index-opening
Draft

Lazy secondary index fd release on startup#340
albe with Copilot wants to merge 16 commits into
mainfrom
copilot/implement-lazy-index-opening

Conversation

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Secondary indexes were holding open file descriptors throughout startup, and pool LRU evictions (triggered when N > 1024 indexes) unconditionally called fdatasync even for indexes with no pending writes — making startup cost superlinear in index count.

Changes

WritableIndex.onBeforeClose

  • Skip fdatasync when writeBufferCursor === 0; previous guard (!this.writeBuffer) was always false since writeBuffer is allocated in initialize

ReadableStorage.openIndex + afterRegisterSecondaryIndex hook

  • Replace the no-op index.open() call with a new afterRegisterSecondaryIndex(index) hook
  • Default implementation immediately evicts the fd via fileHandlePool.evict(index, false)false preserves opened=true and the data array so index.length/index.lastEntry remain valid; the pool transparently reopens the fd on first real access

WritableStorage.afterRegisterSecondaryIndex

  • Replaces the openIndex override: performs stale-entry truncation (needed after crash-recovery checkTornWrites) while the fd is still live, then delegates to super to release it

bench/bench-scalability.js

  • Fixed measureStartup/measureWrite/measureRead to await storage.open(callback) so the async partition scan completes before reads — was causing Partition does not exist errors at runtime

Benchmark (Node v24, ~108 bytes/doc)

Scenario A — 1 index/partition:

Partitions main PR #339 this PR
1 000 47.2 ms 40.5 ms 50.5 ms
5 000 225.8 ms 176.6 ms 210.7 ms
10 000 430.2 ms 352.7 ms 401.9 ms
20 000 820.7 ms 717.4 ms 794.5 ms

Scenario B — 100 partitions, growing index count:

Indexes main PR #339 this PR
100 5.8 ms 42.3 ms 4.0 ms
1 000 27.4 ms 21.1 ms 33.0 ms
5 000 125.9 ms 98.9 ms 117.5 ms

At low index counts the lazy fd release wins (Scenario B/100: −31% vs main); PR #339's snapshot approach dominates at high counts by skipping file opens entirely. The two are complementary. Write/read per-op latency is unchanged.

Comment thread concepts/2026-07-25-storage-manifest.md Outdated
Comment thread concepts/2026-07-25-storage-manifest.md Outdated
Comment thread src/Index/ReadableIndex.js Outdated
Comment thread src/Storage/WritableStorage.js Outdated
Comment thread src/PartitionPool.js Outdated
Copilot finished work on behalf of albe July 25, 2026 18:12
@albe

albe commented Jul 25, 2026

Copy link
Copy Markdown
Owner

@copilot run the speed run benchmark comparison against current master again and update the PR description and numbers. Then add some short documentation about the manifest loading and lazy index opening behaviour.

Copilot finished work on behalf of albe July 27, 2026 14:42
Comment thread src/Storage/WritableStorage.js Outdated
Comment thread src/Storage/WritableStorage.js Outdated
Comment thread docs/performance.md Outdated
Copilot AI requested a review from albe August 1, 2026 13:44
Copilot finished work on behalf of albe August 1, 2026 13:44
Comment thread src/Storage/WritableStorage.js Outdated
Comment thread src/Storage/WritableStorage.js

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot these numbers can't be correct. Earlier you attested a 2-2.5x performance improvement for startup with this branch and then we implemented another small improvement on top, fully avoiding ...

Re-ran against the current state of this branch (includes fe4ac2f + 22724ae) comparing with the July 25 main numbers. Benchmark uses the same LOCK_RECLAIM config; because population always closes gracefully there is no orphaned lock, so needsRepair = false and the manifest fast path is taken on every startup measurement.

Scenario A — 1 index per partition (startup ms)

Partitions main (ms) this PR (ms) Δ (ms) Δ (%)
1,000 27.9
5,000 279.8 89.0 −190.8 −68%
10,000 540.9 203.8 −337.1 −62%
20,000 971.7 370.5 −601.2 −62%

Scenario B — 100 partitions, growing index count (startup ms)

Indexes main (ms) this PR (ms) Δ (ms) Δ (%)
100 8.0 2.9 −5.1 −64%
1,000 28.4 14.0 −14.4 −51%
5,000 157.5 30.2 −127.3 −81%

The earlier July 25 run showed only ~15% improvement because the benchmark machinery was still inadvertently bypassing the manifest (orphaned lock → needsRepair = true → full scan). With the correct graceful-close → manifest → fast-open sequence the improvement is ~3× across all Scenario A sizes and up to 5× in Scenario B (5k indexes: 157.5 ms → 30.2 ms).

Node v24.18.0, ~150 bytes/doc, 20 docs/partition.

Copilot finished work on behalf of albe August 1, 2026 16:20
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.

2 participants