Create new databases with auto_vacuum incremental (closes #43)
check / check (push) Successful in 3m36s
check / check (push) Successful in 3m36s
SQLite only accepts auto_vacuum before the database file is first written. The connection switched to WAL first, which writes the file, so the PRAGMA auto_vacuum in Initialize came too late and was ignored: new databases had auto_vacuum off and the periodic incremental vacuum freed nothing. The setting now goes in the connection string, which the driver applies on open before the journal mode, and the late PRAGMA is removed. Tests check that every pooled connection sees auto_vacuum incremental on a new database and that Vacuum reduces the free page count after routes are deleted. Model: opus-5-5
This commit is contained in:
@@ -3,6 +3,7 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -16,6 +17,13 @@ import (
|
||||
// memory"; the DSN change must leave temp_store below this so they spill to disk.
|
||||
const tempStoreMemory = 2
|
||||
|
||||
// autoVacuumIncremental is the PRAGMA auto_vacuum value meaning "incremental".
|
||||
const autoVacuumIncremental = 2
|
||||
|
||||
// vacuumTestRoutes is how many routes the vacuum test writes and then deletes,
|
||||
// enough to leave many free pages in the file.
|
||||
const vacuumTestRoutes = 2000
|
||||
|
||||
// heldConnections is how many pooled connections the pragma test holds open at
|
||||
// once so each is a distinct SQLite connection that parsed the DSN.
|
||||
const heldConnections = 5
|
||||
@@ -311,7 +319,8 @@ func TestIPv4RangeIntegration(t *testing.T) {
|
||||
|
||||
// TestConnectionPoolPragmas holds several pooled connections open at once and
|
||||
// checks each one carries the per-connection settings from the DSN, plus the
|
||||
// process-wide hard heap limit.
|
||||
// process-wide hard heap limit, and sees the new file with auto_vacuum
|
||||
// incremental.
|
||||
func TestConnectionPoolPragmas(t *testing.T) {
|
||||
cfg := &config.Config{StateDir: t.TempDir()}
|
||||
|
||||
@@ -372,6 +381,72 @@ func TestConnectionPoolPragmas(t *testing.T) {
|
||||
if hardHeapLimit != sqliteHardHeapLimitBytes {
|
||||
t.Errorf("conn %d: hard_heap_limit = %d, want %d", i, hardHeapLimit, sqliteHardHeapLimitBytes)
|
||||
}
|
||||
|
||||
var autoVacuum int
|
||||
if err := c.QueryRowContext(ctx, "PRAGMA auto_vacuum").Scan(&autoVacuum); err != nil {
|
||||
t.Fatalf("conn %d: failed to read auto_vacuum: %v", i, err)
|
||||
}
|
||||
if autoVacuum != autoVacuumIncremental {
|
||||
t.Errorf("conn %d: auto_vacuum = %d, want %d (incremental)", i, autoVacuum, autoVacuumIncremental)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVacuumReturnsFreePages checks that after routes are deleted from a new
|
||||
// database, Vacuum returns the pages they used. With auto_vacuum off (issue
|
||||
// https://git.eeqj.de/sneak/routewatch/issues/43) the free pages stayed in the
|
||||
// file and Vacuum left the count unchanged.
|
||||
func TestVacuumReturnsFreePages(t *testing.T) {
|
||||
cfg := &config.Config{StateDir: t.TempDir()}
|
||||
|
||||
db, err := New(cfg, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ctx := context.Background()
|
||||
ts := time.Now().UTC()
|
||||
const asn = 64500
|
||||
|
||||
routes := make([]*LiveRoute, 0, vacuumTestRoutes)
|
||||
deletions := make([]LiveRouteDeletion, 0, vacuumTestRoutes)
|
||||
for i := range vacuumTestRoutes {
|
||||
route := mkV4Route(t, fmt.Sprintf("10.%d.%d.0/24", i/256, i%256), asn, ts)
|
||||
routes = append(routes, route)
|
||||
deletions = append(deletions, LiveRouteDeletion{
|
||||
Prefix: route.Prefix,
|
||||
OriginASN: asn,
|
||||
PeerIP: route.PeerIP,
|
||||
IPVersion: ipVersionV4,
|
||||
})
|
||||
}
|
||||
|
||||
if err := db.UpsertLiveRouteBatch(routes); err != nil {
|
||||
t.Fatalf("failed to write routes: %v", err)
|
||||
}
|
||||
if err := db.DeleteLiveRouteBatch(deletions); err != nil {
|
||||
t.Fatalf("failed to delete routes: %v", err)
|
||||
}
|
||||
|
||||
var before int
|
||||
if err := db.db.QueryRowContext(ctx, "PRAGMA freelist_count").Scan(&before); err != nil {
|
||||
t.Fatalf("failed to read freelist_count: %v", err)
|
||||
}
|
||||
if before == 0 {
|
||||
t.Fatalf("no free pages after deleting %d routes", vacuumTestRoutes)
|
||||
}
|
||||
|
||||
if err := db.Vacuum(ctx); err != nil {
|
||||
t.Fatalf("Vacuum failed: %v", err)
|
||||
}
|
||||
|
||||
var after int
|
||||
if err := db.db.QueryRowContext(ctx, "PRAGMA freelist_count").Scan(&after); err != nil {
|
||||
t.Fatalf("failed to read freelist_count: %v", err)
|
||||
}
|
||||
if after >= before {
|
||||
t.Errorf("free pages after Vacuum = %d, want fewer than %d", after, before)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user