perf(ai-vault): keep the JSONL line carry linear (#10783)

consumeCompleteJsonlLines re-joined its held-over partial line with every
stream chunk, so one oversized record — a large tool result — cost O(record^2).
It backs the incremental parse for every resumable agent transcript, so the
whole AI Vault corpus paid it.

Hold the pieces in a list and join once, when a newline finally arrives.
Measured on a transcript with a single oversized record: 2.13 ms -> 1.17 ms at
1 MB and 68.22 ms -> 4.35 ms at 8 MB, with byte-identical output. A transcript
of ordinary records never reaches the branch.
This commit is contained in:
Neil 2026-07-26 20:31:48 -07:00 committed by GitHub
parent a8a2e6cb1f
commit 8518cf47af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 5 deletions

View File

@ -128,6 +128,45 @@ describe('parseAgentSessionFileCached', () => {
expect(incremental?.totalTokens).toBe(420)
})
it('parses an oversized record without quadratic carry copying', async () => {
const root = await makeTempDir()
const path = join(root, 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee.jsonl')
// One tool result far larger than a stream chunk. Re-joining the held-over
// partial line per chunk copies O(record^2); the piece list joins once.
const recordBytes = 4 * 1024 * 1024
await writeFile(
path,
`${[
userRecord(0, 'question'),
assistantRecord(1, 'x'.repeat(recordBytes)),
assistantRecord(2, 'tail answer')
].join('\n')}\n`
)
const originalConcat = Buffer.concat
let concatenatedBytes = 0
Buffer.concat = ((list: readonly Uint8Array[], totalLength?: number) => {
const joined = originalConcat(list as Uint8Array[], totalLength)
concatenatedBytes += joined.length
return joined
}) as typeof Buffer.concat
try {
const stats = createSessionParseStats()
const parsed = await parseAgentSessionFileCached(
await claudeCandidate(path),
process.platform,
stats
)
expect(parsed).not.toBeNull()
} finally {
Buffer.concat = originalConcat
}
// Linear joins the record about once; the quadratic form copied many times
// that, growing with the square of the record size.
expect(concatenatedBytes).toBeLessThan(recordBytes * 4)
})
it('shows a trailing unterminated line without double-counting it later', async () => {
const root = await makeTempDir()
const path = join(root, 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee.jsonl')

View File

@ -311,12 +311,28 @@ async function consumeCompleteJsonlLines(args: {
}): Promise<JsonlReadResult> {
let consumedThrough = args.start
let bytesRead = 0
let remainder: Buffer | null = null
// Why a piece list: re-joining the partial line with every chunk made one
// oversized record (a big tool result) cost O(record^2). Joining once, when a
// newline finally arrives, keeps it linear.
let remainderParts: Buffer[] = []
let remainderLength = 0
const stream = createReadStream(args.path, { start: args.start })
for await (const chunk of stream as AsyncIterable<Buffer>) {
bytesRead += chunk.length
const data = remainder ? Buffer.concat([remainder, chunk]) : chunk
// Why check the chunk alone: the pieces held over are all mid-line, so none
// of them contains a newline.
if (!chunk.includes(NEWLINE_BYTE)) {
remainderParts.push(chunk)
remainderLength += chunk.length
continue
}
const data =
remainderLength > 0
? Buffer.concat([...remainderParts, chunk], remainderLength + chunk.length)
: chunk
remainderParts = []
remainderLength = 0
let lineStart = 0
let newlineIndex = data.indexOf(NEWLINE_BYTE, lineStart)
while (newlineIndex !== -1) {
@ -329,13 +345,19 @@ async function consumeCompleteJsonlLines(args: {
newlineIndex = data.indexOf(NEWLINE_BYTE, lineStart)
}
consumedThrough += lineStart
// Copy the tail so retaining it doesn't pin the whole chunk buffer.
remainder = lineStart < data.length ? Buffer.from(data.subarray(lineStart)) : null
if (lineStart < data.length) {
// Copy the tail so retaining it doesn't pin the whole chunk buffer.
remainderParts = [Buffer.from(data.subarray(lineStart))]
remainderLength = data.length - lineStart
}
}
const trailingPartialLine =
remainderLength > 0 ? Buffer.concat(remainderParts, remainderLength).toString('utf-8') : null
return {
consumedThrough,
trailingPartialLine: remainder && remainder.length > 0 ? remainder.toString('utf-8') : null,
trailingPartialLine,
bytesRead
}
}