1 Commits
Author SHA1 Message Date
sneak 75086ee1a5 Write batches through one connection with a 1 GiB cache, in key order (closes #96)
check / check (push) Canceled after 0s
Every write now uses one connection taken from the pool when the
database opens, under the database lock. Its 1 GiB page cache keeps
the pages the route writes touch from one batch to the next. A reading
connection's cache is emptied whenever another connection has written,
so the 9 reading connections drop to 16 MiB each. The soft and hard
heap limits rise to 1.5 and 2 GiB, above the caches; README "Memory"
adds up the new budget. Vacuum, Analyze and Checkpoint check their
context after the lock, which the kept connection does not. Batch
writes sort their rows by peer, then prefix. The benchmark also times
the largest prefix handler batch, 250,000 updates.

Model: opus-5-5
2026-10-19 12:52:03 +00:00
+19 -3
View File
@@ -2232,7 +2232,8 @@ func (d *Database) GetIPInfoContext(
// Vacuum runs incremental vacuum to return up to 1000 unused pages to the
// filesystem. Like Analyze and Checkpoint, it holds the database lock, so it
// and the batch writes wait for each other instead of failing on SQLite's
// write lock.
// write lock, and returns ctx's error without running if ctx ended while it
// waited: the writing connection it uses does not check ctx itself.
func (d *Database) Vacuum(ctx context.Context) error {
// Free up to 1000 pages per call (~4MB with default 4KB page size)
// This keeps each vacuum operation quick
@@ -2241,6 +2242,11 @@ func (d *Database) Vacuum(ctx context.Context) error {
d.lock("Vacuum")
defer d.unlock()
err := ctx.Err()
if err != nil {
return fmt.Errorf("failed to run incremental vacuum: %w", err)
}
rows, err := d.writer.QueryContext(ctx,
fmt.Sprintf("PRAGMA incremental_vacuum(%d)", pagesToFree))
if err != nil {
@@ -2271,9 +2277,14 @@ func (d *Database) Analyze(ctx context.Context) error {
d.lock("Analyze")
defer d.unlock()
err := ctx.Err()
if err != nil {
return fmt.Errorf("failed to analyze database: %w", err)
}
// analysis_limit is a setting of the connection, so it is set in the same
// call as PRAGMA optimize, which runs both statements on one connection.
_, err := d.writer.ExecContext(ctx, fmt.Sprintf(
_, err = d.writer.ExecContext(ctx, fmt.Sprintf(
"PRAGMA analysis_limit=%d; PRAGMA optimize=%#x",
analysisLimit, optimizeAllTables))
if err != nil {
@@ -2290,7 +2301,12 @@ func (d *Database) Checkpoint(ctx context.Context) error {
d.lock("Checkpoint")
defer d.unlock()
_, err := d.writer.ExecContext(ctx, "PRAGMA wal_checkpoint(TRUNCATE)")
err := ctx.Err()
if err != nil {
return fmt.Errorf("failed to checkpoint WAL: %w", err)
}
_, err = d.writer.ExecContext(ctx, "PRAGMA wal_checkpoint(TRUNCATE)")
if err != nil {
return fmt.Errorf("failed to checkpoint WAL: %w", err)
}