Skip to content

Commit a4ced70

Browse files
ralyodioclaude
andcommitted
fix(openontology): migrate in one transaction, and stop timing out on slow disks
CI failed on an unrelated PR when "seeds a package and hydrates it back identically" passed vitest's default 5s timeout. The assertions were fine; the suite is I/O bound and the runner was slow. Two changes. migrate() ran every DDL statement through its own client.execute(), so migration 1's ~30 statements each became a separate durable commit and opening a store paid ~30 fsyncs. Batch each migration into one write transaction instead: locally a fresh migration drops from ~8.2ms to ~5.0ms, and the gap widens as fsync gets more expensive. It also closes a real hole -- a crash part-way could previously leave the schema half-applied while schema_migrations recorded the migration as done, because the statements and the bookkeeping insert were not atomic. Then give the package a 30s testTimeout. These suites drive a real file-backed SQLite database, so their wall time is set by the host filesystem, not by our code. The 5s default is tuned for CPU-bound unit tests and leaves no headroom on a contended runner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 1e4e9de commit a4ced70

2 files changed

Lines changed: 30 additions & 7 deletions

File tree

packages/openontology/src/libsql.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,21 @@ export async function migrate(client: Client): Promise<number> {
225225
let count = 0;
226226
for (const migration of MIGRATIONS) {
227227
if (have.has(migration.version)) continue;
228-
for (const statement of migration.statements) {
229-
await client.execute(statement);
230-
}
231-
await client.execute({
232-
sql: "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)",
233-
args: [migration.version, migration.name, new Date().toISOString()]
234-
});
228+
// One transaction per migration, rather than one implicit transaction per
229+
// statement. Migration 1 alone is ~30 DDL statements, so opening a store
230+
// used to cost ~30 durable commits; on a filesystem with slow fsync that
231+
// dominated the open. It is also safer: a crash part-way can no longer
232+
// leave the schema half-applied while schema_migrations claims it is done.
233+
await client.batch(
234+
[
235+
...migration.statements,
236+
{
237+
sql: "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)",
238+
args: [migration.version, migration.name, new Date().toISOString()] as InArgs
239+
}
240+
],
241+
"write"
242+
);
235243
count += 1;
236244
}
237245
return count;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
// These suites drive a real file-backed SQLite database through
6+
// @libsql/client, so their wall time is set by the host filesystem's
7+
// durability cost, not by our code. Locally the whole libsql suite runs in
8+
// ~250ms; on a contended CI runner a single seed-and-reopen case has been
9+
// seen to pass 5s, which is vitest's default and is tuned for CPU-bound
10+
// unit tests. Give the I/O-bound cases enough headroom that a slow disk
11+
// reports as slow rather than as a spurious failure.
12+
testTimeout: 30_000,
13+
hookTimeout: 30_000
14+
}
15+
});

0 commit comments

Comments
 (0)