authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-10-14 05:38:21-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-14 05:38:21-07:00
log737192594f47d87f28b5f5a3fda3ef792a0d7972
tree3cc11d4ea8f384a2861c7f71216d249316722a7b
parentf37f99ca9e04dd0c4d0ee814402ae224175adc8d
signature Signed by PGP key B5690EEEBB952194

perf(server): unchanged node updates no longer write to SQLite (#19429)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

4 files changed, 88 insertions(+), 0 deletions(-)

apps/server/src/orchestration-v2/ProjectionStore.ts+24
...@@ -73,6 +73,7 @@ import { derivePendingBackgroundWork } from "@t3tools/shared/orchestrationV2Pend...@@ -73,6 +73,7 @@ import { derivePendingBackgroundWork } from "@t3tools/shared/orchestrationV2Pend
73import * as Context from "effect/Context";73import * as Context from "effect/Context";
74import * as DateTime from "effect/DateTime";74import * as DateTime from "effect/DateTime";
75import * as Effect from "effect/Effect";75import * as Effect from "effect/Effect";
76import * as Equal from "effect/Equal";
76import * as Layer from "effect/Layer";77import * as Layer from "effect/Layer";
77import * as Ref from "effect/Ref";78import * as Ref from "effect/Ref";
78import * as Schema from "effect/Schema";79import * as Schema from "effect/Schema";
...@@ -460,6 +461,10 @@ export interface ProjectionStoreV2Shape {...@@ -460,6 +461,10 @@ export interface ProjectionStoreV2Shape {
460 requestId: TurnItemId,461 requestId: TurnItemId,
461 resultId: TurnItemId,462 resultId: TurnItemId,
462 ) => Effect.Effect<boolean, ProjectionStoreV2Error>;463 ) => Effect.Effect<boolean, ProjectionStoreV2Error>;
464 /** True when the projection already stores this exact node, so a write would change nothing. */
465 readonly isNodeStored: (
466 node: OrchestrationV2ExecutionNode,
467 ) => Effect.Effect<boolean, ProjectionStoreV2Error>;
463 readonly getRuntimeRequest: (468 readonly getRuntimeRequest: (
464 threadId: ThreadId,469 threadId: ThreadId,
465 requestId: RuntimeRequestId,470 requestId: RuntimeRequestId,
...@@ -4404,6 +4409,15 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =...@@ -4404,6 +4409,15 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
4404 return rows[0]?.pending === 1;4409 return rows[0]?.pending === 1;
4405 }).pipe(Effect.mapError(controlReadError(threadId)));4410 }).pipe(Effect.mapError(controlReadError(threadId)));
44064411
4412 const isNodeStored: ProjectionStoreV2Shape["isNodeStored"] = (node) =>
4413 Effect.gen(function* () {
4414 const payloadJson = yield* encodeNodePayload(node);
4415 const rows = yield* sql<PayloadRow>`
4416 SELECT payload_json FROM orchestration_v2_projection_nodes WHERE node_id = ${node.id}
4417 `;
4418 return rows[0]?.payload_json === payloadJson;
4419 }).pipe(Effect.mapError(controlReadError(node.threadId)));
4420
4407 const getRuntimeRequest: ProjectionStoreV2Shape["getRuntimeRequest"] = (threadId, requestId) =>4421 const getRuntimeRequest: ProjectionStoreV2Shape["getRuntimeRequest"] = (threadId, requestId) =>
4408 sql4422 sql
4409 .withTransaction(4423 .withTransaction(
...@@ -5992,6 +6006,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =...@@ -5992,6 +6006,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
5992 canStartQueuedRun,6006 canStartQueuedRun,
5993 getPendingNativeUserInputs,6007 getPendingNativeUserInputs,
5994 hasUnpairedRunInterruptRequest,6008 hasUnpairedRunInterruptRequest,
6009 isNodeStored,
5995 getMessageCount,6010 getMessageCount,
5996 getNextTurnItemOrdinal,6011 getNextTurnItemOrdinal,
5997 getTurnItem,6012 getTurnItem,
...@@ -6255,6 +6270,15 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect(...@@ -6255,6 +6270,15 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect(
6255 !projection.turnItems.some((item) => item.id === resultId)6270 !projection.turnItems.some((item) => item.id === resultId)
6256 );6271 );
6257 }),6272 }),
6273 isNodeStored: (node) =>
6274 Ref.get(replayState).pipe(
6275 Effect.map((state) => {
6276 const stored = state.projections
6277 .get(node.threadId)
6278 ?.nodes.find((candidate) => candidate.id === node.id);
6279 return stored !== undefined && Equal.equals(stored, node);
6280 }),
6281 ),
6258 getMessageCount: (threadId) =>6282 getMessageCount: (threadId) =>
6259 Ref.get(replayState).pipe(6283 Ref.get(replayState).pipe(
6260 Effect.map((state) => state.projections.get(threadId)?.messages.length ?? 0),6284 Effect.map((state) => state.projections.get(threadId)?.messages.length ?? 0),
apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts+60
...@@ -325,6 +325,66 @@ layer("ProviderEventIngestorV2", (it) => {...@@ -325,6 +325,66 @@ layer("ProviderEventIngestorV2", (it) => {
325 }),325 }),
326 );326 );
327327
328 it.effect("skips a node update only when it matches the stored node", () =>
329 Effect.gen(function* () {
330 const now = yield* DateTime.now;
331 const eventSink = yield* EventSink.EventSinkV2;
332 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
333 const ingestor = yield* ProviderEventIngestor.ProviderEventIngestorV2;
334 const idAllocator = yield* IdAllocator.IdAllocatorV2;
335 const threadEvent = yield* threadCreatedEvent(now);
336 const threadId = threadEvent.threadId;
337 yield* eventSink.write({ events: [threadEvent] });
338 const providerSessionId = yield* idAllocator.allocate.providerSession({
339 providerInstanceId: modelSelection.instanceId,
340 threadId,
341 });
342 const nodeId = NodeId.make(`${threadId}:approval`);
343 const node: OrchestrationV2ExecutionNode = {
344 id: nodeId,
345 threadId,
346 runId: null,
347 parentNodeId: null,
348 rootNodeId: nodeId,
349 kind: "approval_request",
350 status: "waiting",
351 countsForRun: false,
352 providerThreadId: null,
353 providerTurnId: null,
354 nativeItemRef: null,
355 runtimeRequestId: null,
356 checkpointScopeId: null,
357 startedAt: now,
358 completedAt: null,
359 };
360 const ingestNode = (sent: OrchestrationV2ExecutionNode) =>
361 ingestor.ingestNormalized({
362 providerSessionId,
363 providerInstanceId: modelSelection.instanceId,
364 threadId,
365 event: { type: "node.updated", driver: CODEX_DRIVER, node: sent },
366 });
367
368 assert.lengthOf(yield* ingestNode(node), 1);
369 assert.isEmpty(yield* ingestNode({ ...node }));
370 // The server resolves the request, then the provider asks again with the same node.
371 yield* eventSink.write({
372 events: [
373 {
374 id: yield* idAllocator.allocate.event({ threadId }),
375 type: "node.updated",
376 threadId,
377 occurredAt: now,
378 payload: { ...node, status: "completed", completedAt: now },
379 },
380 ],
381 });
382 assert.lengthOf(yield* ingestNode(node), 1);
383 const projection = yield* projectionStore.getThreadProjection(threadId);
384 assert.equal(projection.nodes.find((entry) => entry.id === nodeId)?.status, "waiting");
385 }),
386 );
387
328 it.effect("rolls back runs a provider rewound off its active branch", () =>388 it.effect("rolls back runs a provider rewound off its active branch", () =>
329 Effect.gen(function* () {389 Effect.gen(function* () {
330 const now = yield* DateTime.now;390 const now = yield* DateTime.now;
apps/server/src/orchestration-v2/ProviderEventIngestor.ts+3
...@@ -611,6 +611,9 @@ export const layer: Layer.Layer<...@@ -611,6 +611,9 @@ export const layer: Layer.Layer<
611 }),611 }),
612 ];612 ];
613 case "node.updated":613 case "node.updated":
614 // Adapters send an unchanged node again with each text delta. Compare with the
615 // stored node, not the last one sent, because the server also writes nodes.
616 if (yield* projections.isNodeStored(input.event.node)) return [];
614 return [617 return [
615 yield* makeDomainEvent(input, {618 yield* makeDomainEvent(input, {
616 type: "node.updated",619 type: "node.updated",
apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts+1
...@@ -255,6 +255,7 @@ it.effect(...@@ -255,6 +255,7 @@ it.effect(
255 getCheckpointContext: () => Effect.die("not used"),255 getCheckpointContext: () => Effect.die("not used"),
256 getCheckpointCaptureContext: () => Effect.die("not used"),256 getCheckpointCaptureContext: () => Effect.die("not used"),
257 getRunMessage: () => Effect.die("not used"),257 getRunMessage: () => Effect.die("not used"),
258 isNodeStored: () => Effect.die("not used"),
258 canStartQueuedRun: () => Effect.die("not used"),259 canStartQueuedRun: () => Effect.die("not used"),
259 getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"),260 getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"),
260 getUnreadableThreadIds: () => Effect.die("unused getUnreadableThreadIds"),261 getUnreadableThreadIds: () => Effect.die("unused getUnreadableThreadIds"),