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
84 lines
2.2 KiB
Go
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...)
|
|
}
|