Use WAL mode and bounded checkpointing for mixed read/write workloads.
Recommended baseline (matches default StoreConfig):
journal_mode=WALsynchronous=NORMALbusy_timeout=5000wal_autocheckpoint=1000
When durability requirements are strict and write latency is acceptable, use synchronous=FULL.
Current schema includes critical indexes:
messages(account_id)andmessages(folder_id)messages(subject)for broad LIKE filter supportoutbox(account_id)andoutbox(status, next_attempt_at)sync_state(account_id, folder_id)account_feature_flags(account_id)
Operational checks:
- Run
EXPLAIN QUERY PLANfor slow inbox/search/retry queries. - Add targeted indexes only after query-plan confirmation.
- Avoid index bloat from speculative indexes.
Key growth drivers:
messages: dominant table for inbox retention.outbox: accumulates sent + failed history.- WAL file size: rises with write bursts until checkpoint.
Suggested thresholds:
- Track DB file size and WAL size independently.
- Alert when WAL remains large over multiple checkpoints.
- Alert when outbox failed backlog exceeds operational SLO.
Repository helpers are available for routine cleanup:
delete_sent_outbox_before(cutoff_ts)delete_old_messages_before(cutoff_ts)
Suggested jobs:
- Daily: delete old
sentoutbox rows beyond retention. - Weekly: delete old message rows per policy.
- After large deletes: run
VACUUMin a maintenance window if file compaction is needed.
Outbox retry pressure:
SELECT status, COUNT(*) FROM outbox GROUP BY status;Largest message-age buckets:
SELECT
CASE
WHEN received_at >= strftime('%s','now') - 86400 THEN 'lt_1d'
WHEN received_at >= strftime('%s','now') - 604800 THEN 'lt_7d'
ELSE 'ge_7d'
END AS age_bucket,
COUNT(*)
FROM messages
GROUP BY age_bucket;WAL checkpoint and size stabilization:
PRAGMA wal_checkpoint(TRUNCATE);