| author | |
| committer | |
| log | 614b432a79b0acae38c27c00b1e4eece223ecfe4 |
| tree | ec5bd1cac63cd222d49384ddadf3179b45cd6275 |
| parent | acb90279e2adad19a63586944543fe7346499467 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Soroush Baraouf <soroushraouf44@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>4 files changed, 36 insertions(+), 0 deletions(-)
apps/server/scripts/t3-sqlite-state.test.ts+19| ... | @@ -75,6 +75,25 @@ it.layer(NodeServices.layer)("t3-sqlite-state", (it) => { | ... | @@ -75,6 +75,25 @@ it.layer(NodeServices.layer)("t3-sqlite-state", (it) => { |
| 75 | }), | 75 | }), |
| 76 | ); | 76 | ); |
| 77 | 77 | ||
| 78 | it.effect("enables checkpoint_fullfsync on its direct connection", () => | ||
| 79 | Effect.gen(function* () { | ||
| 80 | const fs = yield* FileSystem.FileSystem; | ||
| 81 | const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-sqlite-state-fsync-" }); | ||
| 82 | yield* createFixtureDatabase(baseDir); | ||
| 83 | |||
| 84 | const result = yield* runSqliteState({ | ||
| 85 | operation: "query", | ||
| 86 | baseDir, | ||
| 87 | sql: "PRAGMA checkpoint_fullfsync", | ||
| 88 | }); | ||
| 89 | |||
| 90 | assert.equal(result.operation, "query"); | ||
| 91 | if (result.operation === "query") { | ||
| 92 | assert.deepStrictEqual(result.rows, [{ checkpoint_fullfsync: 1 }]); | ||
| 93 | } | ||
| 94 | }), | ||
| 95 | ); | ||
| 96 | |||
| 78 | it.effect.skipIf(!symlinksSupported)( | 97 | it.effect.skipIf(!symlinksSupported)( |
| 79 | "backs up isolated state before writes and refuses the shared home", | 98 | "backs up isolated state before writes and refuses the shared home", |
| 80 | () => | 99 | () => |
apps/server/scripts/t3-sqlite-state.ts+3| ... | @@ -201,6 +201,9 @@ export const runSqliteState = Effect.fn("runSqliteState")(function* ( | ... | @@ -201,6 +201,9 @@ export const runSqliteState = Effect.fn("runSqliteState")(function* ( |
| 201 | const program = Effect.gen(function* () { | 201 | const program = Effect.gen(function* () { |
| 202 | const sql = yield* SqlClient.SqlClient; | 202 | const sql = yield* SqlClient.SqlClient; |
| 203 | yield* sql.unsafe("PRAGMA busy_timeout = 5000").unprepared; | 203 | yield* sql.unsafe("PRAGMA busy_timeout = 5000").unprepared; |
| 204 | // This connection bypasses persistence/Sqlite.ts and can checkpoint | ||
| 205 | // statev2.sqlite, so it needs the same F_FULLFSYNC checkpoint barrier. | ||
| 206 | yield* sql.unsafe("PRAGMA checkpoint_fullfsync = ON").unprepared; | ||
| 204 | 207 | ||
| 205 | if (input.operation === "query") { | 208 | if (input.operation === "query") { |
| 206 | const rows = yield* sql.unsafe<RawSqliteRow>(source).unprepared.pipe( | 209 | const rows = yield* sql.unsafe<RawSqliteRow>(source).unprepared.pipe( |
apps/server/src/persistence/Sqlite.test.ts+10| ... | @@ -136,3 +136,13 @@ it.effect("applies busy_timeout in the shared persistence setup", () => | ... | @@ -136,3 +136,13 @@ it.effect("applies busy_timeout in the shared persistence setup", () => |
| 136 | assert.equal(rows[0]?.timeout, 5000); | 136 | assert.equal(rows[0]?.timeout, 5000); |
| 137 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | 137 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), |
| 138 | ); | 138 | ); |
| 139 | |||
| 140 | it.effect("enables checkpoint_fullfsync in the shared persistence setup", () => | ||
| 141 | Effect.gen(function* () { | ||
| 142 | const sql = yield* SqlClient.SqlClient; | ||
| 143 | const rows = yield* sql<{ | ||
| 144 | readonly checkpoint_fullfsync: number; | ||
| 145 | }>`PRAGMA checkpoint_fullfsync`; | ||
| 146 | assert.equal(rows[0]?.checkpoint_fullfsync, 1); | ||
| 147 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | ||
| 148 | ); |
apps/server/src/persistence/Sqlite.ts+4| ... | @@ -25,6 +25,10 @@ const layerSetup = Layer.effectDiscard( | ... | @@ -25,6 +25,10 @@ const layerSetup = Layer.effectDiscard( |
| 25 | // cannot corrupt the database and an app crash loses nothing; an OS crash | 25 | // cannot corrupt the database and an app crash loses nothing; an OS crash |
| 26 | // or power loss can roll back commits since the last checkpoint. | 26 | // or power loss can roll back commits since the last checkpoint. |
| 27 | yield* sql`PRAGMA synchronous = NORMAL;`; | 27 | yield* sql`PRAGMA synchronous = NORMAL;`; |
| 28 | // Checkpoints are now the only sync, and macOS fsync() does not flush the | ||
| 29 | // drive cache. F_FULLFSYNC makes them reach disk; other platforms ignore it. | ||
| 30 | // Per-commit fullfsync stays off. | ||
| 31 | yield* sql`PRAGMA checkpoint_fullfsync = ON;`; | ||
| 28 | // PASSIVE checkpoints never shrink the -wal file, so it otherwise keeps its | 32 | // PASSIVE checkpoints never shrink the -wal file, so it otherwise keeps its |
| 29 | // largest size until the last connection closes. | 33 | // largest size until the last connection closes. |
| 30 | yield* sql.unsafe(`PRAGMA journal_size_limit = ${WAL_SIZE_LIMIT_BYTES};`); | 34 | yield* sql.unsafe(`PRAGMA journal_size_limit = ${WAL_SIZE_LIMIT_BYTES};`); |