Database Migrations & Pooling

Configure PostgreSQL 16+, tune pgxpool connection limits, and execute automated database migrations.

ScanDrix requires PostgreSQL 16+ with Row-Level Security (RLS) enabled. The Go backend uses pgx/v5 with an internal pool manager (pgxpool) optimized for multi-tenant isolation and high-concurrency connection handling.

Connection pool configuration

Tune PostgreSQL pool settings via environment variables in production:

VariableDefaultRecommended ProductionDescription
DATABASE_URL—postgres://user:pass@host:5432/scandrix?sslmode=requireStandard PostgreSQL connection URI.
DB_MAX_CONNS2550 - 100Maximum open connections per API replica.
DB_MIN_CONNS210Minimum idle connections kept warm.
DB_MAX_CONN_LIFETIME1h1hMaximum time a connection may be reused.
DB_MAX_CONN_IDLE_TIME15m15mMaximum time an idle connection remains alive.
go
// Sample internal pool configuration from ScanDrix/internal/database/db.go
config.MaxConns = 50
config.MinConns = 10
config.MaxConnLifetime = 1 * time.Hour
config.MaxConnIdleTime = 15 * time.Minute
config.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol

Running database migrations

ScanDrix uses golang-migrate/migrate for versioned, atomic schema migrations located in migrations/:

bash
# Apply all pending migrations forward
scandrix db migrate up

# Check current migration schema version
scandrix db version

# Roll back the single most recent migration (if needed)
scandrix db migrate down 1

Standalone Docker migration runner

In automated CI/CD pipelines, run migrations as a pre-deploy Kubernetes Job:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: scandrix-migration-job
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrator
          image: ghcr.io/scandrix/scandrix:latest
          command: ["/bin/scandrix", "db", "migrate", "up"]
          envFrom:
            - secretRef:
                name: scandrix-db-credentials

Row-Level Security (RLS) verification

ScanDrix enforces tenant isolation at the database engine level. Every tenant-scoped query runs inside a transaction that sets:

sql
SET LOCAL app.current_tenant_id = 'org_abc123';

Any attempt by a query to read or write rows belonging to another organization ID is rejected by PostgreSQL with an RLS policy violation error.