Files
routewatch/internal/database/slowquery.go
T
sneak 5ae25a1d48
check / check (push) Canceled after 0s
Write batches through one connection with a 1 GiB cache, in key order (closes #96)
Every write now uses one connection taken from the pool when the
database opens and used 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 so they do not shrink the
caches, and README "Memory" adds up the new budget. 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:50:03 +00:00

84 lines
2.2 KiB
Go

package database
import (
"context"
"database/sql"
"time"
"git.eeqj.de/sneak/routewatch/internal/logger"
)
const slowQueryThreshold = 25 * time.Millisecond
// logSlowQuery logs queries that take longer than slowQueryThreshold
func logSlowQuery(logger *logger.Logger, query string, start time.Time) {
elapsed := time.Since(start)
if elapsed > slowQueryThreshold {
logger.Debug("Slow query", "query", query, "duration", elapsed)
}
}
// exec runs a statement on the writing connection, with slow query logging
func (d *Database) exec(query string, args ...any) error {
start := time.Now()
defer logSlowQuery(d.logger, query, start)
_, err := d.writer.ExecContext(context.Background(), query, args...)
return err
}
// loggingTx wraps sql.Tx to log slow queries
type loggingTx struct {
*sql.Tx
logger *logger.Logger
}
// QueryRow wraps sql.Tx.QueryRow to log slow queries
func (tx *loggingTx) QueryRow(query string, args ...any) *sql.Row {
start := time.Now()
defer logSlowQuery(tx.logger, query, start)
return tx.Tx.QueryRowContext(context.Background(), query, args...)
}
// QueryRowContext wraps sql.Tx.QueryRowContext to log slow queries
func (tx *loggingTx) QueryRowContext(
ctx context.Context, query string, args ...any,
) *sql.Row {
start := time.Now()
defer logSlowQuery(tx.logger, query, start)
return tx.Tx.QueryRowContext(ctx, query, args...)
}
// QueryContext wraps sql.Tx.QueryContext to log slow queries
func (tx *loggingTx) QueryContext(
ctx context.Context, query string, args ...any,
) (*sql.Rows, error) {
start := time.Now()
defer logSlowQuery(tx.logger, query, start)
//nolint:sqlclosecheck // returns the rows, which the caller closes
return tx.Tx.QueryContext(ctx, query, args...)
}
// Exec wraps sql.Tx.Exec to log slow queries
func (tx *loggingTx) Exec(query string, args ...any) (sql.Result, error) {
start := time.Now()
defer logSlowQuery(tx.logger, query, start)
return tx.Tx.ExecContext(context.Background(), query, args...)
}
// ExecContext wraps sql.Tx.ExecContext to log slow queries
func (tx *loggingTx) ExecContext(
ctx context.Context, query string, args ...any,
) (sql.Result, error) {
start := time.Now()
defer logSlowQuery(tx.logger, query, start)
return tx.Tx.ExecContext(ctx, query, args...)
}