Skip to content

feat: add periodic WAL checkpoint to idle maintenance#181

Merged
coji merged 4 commits intomainfrom
feat/wal-checkpoint-idle-maintenance
Mar 29, 2026
Merged

feat: add periodic WAL checkpoint to idle maintenance#181
coji merged 4 commits intomainfrom
feat/wal-checkpoint-idle-maintenance

Conversation

@coji
Copy link
Copy Markdown
Owner

@coji coji commented Mar 29, 2026

Summary

Closes #180

  • Add PRAGMA wal_checkpoint(TRUNCATE) to runIdleMaintenance, throttled to at most once per 60 seconds
  • Only runs for local SQLite backends (skips PostgreSQL and browser/OPFS via isBrowserLikeEnvironment())
  • Checks the busy flag from checkpoint result — retries on next cycle if SQLite reports busy
  • Runs outside the write mutex since checkpoint doesn't block writers (SQLite backs off with busy=1 instead)

Test plan

  • pnpm validate passes (244 tests, 28 tasks)
  • Reviewed by Codex — addressed all findings (target filtering, busy handling)
  • /simplify review — removed Store interface leak, cached environment check, shared timestamp

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • Chores

    • データベースのアイドル時保守を改良し、WALチェックポイントの自動定期実行と頻度制御(スロットリング)を追加して安定性とディスク管理を改善しました。
    • マイグレーション完了後にWALチェックポイント対応を非同期で検出するフローを導入し、保守開始タイミングを調整しました。
  • Bug Fixes

    • チェックポイント失敗時のエラー報告を強化し、バックグラウンド保守の堅牢性を向上させました。

…ends

Closes #180

SQLite WAL files grow unbounded in long-running deployments because
durably never explicitly checkpoints. Add PRAGMA wal_checkpoint(TRUNCATE)
to the idle maintenance cycle, throttled to at most once per 60 seconds.

- Only runs for local SQLite backends (skips PostgreSQL and browser/OPFS)
- Checks the busy flag so failed checkpoints retry on the next cycle
- Runs outside the write mutex since SQLite handles checkpoint concurrency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
durably-demo Ready Ready Preview Mar 29, 2026 1:29pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
durably-demo-vercel-turso Ignored Ignored Preview Mar 29, 2026 1:29pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@coji has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 53 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 53 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 345997bf-2989-4535-ba1e-7a078ead4191

📥 Commits

Reviewing files that changed from the base of the PR and between 80971fa and 9a72f3e.

📒 Files selected for processing (1)
  • packages/durably/src/durably.ts
📝 Walkthrough

Walkthrough

runIdleMaintenance に SQLite WAL チェックポイントの周期実行とプローブを追加しました。マイグレーション完了後に非同期で WAL サポートを検出し、汎用バックエンドで定期的に PRAGMA wal_checkpoint(TRUNCATE) を実行、エラーは worker:error イベントとして報告します。

Changes

Cohort / File(s) Summary
SQLite WAL state & probe
packages/durably/src/durably.ts
DurablyStatewalCheckpointSupported/probeWalCheckpoint/lastCheckpointAt を追加。sql をインポートし、マイグレーション完了後に probeWalCheckpoint を非同期で呼び出してプローブ結果をクリアする処理を追加。
Idle maintenance & checkpointing
packages/durably/src/durably.ts
runIdleMaintenance を共通の nowMs タイムスタンプで制御するよう修正。CHECKPOINT_INTERVAL_MS に基づき PRAGMA wal_checkpoint(TRUNCATE) を周期実行(lastCheckpointAt でスロットル)。チェックポイント実行時のエラーは worker:error(context: wal-checkpoint)で報告。busy !== 0 の結果は再試行タイミングを調整するため lastCheckpointAt を進める挙動を追加。
WAL probe behavior
packages/durably/src/durably.ts
汎用バックエンド(backend === 'generic')かつブラウザ類でない環境で PRAGMA wal_checkpoint(PASSIVE) を試行し、成功で walCheckpointSupported = true、失敗は握りつぶして無視するプローブを実装。

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler as Scheduler
  participant Durably as Durably
  participant Kysely as Kysely (sql)
  participant SQLite as SQLite

  Scheduler->>Durably: runIdleMaintenance(now)
  Durably->>Durably: calculate nowMs, leases/purge checks
  alt probe not yet run
    Durably->>Kysely: sql`PRAGMA wal_checkpoint(PASSIVE)` (probe)
    Kysely->>SQLite: execute PRAGMA
    SQLite-->>Kysely: success / error
    Kysely-->>Durably: probe result -> set walCheckpointSupported (or ignore error)
  end
  alt walCheckpointSupported && interval passed
    Durably->>Kysely: sql`PRAGMA wal_checkpoint(TRUNCATE)`
    Kysely->>SQLite: execute PRAGMA
    SQLite-->>Kysely: {busy: 0/1}
    Kysely-->>Durably: checkpoint result
    Durably->>Durably: update lastCheckpointAt (throttle / retry timing)
    Durably->>Durably: emit worker:error if SQL error (context: "wal-checkpoint")
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 わたしは跳ねて、WALをのぞき見し、

静かな夜に PRAGMA を打ち、
ログを小さく、土を軽くし、
ふわりと最後のチェックを刻む。 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding periodic WAL checkpoint functionality to the idle maintenance process.
Linked Issues check ✅ Passed All coding requirements from issue #180 are met: WAL checkpoint PRAGMA added to runIdleMaintenance, throttled to ~60s intervals, restricted to generic backends, handles busy flag retries, and uses Kysely sql templates.
Out of Scope Changes check ✅ Passed All changes directly support the WAL checkpoint feature: probe capability, maintenance flow enhancement, shared timestamp optimization, and error handling are all on-scope improvements related to the core objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wal-checkpoint-idle-maintenance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/durably/src/durably.ts`:
- Around line 993-1000: The checkpoint logic currently only updates
lastCheckpointAt when row?.busy === 0, causing repeated immediate retries during
long read transactions; change the behavior in the block that runs PRAGMA
wal_checkpoint(TRUNCATE) so that lastCheckpointAt is advanced to nowMs
regardless of row.busy (and still perform the checkpoint when busy === 0),
thereby enforcing CHECKPOINT_INTERVAL_MS throttling; locate the code around
PRAGMA wal_checkpoint(TRUNCATE), lastCheckpointAt, CHECKPOINT_INTERVAL_MS and
row?.busy to make this change.
- Around line 966-967: The current check uses backend === 'generic' to set
shouldWalCheckpoint which enables SQLite-specific PRAGMA
wal_checkpoint(TRUNCATE) for non-Postgres backends and incorrectly runs on
non-SQLite dialects; change the condition in the shouldWalCheckpoint logic to
narrowly detect SQLite (e.g., inspect the exported dialect value or a SQLite
capability) instead of relying on DatabaseBackend/generic — use the Dialect
parameter or an explicit isSqliteDialect/isSqliteCapability check and keep the
isBrowserLikeEnvironment() guard so PRAGMA wal_checkpoint only runs when the
dialect is actually SQLite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e764f7a7-72d4-4f53-bdfa-6e0188919c69

📥 Commits

Reviewing files that changed from the base of the PR and between 7ade0c8 and 30f179b.

📒 Files selected for processing (1)
  • packages/durably/src/durably.ts

Remote backends like Turso reject PRAGMA wal_checkpoint with
SQL_PARSE_ERROR. Instead of guessing from dialect type, probe
with PRAGMA wal_checkpoint(PASSIVE) during migrate() — if it
succeeds, enable periodic TRUNCATE checkpoints; if it throws,
silently disable. This correctly handles all backends:

- Local better-sqlite3: probe succeeds, checkpoints enabled
- Local libSQL: probe succeeds (safe no-op), checkpoints enabled
- Remote Turso: probe throws SQL_PARSE_ERROR, checkpoints disabled
- Browser OPFS: skipped entirely (isBrowserLikeEnvironment check)
- PostgreSQL: skipped entirely (backend !== 'generic')

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Checkpoint errors now emit worker:error with context 'wal-checkpoint'
  instead of being silently swallowed, preserving operational visibility
- Null out probeWalCheckpoint after migrate() to prevent stale references
- Busy checkpoints retry after 30s (half interval) instead of every tick

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/durably/src/durably.ts`:
- Around line 983-1006: The checkpoint timing uses the earlier nowMs (used for
releaseExpiredLeases/purge) which may be stale by the time the checkpoint runs;
before checking/performing the WAL checkpoint (the block using
CHECKPOINT_INTERVAL_MS, lastCheckpointAt and the sql`PRAGMA
wal_checkpoint(TRUNCATE)` call), recompute the current time into a new variable
(e.g., checkpointNow = Date.now()), use checkpointNow for the throttle check
(checkpointNow - lastCheckpointAt), assign lastCheckpointAt = checkpointNow when
starting the checkpoint attempt, and when applying the busy backoff set
lastCheckpointAt = checkpointNow - CHECKPOINT_INTERVAL_MS/2 so the backoff
reflects the actual attempt time; leave releaseExpiredLeases and purgeRuns using
the original nowMs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d21510c-23e7-44fc-8bdf-90665b00476d

📥 Commits

Reviewing files that changed from the base of the PR and between 5879067 and 80971fa.

📒 Files selected for processing (1)
  • packages/durably/src/durably.ts

Check probe result: log === -1 indicates WAL is not active (e.g. libSQL
local uses DELETE journal mode). Only enable periodic checkpoints when
the probe confirms WAL is in use, avoiding no-op PRAGMA queries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coji coji merged commit 811f2b1 into main Mar 29, 2026
5 checks passed
@coji coji deleted the feat/wal-checkpoint-idle-maintenance branch March 29, 2026 13:36
@coji coji mentioned this pull request Mar 29, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add periodic WAL checkpoint to idle maintenance for SQLite backends

1 participant