Skip to content

Commit cdfa980

Browse files
committed
fix(server): the V1 transcript import no longer stalls on each thread's V1 events
The importer's lookup of already imported messages filters to V2 thread events, but SQLite planned it on the stream index, which also walks the thread's V1 events and reads past each payload. On a 14.1 GiB V1 database the 353 lookups took 148 s and stalled the event loop for up to 11 s. Pinning the V2 partial index, as latestAgentSequence already does, brought them to 0.31 s. Fixes #20966
1 parent 16918b5 commit cdfa980

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

‎apps/server/src/orchestration-v2/legacy/LegacyV1ThreadImporter.test.ts‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,72 @@ it.layer(layerTest)("LegacyV1ThreadImporter", (it) => {
534534
}),
535535
);
536536
});
537+
538+
it.layer(layerTest)("LegacyV1ThreadImporter transcript lookup", (it) => {
539+
it.effect("finds imported messages on the V2 thread index, past the thread's V1 events", () =>
540+
Effect.gen(function* () {
541+
const sql = yield* SqlClient.SqlClient;
542+
const importer = yield* LegacyV1ThreadImporter.LegacyV1ThreadImporter;
543+
const threadId = ThreadId.make("thread:legacy-lookup");
544+
yield* sql`
545+
INSERT INTO projection_projects (
546+
project_id, title, workspace_root, default_model_selection_json, scripts_json,
547+
created_at, updated_at, deleted_at
548+
) VALUES (
549+
'project:legacy-lookup', 'Legacy project', '/tmp/legacy-lookup',
550+
'{"instanceId":"codex","model":"gpt-5.4"}', '[]',
551+
'2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z', NULL
552+
)
553+
`;
554+
yield* sql`
555+
INSERT INTO projection_threads (
556+
thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode,
557+
created_at, updated_at
558+
) VALUES (
559+
${threadId}, 'project:legacy-lookup', 'Migrated conversation',
560+
'{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default',
561+
'2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z'
562+
)
563+
`;
564+
yield* sql`
565+
INSERT INTO projection_thread_messages (
566+
message_id, thread_id, role, text, is_streaming, created_at, updated_at
567+
) VALUES (
568+
'message:legacy-lookup:1', ${threadId}, 'user', 'Question', 0,
569+
'2026-01-01T01:00:00.000Z', '2026-01-01T01:00:00.000Z'
570+
)
571+
`;
572+
yield* importer.reconcileShells;
573+
574+
const statements: string[] = [];
575+
const tracer = Tracer.make({
576+
span(options) {
577+
const span = new Tracer.NativeSpan(options);
578+
const end = span.end.bind(span);
579+
span.end = (endTime, exit) => {
580+
end(endTime, exit);
581+
const query = span.attributes.get("db.query.text");
582+
if (
583+
typeof query === "string" &&
584+
/^\s*SELECT event_id\s+FROM orchestration_events/.test(query)
585+
) {
586+
statements.push(query);
587+
}
588+
};
589+
return span;
590+
},
591+
});
592+
yield* importer.ensureTranscript(threadId).pipe(Effect.withTracer(tracer));
593+
594+
assert.lengthOf(statements, 1);
595+
const plan = yield* sql.unsafe<{ readonly detail: string }>(
596+
`EXPLAIN QUERY PLAN ${statements[0]}`,
597+
[threadId, "migration:v1:message:%"],
598+
);
599+
assert.match(
600+
plan.map((row) => row.detail).join("\n"),
601+
/USING INDEX idx_orchestration_events_agent_stream_sequence \(stream_id=\?\)/,
602+
);
603+
}),
604+
);
605+
});

‎apps/server/src/orchestration-v2/legacy/LegacyV1ThreadImporter.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,11 @@ const make = Effect.gen(function* () {
708708
return { importedThreadCount: 0, importedMessageCount: 0 };
709709
}
710710
const messages = yield* listMessages(threadId);
711+
// The thread's V1 events are still in the table here. Without the hint
712+
// SQLite walks them through the stream index and reads past each payload.
711713
const existingRows = yield* sql<{ readonly event_id: string }>`
712714
SELECT event_id
713-
FROM orchestration_events
715+
FROM orchestration_events INDEXED BY idx_orchestration_events_agent_stream_sequence
714716
WHERE application_event_version = 2
715717
AND aggregate_kind = 'thread'
716718
AND stream_id = ${threadId}

0 commit comments

Comments
 (0)