check / check (push) Canceled after 0s
The live route tables lose their random UUID key; the row id the prefix-length sample draws is declared as the integer key. The prefix tables are keyed by prefix and keep only first_seen; a batch inserts only prefixes not stored yet. A route update sets only the columns that change. Each kept index names the queries using it. Every write goes through one connection kept for writing, with a 1 GiB page cache; reading connections get 16 MiB, heap limits rise to 1.5 and 2 GiB (README "Memory"). Batch writes go in key order. A benchmark run by hand writes 250,000 updates on 5 million routes faster than the feed's 10,000 a second; a live run follows. 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...)
|
|
}
|