Skip to content

Make explicit non-passive Sqlite WAL checkpoints#85

Merged
OpsBotPrime merged 3 commits intomasterfrom
active-sqlite-wal-checkpointing
Mar 26, 2026
Merged

Make explicit non-passive Sqlite WAL checkpoints#85
OpsBotPrime merged 3 commits intomasterfrom
active-sqlite-wal-checkpointing

Conversation

@Qqwy
Copy link
Contributor

@Qqwy Qqwy commented Mar 26, 2026

The default way SQLite in WAL mode performs checkpoints, is by waiting for quiet time where there are no readers nor writers.

Any read-connection can block this passive WAL checkpointing from making progress. We have observed that in production when having large workloads with consumers working on hundreds of chunks concurrently.

Whereas the WAL is not expected to grow much beyond 4MiB (that's when the passive autocheckpointing kicks in), we saw WALs of > 850MiB. At that point, reads slow down significantly and that can cause a failure for the system as a whole.

To mitigate this, as per the SQLite docs, this PR:

  • Disables the passive autocheckpointing (leaving it enabled conflicts with manual checkpointing which can then result in a SQLITE_BUSY)
  • Runs active checkpointing every second. It is very fast when there is little-to-no work to do, but under load it is expected that we'll hit the '1000 page mutations' (AKA the 4MiB default WAL size) within a second.
  • We use the 'RESTART' strategy, together with a journal_file_limit that ensures that the WAL will be trimmed down to the max of 4MiB. That means we don't always fully truncate, nor do we keep it at 'whatever the max happened to be'.

Doing this checkpointing does mean that every second there is a tiny timeframe in which both write-tasks and also all read-tasks will have to wait. This is unlikely to cause any problems. We now explicitly configure the busy timeout to be 5 seconds, which was the prior implicit default of SQLx/Rusqlite for good measure.

The default way SQLite in WAL mode performs checkpoints, is by waiting
for quiet time where there are no readers nor writers.

Any read-connection can block this passive WAL checkpointing from making
progress. We have observed that in production when having large
workloads with consumers working on hundreds of chunks concurrently.

Whereas the WAL is not expected to grow much beyond 4MiB (that's when
the passive autocheckpointing kicks in), we saw WALs of > 850MiB. At
that point, reads slow down significantly and that can cause a failure
for the system as a whole.

To mitigate this, as per the SQLite docs, this PR:
- Disables the passive autocheckpointing (leaving it enabled conflicts
  with manual checkpointing which can then result in a SQLITE_BUSY)
- Runs active checkpointing _every second_. It is very fast when there
  is little-to-no work to do, but under load it is expected that we'll
hit the '1000 page mutations' (AKA the 4MiB default WAL size) within a
second.
- We use the 'RESTART' strategy, together with a `journal_file_limit`
  that ensures that the WAL will be trimmed down to the max of 4MiB.
That means we don't always fully truncate, nor do we keep it at
'whatever the max happened to be'.

Doing this checkpointing does mean that every second there is a tiny
timeframe in which both write-tasks and also all read-tasks will have to
wait. This is unlikely to cause any problems. We now explicitly
configure the busy timeout to be 5 seconds, which was the prior implicit
default of SQLx/Rusqlite for good measure.
@Qqwy
Copy link
Contributor Author

Qqwy commented Mar 26, 2026

SHOW @ReinierMaas

@Qqwy Qqwy force-pushed the active-sqlite-wal-checkpointing branch from 7894cc2 to 1f005b9 Compare March 26, 2026 11:47
@Qqwy
Copy link
Contributor Author

Qqwy commented Mar 26, 2026

@OpsBotPrime merge and tag

@OpsBotPrime
Copy link
Contributor

Rebased as 9b4d5e2, waiting for CI …

OpsBotPrime added a commit that referenced this pull request Mar 26, 2026
Approved-by: Qqwy
Priority: Normal
Auto-deploy: false
@OpsBotPrime
Copy link
Contributor

CI job 🟡 started.

@OpsBotPrime
Copy link
Contributor

The build failed ❌.

If this is the result of a flaky test, then tag me again with the retry command. Otherwise, push a new commit and tag me again.

@Qqwy
Copy link
Contributor Author

Qqwy commented Mar 26, 2026

@OpsBotPrime retry

Approved-by: Qqwy
Priority: Normal
Auto-deploy: false
@OpsBotPrime
Copy link
Contributor

Rebased as 5c25128, waiting for CI …

@OpsBotPrime
Copy link
Contributor

CI job 🟡 started.

Copy link
Contributor

@ReinierMaas ReinierMaas left a comment

Choose a reason for hiding this comment

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

LGTM! Only a single comment thanks for looking into this!

Comment on lines +246 to +247
/// We use the 'TRUNCATE' strategy, which will do the most work but will briefly block the writer *and* all readers
///
Copy link
Contributor

Choose a reason for hiding this comment

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

You use the RESTART strategy.

@OpsBotPrime
Copy link
Contributor

@Qqwy I tagged your PR with v46. Please wait for the build of 5c25128 to pass and don't forget to deploy it!

@OpsBotPrime OpsBotPrime merged commit 5c25128 into master Mar 26, 2026
7 checks passed
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.

3 participants