perf(ai-vault): bound scope cache write batch enumeration (#31950)

* perf(ai-vault): bound scope cache write batch enumeration

* test(ai-vault): type scope cache stamp fixture

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-22 13:20:23 -07:00
committed by GitHub
co-authored by OrcaWin
parent c1709f8f81
commit ac9dc0bc0d
2 changed files with 54 additions and 13 deletions
@@ -25,7 +25,12 @@ import {
type SessionParseStats
} from './session-scanner-parse-cache'
import type * as ParseCacheModule from './session-scanner-parse-cache'
import { getSessionParseCacheEntry, storeSessionParseCacheEntry } from './session-parse-cache-store'
import {
getSessionParseCacheEntry,
storeSessionParseCacheEntry,
storeSessionScopeCwd,
type SessionScopeCwdStamp
} from './session-parse-cache-store'
import type { SessionSidecarObservation } from './session-sidecar-stat'
import { isolatedScanRoots } from './session-scanner-test-fixtures'
import { parseClaudeSessionFile } from './session-scanner-primary-parsers'
@@ -163,6 +168,40 @@ describe('session parse cache persistence', () => {
expect(snapshot).not.toBe(configured)
})
it('persists scope rows across chunks and keeps replacements queued during a yield', async () => {
const root = await makeTempDir()
const cacheFile = join(root, 'session-parse-cache.json')
initSessionParseCachePersistence({ filePath: cacheFile, appVersion: APP_VERSION })
const paths = Array.from({ length: 1_001 }, (_, index) => join(root, `session-${index}`))
const stamp: SessionScopeCwdStamp = {
mtimeMs: 1,
sizeBytes: 10,
platform: 'linux',
cwd: '/original'
}
for (const path of paths) {
storeSessionScopeCwd(path, stamp)
}
const flushing = flushSessionParseCachePersist()
const replacement = { ...stamp, mtimeMs: 2, cwd: '/updated' }
storeSessionScopeCwd(paths[0]!, replacement)
storeSessionScopeCwd(paths[750]!, replacement)
await flushing
resetSessionParseCachePersistenceForTests()
resetSessionParseCacheForTests()
const database = new SessionParseCacheDatabase(join(root, 'session-parse-cache.sqlite'))
try {
const stored = database.readScopeCwds(paths).found
expect(stored.size).toBe(paths.length)
for (const [index, path] of paths.entries()) {
expect(stored.get(path)?.read).toEqual(index === 0 || index === 750 ? replacement : stamp)
}
} finally {
database.close()
}
})
it('round-trips: a persisted entry is a reused hit after a restart, without reading the transcript', async () => {
const root = await makeTempDir()
const cacheFile = join(root, 'vault-state', 'session-parse-cache.json')
@@ -88,24 +88,14 @@ export function resetSessionParseCachePersistenceForTests(): void {
export async function flushSessionParseCachePersist(): Promise<void> {
clearWriteTimer()
while (pendingWrites.size > 0) {
const chunk: [string, PersistedSessionParseCacheEntry][] = []
for (const row of pendingWrites) {
chunk.push(row)
pendingWrites.delete(row[0])
if (chunk.length === WRITE_CHUNK_ROWS) {
break
}
}
const chunk = takeWriteChunk(pendingWrites)
openedDatabase()?.write(chunk, Date.now())
if (pendingWrites.size > 0) {
await yieldToEventLoop()
}
}
while (pendingScopeCwds.size > 0) {
const chunk = [...pendingScopeCwds].slice(0, WRITE_CHUNK_ROWS)
for (const [path] of chunk) {
pendingScopeCwds.delete(path)
}
const chunk = takeWriteChunk(pendingScopeCwds)
openedDatabase()?.writeScopeCwds(chunk, Date.now())
if (pendingScopeCwds.size > 0) {
await yieldToEventLoop()
@@ -113,6 +103,18 @@ export async function flushSessionParseCachePersist(): Promise<void> {
}
}
function takeWriteChunk<T>(pending: Map<string, T>): [string, T][] {
const chunk: [string, T][] = []
for (const row of pending) {
chunk.push(row)
pending.delete(row[0])
if (chunk.length === WRITE_CHUNK_ROWS) {
break
}
}
return chunk
}
// Opened on first need, so Electron main (which never looks rows up) never opens it.
function openedDatabase(): SessionParseCacheDatabase | null {
if (database !== undefined || !owner || options === null) {