# CloudNativePG PostgreSQL Database

**Host:** `ddb-cluster-rw.ddb.svc.cluster.local` (read-write)  
**Read replica:** `ddb-cluster-ro.ddb.svc.cluster.local` (read-only)  
**Port:** `5432`  
**Namespace:** `ddb`

## When to Use

- **Multi-replica HA** — 3 replicas, automatic failover
- **pgvector extension** — Vector similarity search (LLM embeddings)
- **Transactional data** — Authentik, Story Crater backend, custom apps
- **Declarative backups** — Automated WAL archiving to MinIO

## Quick Start

**1. Connect from pod:**
```bash
# Inside a pod (inject secret mount)
psql -h ddb-cluster-rw.ddb.svc.cluster.local \
  -U story_crater \
  -d story_crater \
  -W  # prompt for password (from Secret)
```

**2. Create database & user (one-time):**
```bash
# Already done by helmfile postsync hook
# But if needed manually:

psql -h ddb-cluster-rw.ddb.svc.cluster.local \
  -U postgres \
  -c "CREATE DATABASE myapp OWNER postgres;"

psql -h ddb-cluster-rw.ddb.svc.cluster.local \
  -U postgres \
  -d myapp \
  -c "CREATE USER myapp_user WITH PASSWORD 'secret';"

psql -h ddb-cluster-rw.ddb.svc.cluster.local \
  -U postgres \
  -d myapp \
  -c "GRANT ALL PRIVILEGES ON DATABASE myapp TO myapp_user;"
```

**3. Enable pgvector:**
```bash
psql -h ddb-cluster-rw.ddb.svc.cluster.local \
  -U postgres \
  -d myapp \
  -c "CREATE EXTENSION IF NOT EXISTS vector;"
```

**4. Create table with embeddings:**
```sql
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  embedding vector(1536),  -- OpenAI embeddings
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX ON documents USING IVFFLAT (embedding vector_cosine_ops);
```

## Configuration

| Key | Value |
|-----|-------|
| Host (RW) | `ddb-cluster-rw.ddb.svc.cluster.local` |
| Host (RO) | `ddb-cluster-ro.ddb.svc.cluster.local` |
| Port | 5432 |
| Replicas | 3 (automatic failover) |
| Extensions | pgvector (LLM embeddings), uuid-ossp |
| Backups | WAL archiving to MinIO (continuous) |
| Retention | 30 days |

## Common Patterns

**Connection pooling (from app):**
```go
import "github.com/jackc/pgx/v5/pgxpool"

config, _ := pgxpool.ParseConfig("postgres://user:pass@ddb-cluster-rw.ddb.svc.cluster.local:5432/myapp")
config.MaxConns = 25
config.MinConns = 5
pool, _ := pgxpool.NewWithConfig(ctx, config)

// Use pool
row := pool.QueryRow(ctx, "SELECT COUNT(*) FROM users")
```

**Read from replica (analytics):**
```go
// Offload SELECT queries to read replica
pool.QueryRow(ctx, "SELECT * FROM documents LIMIT 1") // auto-routes to RO if available

// Writes always go to RW
pool.Exec(ctx, "INSERT INTO documents ...")
```

**Vector similarity search:**
```sql
SELECT id, content, embedding <-> $1 AS distance
FROM documents
ORDER BY embedding <-> $1
LIMIT 10;
-- $1 = query embedding (e.g., from OpenAI API)
```

**Backup & restore:**
```bash
# Backups are automatic (WAL to MinIO)
# To restore from backup:
# 1. Check MinIO s3://postgresql-backups/
# 2. Use PostgreSQL PITR (point-in-time recovery)
# 3. Contact SRE for restore procedure
```

## Monitoring

**Grafana dashboard:** `svc-postgresql` (auto-configured)

**Key metrics:**
- `pg_stat_activity_connections` — active connections
- `pg_stat_database_blks_read` — disk I/O
- `pg_replication_lag_seconds` — replica lag (goal: < 1s)

**CLI health check:**
```bash
# Check replication status
kubectl exec -n ddb pod/ddb-cluster-1 -- \
  psql -U postgres -c "SELECT slot_name, restart_lsn FROM pg_replication_slots;"

# Check replica lag
kubectl exec -n ddb pod/ddb-cluster-2 -- \
  psql -U postgres -c "SELECT now() - pg_last_xact_replay_timestamp() AS lag;"
```

## Secrets & Credentials

**All user passwords stored in Vault:**
```bash
# Read password
core get cluster/STORY_CRATER_PG_PASSWORD --key STORY_CRATER_PG_PASSWORD

# Inject into pod (auto via Secret volume)
# Mount: /run/secrets/db-password
```

**Connection string from env:**
```bash
POSTGRES_CONNECTION="postgres://story_crater:${STORY_CRATER_PG_PASSWORD}@ddb-cluster-rw.ddb.svc.cluster.local:5432/story_crater"
```

## Troubleshooting

**Cannot connect (connection refused):**
```bash
# Verify cluster is running
k get pods -n ddb

# Check Service DNS
k exec -it pod/debug-pod -- nslookup ddb-cluster-rw.ddb.svc.cluster.local

# Verify Secret has password
k get secret -n ddb ddb-cluster-superuser -o jsonpath='{.data.password}' | base64 -d
```

**Replica lag is high (> 10s):**
```bash
# Check replica pod CPU/memory
k top pod -n ddb

# Scale down other workloads if cluster is overloaded
# Or scale up database resources (helmfile.yaml.gotmpl)
```

**pgvector queries slow:**
```sql
-- Ensure index exists
SELECT * FROM pg_indexes WHERE tablename = 'documents' AND indexname LIKE '%embedding%';

-- Re-index if missing
CREATE INDEX ON documents USING IVFFLAT (embedding vector_cosine_ops);
```

See `/TROUBLESHOOTING.md` for full incident guide.
