db

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BusyRetryBackoffs = []time.Duration{
	10 * time.Millisecond,
	20 * time.Millisecond,
	40 * time.Millisecond,
	80 * time.Millisecond,
	160 * time.Millisecond,
	320 * time.Millisecond,
	640 * time.Millisecond,
	1000 * time.Millisecond,
}

BusyRetryBackoffs is the shared retry schedule for transient dqlite/SQLite contention. Total max wait ~2.27s across 8 retries: the budget must outlast worst-case transient write-lock windows, which under burst catalog contention exceed a few hundred ms. It is the single source of truth for the autocommit pool retry here and the per-transaction busy-retry helpers in internal/run and internal/backfill, so the layers compose predictably and cannot drift.

Functions

func Connection

func Connection() *gorm.DB

func Migrate

func Migrate() (err error)

func MigrateTaskRunUniquePartitionIndex

func MigrateTaskRunUniquePartitionIndex(conn *gorm.DB) error

MigrateTaskRunUniquePartitionIndex drops a pre-fan-out idx_taskrun_jobrun_task so AutoMigrate can recreate it in its fan-out shape: UNIQUE over (job_run_id, task_id, partition_index).

Why an explicit migration is required. Stream A1 changed the struct tags on models.TaskRun from `index:idx_taskrun_jobrun_task` to `uniqueIndex:idx_taskrun_jobrun_task` and added partition_index as a third member — but kept the index NAME. GORM's AutoMigrate only ever asks "does an index with this name exist?"; when it does, it leaves it completely alone. It never compares the existing index's columns or its uniqueness against the model. So on a fresh database the new tags produce the right index and everything looks correct in CI, while every EXISTING deployment silently keeps the old two-column, non-unique index forever. The unique (job_run_id, task_id, partition_index) constraint — the core invariant that makes a TaskRun row addressable under fan-out, and the DB-level backstop against a double expansion inserting duplicate instances — would simply never be enforced in production, with no error anywhere.

The fix is idempotent and safe to run on every boot:

  1. Read the existing index definition (nothing to do if there is none — AutoMigrate creates the correct one).
  2. If it is already UNIQUE and already covers partition_index, leave it.
  3. Otherwise DROP it; AutoMigrate, which runs immediately after, recreates it from the current struct tags.

Dropping is safe with respect to data: the new index is strictly wider than the old one, so any row set the old index permitted (one row per (job_run_id, task_id), i.e. partition_index 0) satisfies the new uniqueness, and a database that already holds fanned rows has distinct partition_index values per group by construction. There is no window in which queries lose their index either — both steps run inside the same Migrate() call before the server starts serving.

func NewLogger

func NewLogger() gormlogger.Interface

NewLogger creates a GORM logger that delegates to pkg/log.

func Transaction

func Transaction(ctx context.Context, fn func(tx *gorm.DB) error) error

Transaction runs fn inside a single transaction against the default connection, retrying the whole transaction on transient dqlite contention.

Use this for multi-statement units that must commit atomically. Statements issued inside a transaction bypass the connection pool's per-statement retry (re-running one statement of a partially-applied transaction would be unsafe), so the entire BEGIN..COMMIT is retried here on the shared BusyRetryBackoffs budget. A rolled-back transaction leaves no state, so re-running fn from the top is safe.

Types

type DatabaseRole

type DatabaseRole string

DatabaseRole identifies the logical database tier for a table.

const (
	DatabaseRoleCatalog DatabaseRole = "catalog"
	DatabaseRoleHot     DatabaseRole = "hot"
	DatabaseRoleCold    DatabaseRole = "cold"
)

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router owns the catalog, hot-shard, and cold-history database handles.

The default shard count is one, in which case every route returns the catalog connection and existing single-database behavior is preserved.

func DefaultRouter

func DefaultRouter() *Router

DefaultRouter returns the process-wide database router.

func NewRouter

func NewRouter(catalog *gorm.DB, hot []*gorm.DB, cold *gorm.DB) (*Router, error)

NewRouter constructs a Router from already-opened connections. Tests and non-dqlite callers can use this directly; production code should normally use DefaultRouter().

func (*Router) Catalog

func (r *Router) Catalog() *gorm.DB

Catalog returns the write-light catalog database.

func (*Router) Close

func (r *Router) Close() error

Close releases every distinct database the router owns (catalog, hot shards, cold). For internal dqlite the pool is a read/write splitter, so this closes both its write and read pools; otherwise it closes the single pool. Handles can alias (single-shard mode points hot/cold at the catalog), so each distinct handle is closed once.

func (*Router) Cold

func (r *Router) Cold() *gorm.DB

Cold returns the cold-history database. With one shard it aliases Catalog().

func (*Router) Database

func (r *Router) Database(role DatabaseRole, runID uuid.UUID) (*gorm.DB, int, error)

Database returns the connection for a logical role. Hot routes require a non-zero run ID so all rows in one run stay transactionally local.

func (*Router) HotShard

func (r *Router) HotShard(index int) (*gorm.DB, error)

HotShard returns a hot shard by index.

func (*Router) HotShardForRun

func (r *Router) HotShardForRun(runID uuid.UUID) (*gorm.DB, int, error)

HotShardForRun returns the hot shard that owns a run's lifecycle rows.

func (*Router) HotShards

func (r *Router) HotShards() []*gorm.DB

HotShards returns a copy of the hot shard connection slice.

func (*Router) RouteTable

func (r *Router) RouteTable(table string, runID uuid.UUID) (*gorm.DB, DatabaseRole, int, error)

RouteTable returns the database for a table. Hot tables require runID; catalog tables ignore it.

func (*Router) ShardCount

func (r *Router) ShardCount() int

ShardCount returns the number of hot shards.

func (*Router) ShardForRunID

func (r *Router) ShardForRunID(runID uuid.UUID) int

ShardForRunID maps a job run ID to a stable hot-shard index.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL