munin/internal/store/pool.go
2026-10-19 10:13:56 +02:00

90 lines
3.2 KiB
Go

package store
import (
"context"
"database/sql"
)
// pool ist ein Verbindungspool samt dem Dialekt seiner Datenbank.
//
// # Warum nicht direkt *sql.DB?
//
// Jede Abfrage muss durch dialect.rebind, bevor sie an die Datenbank geht —
// sonst bekommt PostgreSQL ein "?", mit dem es nichts anfangen kann. Würde der
// Store *sql.DB direkt benutzen, wäre das bei jeder der rund 100 Abfragen eine
// Stelle, an der man es vergessen kann.
//
// pool bietet deshalb nur die Methoden an, die der Store braucht, und jede
// davon wandelt um. An *sql.DB selbst kommt außerhalb dieser Datei niemand
// heran — eine Abfrage am Dialekt vorbei lässt sich gar nicht erst schreiben.
type pool struct {
db *sql.DB
d dialect
}
func (p pool) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
return p.db.ExecContext(ctx, p.d.rebind(query), args...)
}
func (p pool) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
return p.db.QueryContext(ctx, p.d.rebind(query), args...)
}
func (p pool) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row {
return p.db.QueryRowContext(ctx, p.d.rebind(query), args...)
}
// beginTx startet eine Transaktion, deren Abfragen ebenfalls umgewandelt
// werden.
func (p pool) beginTx(ctx context.Context) (*transaction, error) {
tx, err := p.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
return &transaction{tx: tx, d: p.d}, nil
}
func (p pool) Close() error { return p.db.Close() }
// PoolStats sind die Zahlen eines Lese- und eines Schreibpools, wie
// database/sql sie führt — für die Systemseite (AUSBAUSTUFEN.md 5.12).
type PoolStats struct {
Read, Write sql.DBStats
}
// OpenConns zählt die offenen Verbindungen beider Pools zusammen. Genau so viele
// belegt Munin gerade beim Datenbankserver.
func (v PoolStats) OpenConns() int { return v.Read.OpenConnections + v.Write.OpenConnections }
// InUse zählt die Verbindungen, die gerade eine Abfrage ausführen.
func (v PoolStats) InUse() int { return v.Read.InUse + v.Write.InUse }
// WaitCount zählt, wie oft eine Anfrage auf eine freie Verbindung warten
// musste, seit der Pool geöffnet wurde. Wächst die Zahl stetig, sind die
// Pools zu klein für die Last.
func (v PoolStats) WaitCount() int64 { return v.Read.WaitCount + v.Write.WaitCount }
// transaction ist das Gegenstück zu pool für *sql.Tx: dieselben Methoden,
// dieselbe Umwandlung.
type transaction struct {
tx *sql.Tx
d dialect
}
func (t *transaction) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
return t.tx.ExecContext(ctx, t.d.rebind(query), args...)
}
func (t *transaction) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
return t.tx.QueryContext(ctx, t.d.rebind(query), args...)
}
func (t *transaction) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row {
return t.tx.QueryRowContext(ctx, t.d.rebind(query), args...)
}
func (t *transaction) Commit() error { return t.tx.Commit() }
// Rollback nach einem erfolgreichen Commit ist wirkungslos — deshalb kann es
// bedenkenlos per defer stehen.
func (t *transaction) Rollback() error { return t.tx.Rollback() }