Repository navigation
perf(server): phone reconnects no longer stall on counting every turn item #26816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+152
−14
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
apps/server/src/persistence/Migrations/074_TurnItemCounts.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { assert, it } from "@effect/vitest"; | ||
| import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as SqlClient from "effect/sql/SqlClient"; | ||
|
|
||
| import { runMigrations } from "../Migrations.ts"; | ||
|
|
||
| const layer = it.layer(NodeSqliteClient.layer({ filename: ":memory:" })); | ||
|
|
||
| layer("074_TurnItemCounts", (it) => { | ||
| it.effect("backfills item counts, then follows upserts, moves, and deletes", () => | ||
| Effect.gen(function* () { | ||
| const sql = yield* SqlClient.SqlClient; | ||
| yield* runMigrations({ toMigrationInclusive: 73 }); | ||
| const upsert = (id: string, threadId: string, runId: string | null) => sql` | ||
| INSERT INTO orchestration_v2_projection_turn_items ( | ||
| turn_item_id, thread_id, run_id, ordinal, type, status, updated_at, payload_json | ||
| ) | ||
| VALUES ( | ||
| ${id}, ${threadId}, ${runId}, 1, 'assistant_message', 'completed', | ||
| '2026-10-29T00:00:00.000Z', '{}' | ||
| ) | ||
| ON CONFLICT(turn_item_id) DO UPDATE SET | ||
| thread_id = excluded.thread_id, | ||
| run_id = excluded.run_id, | ||
| status = excluded.status | ||
| `; | ||
| yield* upsert("runless-1", "thread-1", null); | ||
| yield* upsert("runless-2", "thread-1", null); | ||
| yield* upsert("run-1", "thread-1", "run-1"); | ||
|
|
||
| yield* runMigrations(); | ||
|
|
||
| const counts = () => | ||
| sql<{ readonly thread_id: string; readonly run_key: string; readonly item_count: number }>` | ||
| SELECT thread_id, run_key, item_count | ||
| FROM orchestration_v2_projection_turn_item_counts | ||
| ORDER BY thread_id, run_key | ||
| `; | ||
| assert.deepEqual(yield* counts(), [ | ||
| { thread_id: "thread-1", run_key: "", item_count: 2 }, | ||
| { thread_id: "thread-1", run_key: "run-1", item_count: 1 }, | ||
| ]); | ||
|
|
||
| yield* upsert("run-1", "thread-1", "run-1"); | ||
| yield* upsert("run-2", "thread-2", "run-2"); | ||
| yield* upsert("runless-2", "thread-1", "run-1"); | ||
| assert.deepEqual(yield* counts(), [ | ||
| { thread_id: "thread-1", run_key: "", item_count: 1 }, | ||
| { thread_id: "thread-1", run_key: "run-1", item_count: 2 }, | ||
| { thread_id: "thread-2", run_key: "run-2", item_count: 1 }, | ||
| ]); | ||
|
|
||
| yield* sql`DELETE FROM orchestration_v2_projection_turn_items WHERE thread_id = 'thread-1'`; | ||
| assert.deepEqual(yield* counts(), [ | ||
| { thread_id: "thread-2", run_key: "run-2", item_count: 1 }, | ||
| ]); | ||
| }), | ||
| ); | ||
| }); |
61 changes: 61 additions & 0 deletions
61
apps/server/src/persistence/Migrations/074_TurnItemCounts.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import * as Effect from "effect/Effect"; | ||
| import * as SqlClient from "effect/sql/SqlClient"; | ||
|
|
||
| /** | ||
| * Shell snapshots read item counts from here instead of walking every turn item, which on a | ||
| * large history takes seconds once the index is out of the page cache. Triggers keep it exact | ||
| * for every writer, including projection rebuilds and dev-database pruning. | ||
| */ | ||
| export default Effect.gen(function* () { | ||
| const sql = yield* SqlClient.SqlClient; | ||
|
|
||
| // run_key is '' for items without a run; NULLs never conflict in a primary key. | ||
| yield* sql` | ||
| CREATE TABLE orchestration_v2_projection_turn_item_counts ( | ||
| thread_id TEXT NOT NULL, | ||
| run_key TEXT NOT NULL, | ||
| item_count INTEGER NOT NULL, | ||
| PRIMARY KEY (thread_id, run_key) | ||
| ) WITHOUT ROWID | ||
| `; | ||
| yield* sql` | ||
| INSERT INTO orchestration_v2_projection_turn_item_counts (thread_id, run_key, item_count) | ||
| SELECT thread_id, COALESCE(run_id, ''), COUNT(*) | ||
| FROM orchestration_v2_projection_turn_items | ||
| GROUP BY thread_id, run_id | ||
| `; | ||
| // An upsert that hits an existing item fires only the update trigger. | ||
| yield* sql` | ||
| CREATE TRIGGER orchestration_v2_turn_item_counts_insert | ||
| AFTER INSERT ON orchestration_v2_projection_turn_items | ||
| BEGIN | ||
| INSERT INTO orchestration_v2_projection_turn_item_counts (thread_id, run_key, item_count) | ||
| VALUES (new.thread_id, COALESCE(new.run_id, ''), 1) | ||
| ON CONFLICT (thread_id, run_key) DO UPDATE SET item_count = item_count + 1; | ||
| END | ||
| `; | ||
| yield* sql` | ||
| CREATE TRIGGER orchestration_v2_turn_item_counts_update | ||
| AFTER UPDATE OF thread_id, run_id ON orchestration_v2_projection_turn_items | ||
| WHEN old.thread_id IS NOT new.thread_id OR old.run_id IS NOT new.run_id | ||
| BEGIN | ||
| UPDATE orchestration_v2_projection_turn_item_counts SET item_count = item_count - 1 | ||
| WHERE thread_id = old.thread_id AND run_key = COALESCE(old.run_id, ''); | ||
| DELETE FROM orchestration_v2_projection_turn_item_counts | ||
| WHERE thread_id = old.thread_id AND run_key = COALESCE(old.run_id, '') AND item_count <= 0; | ||
| INSERT INTO orchestration_v2_projection_turn_item_counts (thread_id, run_key, item_count) | ||
| VALUES (new.thread_id, COALESCE(new.run_id, ''), 1) | ||
| ON CONFLICT (thread_id, run_key) DO UPDATE SET item_count = item_count + 1; | ||
| END | ||
| `; | ||
| yield* sql` | ||
| CREATE TRIGGER orchestration_v2_turn_item_counts_delete | ||
| AFTER DELETE ON orchestration_v2_projection_turn_items | ||
| BEGIN | ||
| UPDATE orchestration_v2_projection_turn_item_counts SET item_count = item_count - 1 | ||
| WHERE thread_id = old.thread_id AND run_key = COALESCE(old.run_id, ''); | ||
| DELETE FROM orchestration_v2_projection_turn_item_counts | ||
| WHERE thread_id = old.thread_id AND run_key = COALESCE(old.run_id, '') AND item_count <= 0; | ||
| END | ||
| `; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.