Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/server/scripts/t3-sqlite-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ it.layer(NodeServices.layer)("t3-sqlite-state", (it) => {
}),
);

it.effect("enables checkpoint_fullfsync on its direct connection", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-sqlite-state-fsync-" });
yield* createFixtureDatabase(baseDir);

const result = yield* runSqliteState({
operation: "query",
baseDir,
sql: "PRAGMA checkpoint_fullfsync",
});

assert.equal(result.operation, "query");
if (result.operation === "query") {
assert.deepStrictEqual(result.rows, [{ checkpoint_fullfsync: 1 }]);
}
}),
);

it.effect.skipIf(!symlinksSupported)(
"backs up isolated state before writes and refuses the shared home",
() =>
Expand Down
3 changes: 3 additions & 0 deletions apps/server/scripts/t3-sqlite-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ export const runSqliteState = Effect.fn("runSqliteState")(function* (
const program = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
yield* sql.unsafe("PRAGMA busy_timeout = 5000").unprepared;
// This connection bypasses persistence/Layers/Sqlite.ts and can checkpoint
// state.sqlite, so it needs the same F_FULLFSYNC checkpoint barrier.
yield* sql.unsafe("PRAGMA checkpoint_fullfsync = ON").unprepared;

if (input.operation === "query") {
const rows = yield* sql.unsafe<RawSqliteRow>(source).unprepared.pipe(
Expand Down
10 changes: 10 additions & 0 deletions apps/server/src/persistence/Layers/Sqlite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,13 @@ it.effect("applies busy_timeout in the shared persistence setup", () =>
assert.equal(rows[0]?.timeout, 5000);
}).pipe(Effect.provide(SqlitePersistenceMemory)),
);

it.effect("enables checkpoint_fullfsync in the shared persistence setup", () =>
Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
const rows = yield* sql<{
readonly checkpoint_fullfsync: number;
}>`PRAGMA checkpoint_fullfsync`;
assert.equal(rows[0]?.checkpoint_fullfsync, 1);
}).pipe(Effect.provide(SqlitePersistenceMemory)),
);
3 changes: 3 additions & 0 deletions apps/server/src/persistence/Layers/Sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const setup = Layer.effectDiscard(
yield* sql`PRAGMA busy_timeout = 5000;`;
yield* sql`PRAGMA foreign_keys = ON;`;
yield* sql`PRAGMA journal_mode = WAL;`;
// macOS fsync() does not flush the drive cache. Checkpoint syncs must use
// F_FULLFSYNC so a power loss cannot publish page references without the pages.
yield* sql`PRAGMA checkpoint_fullfsync = ON;`;
// PASSIVE checkpoints never shrink the -wal file, so it otherwise keeps its
// largest size until the last connection closes.
yield* sql.unsafe(`PRAGMA journal_size_limit = ${WAL_SIZE_LIMIT_BYTES};`);
Expand Down
Loading