Skip to content

Commit 9dfe52f

Browse files
committed
fix(store): warn when a checkpoint can't reset a large WAL (starvation)
Add a checkpoint-starvation warning to cbm_store_checkpoint: read the wal_checkpoint_v2 out-params (frames in the WAL vs frames reclaimed) and, when the WAL is large (>= ~1 GiB of frames) but the PASSIVE checkpoint couldn't reset it because readers hold marks, log store.wal.starving. This is the operator visibility the reclaim limit can't provide on its own — it surfaces the starvation (and its scale) before the -wal file fills the disk. Portable (no file stat / no path handling), fires only under genuine starvation. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 4a3a9b7 commit 9dfe52f

1 file changed

Lines changed: 22 additions & 1 deletion

File tree

‎src/store/store.c‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ enum {
2626
ST_FOUND = -1,
2727
ST_BUF_16 = 16,
2828
ST_BUF_64 = 64,
29+
/* #1083: warn when a PASSIVE checkpoint sees a WAL this large (frames) but
30+
* can't reset it — ~1 GiB at a 4 KiB page, far past the healthy ~1000-frame
31+
* autocheckpoint, so it only fires under genuine starvation. */
32+
ST_WAL_STARVE_WARN_FRAMES = 262144,
2933
/* file: URI for the immutable read-only fallback. A path is at most
3034
* CBM_SZ_1K; percent-encoding can triple it, plus the "file://" prefix,
3135
* the "?immutable=1" suffix and a leading '/'. */
@@ -1087,11 +1091,28 @@ int cbm_store_checkpoint(cbm_store_t *s) {
10871091
* SIGBUS in a sibling process that has the DB mmap'd through SQLite
10881092
* when it next faults a page in the now-shorter region.
10891093
* See https://www.sqlite.org/c3ref/c_checkpoint_full.html */
1090-
int rc = sqlite3_wal_checkpoint_v2(s->db, NULL, SQLITE_CHECKPOINT_PASSIVE, NULL, NULL);
1094+
int wal_frames = 0;
1095+
int checkpointed = 0;
1096+
int rc = sqlite3_wal_checkpoint_v2(s->db, NULL, SQLITE_CHECKPOINT_PASSIVE, &wal_frames,
1097+
&checkpointed);
10911098
if (rc != SQLITE_OK) {
10921099
store_set_error_sqlite(s, "checkpoint");
10931100
return CBM_STORE_ERR;
10941101
}
1102+
/* #1083: a large WAL that a PASSIVE checkpoint can't fully reset — because
1103+
* concurrent readers hold marks — is the checkpoint-starvation signal. Warn
1104+
* so an operator can see it (and the driving processes) before the -wal file
1105+
* fills the disk; journal_size_limit only reclaims once a reset succeeds.
1106+
* wal_frames = frames in the WAL, checkpointed = frames reclaimed this pass. */
1107+
if (wal_frames >= ST_WAL_STARVE_WARN_FRAMES && checkpointed < wal_frames) {
1108+
char frames_buf[ST_BUF_16];
1109+
char ckpt_buf[ST_BUF_16];
1110+
snprintf(frames_buf, sizeof(frames_buf), "%d", wal_frames);
1111+
snprintf(ckpt_buf, sizeof(ckpt_buf), "%d", checkpointed);
1112+
cbm_log_warn("store.wal.starving", "wal_frames", frames_buf, "checkpointed", ckpt_buf,
1113+
"hint",
1114+
"concurrent readers block the WAL reset — the -wal file keeps growing");
1115+
}
10951116
return exec_sql(s, "PRAGMA optimize;");
10961117
}
10971118

0 commit comments

Comments
 (0)