mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 08:02:02 +00:00
* fix(chat): stream journal replay without retaining obsolete revisions * fix: page journal replay reads so no SQLite snapshot outlives its statement - iterateJournalEpochRows fetches one completed LIMIT statement per page instead of a lazily consumed .iterate() cursor, so reduction never runs inside an open read snapshot and a WAL checkpoint can pass mid-replay. Regression test: a checkpoint issued from inside the reducer is not busy. - The retention test now asserts the applyJournalRow spy observed every row, so the 8 MiB bound cannot pass vacuously if the spy stops intercepting. - Reliability gate manifest records the new assertion and the paged read design.
123 lines
4.4 KiB
JavaScript
123 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import assert from 'node:assert/strict'
|
|
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { basename, dirname, join } from 'node:path'
|
|
import { createRequire } from 'node:module'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { build } from 'esbuild'
|
|
|
|
// Pass a directory containing journal-open.ts and journal-row-table.ts from the base commit.
|
|
const baselineDir = process.argv[2]
|
|
assert.ok(
|
|
baselineDir,
|
|
'Usage: node --expose-gc journal-replay-retention-benchmark.mjs BASELINE_DIR'
|
|
)
|
|
assert.ok(global.gc, 'Run with --expose-gc to measure live backing memory during replay')
|
|
const root = fileURLToPath(new URL('../..', import.meta.url))
|
|
const fixture = await mkdtemp(join(tmpdir(), 'orca-journal-replay-bench-'))
|
|
try {
|
|
const implementations = {}
|
|
for (const arm of ['baseline', 'current']) {
|
|
const outfile = join(fixture, `${arm}.cjs`)
|
|
await build({
|
|
stdin: {
|
|
contents:
|
|
"export {openAgentSessionJournal} from './src/main/native-chat/agent-session-journal/journal-store-factory'; export {loadJournal} from './src/main/native-chat/agent-session-journal/journal-open'; export {journalDatabaseFile} from './src/main/native-chat/agent-session-journal/journal-paths';",
|
|
resolveDir: root
|
|
},
|
|
bundle: true,
|
|
platform: 'node',
|
|
format: 'cjs',
|
|
outfile,
|
|
plugins: [
|
|
{
|
|
name: 'replay-memory-probe',
|
|
setup(plugin) {
|
|
plugin.onLoad(
|
|
{ filter: /journal-(?:open|row-table|reducer)\.ts$/ },
|
|
async ({ path }) => {
|
|
const leaf = basename(path)
|
|
let source = await readFile(
|
|
arm === 'baseline' && leaf !== 'journal-reducer.ts'
|
|
? join(baselineDir, leaf)
|
|
: path,
|
|
'utf8'
|
|
)
|
|
if (leaf === 'journal-reducer.ts') {
|
|
const marker =
|
|
'export function applyJournalRow(state: JournalReducerState, row: JournalRow): void {'
|
|
assert.ok(source.includes(marker))
|
|
source = source.replace(
|
|
marker,
|
|
`${marker}\nglobalThis.__replayMemoryProbe?.(row.seq);`
|
|
)
|
|
}
|
|
return { contents: source, loader: 'ts', resolveDir: dirname(path) }
|
|
}
|
|
)
|
|
}
|
|
}
|
|
]
|
|
})
|
|
implementations[arm] = createRequire(import.meta.url)(outfile)
|
|
}
|
|
const identity = {
|
|
sessionId: 'benchmark',
|
|
workspaceId: 'fixture',
|
|
hostId: 'local',
|
|
agent: 'codex',
|
|
providerHandle: { kind: 'codex', threadId: 'thread' }
|
|
}
|
|
const journalDir = join(fixture, 'session')
|
|
const journal = await implementations.current.openAgentSessionJournal({ identity, journalDir })
|
|
const item = { provider: 'codex', threadId: 'thread', turnId: 'turn', ordinal: 0 }
|
|
const text = 'x'.repeat(32768)
|
|
for (let revision = 0; revision < 2000; revision++) {
|
|
await journal.appendItem(
|
|
item,
|
|
{
|
|
kind: 'message',
|
|
role: 'assistant',
|
|
blocks: [{ type: 'text', text: `${text}${revision}` }]
|
|
},
|
|
{ fence: 1 }
|
|
)
|
|
}
|
|
await journal.close()
|
|
for (const arm of ['baseline', 'current', 'current', 'baseline']) {
|
|
global.gc()
|
|
const start = performance.now()
|
|
let loaded = implementations[arm].loadJournal(journalDir, identity.sessionId)
|
|
const ms = performance.now() - start
|
|
assert.equal(loaded.state.items.size, 1)
|
|
assert.equal([...loaded.state.items.values()][0].revision, 2000)
|
|
loaded = null
|
|
global.gc()
|
|
const initialHeap = process.memoryUsage().heapUsed
|
|
let peakLiveHeap = initialHeap
|
|
globalThis.__replayMemoryProbe = (sequence) => {
|
|
if (sequence !== 1 && sequence % 256 !== 0) {
|
|
return
|
|
}
|
|
global.gc()
|
|
peakLiveHeap = Math.max(peakLiveHeap, process.memoryUsage().heapUsed)
|
|
}
|
|
loaded = implementations[arm].loadJournal(journalDir, identity.sessionId)
|
|
delete globalThis.__replayMemoryProbe
|
|
assert.equal(loaded.state.items.size, 1)
|
|
loaded = null
|
|
console.log(
|
|
JSON.stringify({
|
|
arm,
|
|
ms,
|
|
databaseBytes: (await stat(implementations[arm].journalDatabaseFile(journalDir))).size,
|
|
peakLiveHeapDelta: peakLiveHeap - initialHeap
|
|
})
|
|
)
|
|
}
|
|
} finally {
|
|
delete globalThis.__replayMemoryProbe
|
|
await rm(fixture, { recursive: true, force: true })
|
|
}
|