Checkpoint without waiting on readers and log a busy checkpoint (closes #102)
check / check (push) Canceled after 0s

The 5-second checkpoint ran in TRUNCATE mode, which waits up to the
5-second busy timeout for every open read while holding the database
lock every batch write needs. It now runs in PASSIVE mode, which never
waits. A WAL file over 64 MiB instead gets one TRUNCATE checkpoint whose
wait for reads is capped at 500 ms and logged; it empties the file once
no read is open, and while reads follow one another with no pause it
can give up every time. A checkpoint that leaves part of the WAL
uncopied returns ErrCheckpointIncomplete, which the maintainer logs as
incomplete instead of completed. The writing connection's
journal_size_limit is 64 MiB.

Model: opus-5-5
This commit was merged in pull request #126.
This commit is contained in:
clawbot committed 2026-10-20 01:06:51 +02:00
1 parent 28f60500cb
commit 76c2e523b5
4 files changed
+430 -6

No files matched your search

+13 -2
View File
@@ -2,6 +2,7 @@ package routewatch
import (
"context"
"errors"
"log/slog"
"sync"
"time"
@@ -34,6 +35,12 @@ const (
analyzeTimeout = 5 * time.Minute
)
// checkpointIncompleteMessage is logged, at debug level like a completed
// checkpoint, when a checkpoint leaves part of the WAL for a later one: while
// reads overlap, most do. The database warns when the WAL file stays over its
// size limit.
const checkpointIncompleteMessage = "WAL checkpoint incomplete"
// DBMaintainer handles background database maintenance tasks.
type DBMaintainer struct {
db database.Store
@@ -123,9 +130,13 @@ func (m *DBMaintainer) runCheckpoint(ctx context.Context) {
duration, err := runWithTimeout(ctx, checkpointTimeout, m.db.Checkpoint)
m.recordResult(&m.lastCheckpoint, &m.lastCheckpointError, &m.checkpointCount, err)
if err != nil {
switch {
case errors.Is(err, database.ErrCheckpointIncomplete):
m.logger.Debug(checkpointIncompleteMessage,
"error", err, "duration", duration)
case err != nil:
m.logger.Error("WAL checkpoint failed", "error", err, "duration", duration)
} else {
default:
m.logger.Debug("WAL checkpoint completed", "duration", duration)
}
}