# M2.2 — CNPG `memory-db` + pgvector | Field | Value | |---|---| | Phase | M2 — Projections | | Size | M — 1–3 days | | Status | ✅ Done | | Flags | homelab | | Spec | inlined below | | Blocks | — | ## Goal A Postgres with pgvector, provisioned the way everything else in the cluster is: through git, with no manual `psql`. ## Facts (inlined — no spec read needed) **pgvector needs no custom image.** Verified on the running cluster: ``` $ psql -tAc "select name,default_version,installed_version from pg_available_extensions where name='vector'" vector|0.7.0| ``` on the stock `ghcr.io/cloudnative-pg/postgresql:16.2`. Available, not yet installed — `CREATE EXTENSION` is all that is missing. **The operator is CNPG 1.30.0**, which supports declarative extensions on the `Database` CRD (`kubectl explain database.spec.extensions` resolves). So the extension is git-managed too — no manual step, consistent with the GitOps rule that infrastructure changes flow through version control. Follow `k8s/infra/databases/temporal-db.yaml` exactly: 3 instances, `imageName` pinned, `enableSuperuserAccess: false`, `storageClass: longhorn-cnpg`, `enablePodMonitor: true`, control-plane tolerations, `podAntiAffinityType: preferred`. Storage: 10Gi matches the existing clusters. At 768 dims × 4 bytes, a vector is ~3 KB; tens of thousands of nodes is well under a gigabyte, so 10Gi is generous and consistent rather than tight. ## Steps 1. `k8s/infra/databases/memory-db.yaml` — `Cluster` + `Database` with `extensions: [{name: vector, ensure: present}]`. 2. Namespace `memory`, created by the ArgoCD app that owns it. 3. Add to the owning kustomization's explicit resource list — an unlisted file is silently dropped with no error and no drift shown. 4. Commit, push, let ArgoCD sync. **No `kubectl apply`.** 5. Verify the extension installed and the app user can create tables. 6. Record the connection string convention in the repo README; the password comes from the CNPG-generated secret, never committed. ## Acceptance - `Cluster` reaches `Cluster in healthy state` with 3 instances. - `select extversion from pg_extension where extname='vector'` returns a version. - ArgoCD shows the app `Synced/Healthy`. - No manual `psql` was run to get there. ## Verify **Harness:** `kubectl` and `psql` read-only checks after sync. **Integration test** — `verify/m2.2.sh`, output diffed against `expected/m2.2.txt`: 1. `a1_cluster_healthy` — `kubectl get cluster -n memory memory-db` reports 3/3 ready. 2. `a2_extension_installed` — `select extname, extversion from pg_extension where extname='vector'` returns one row. 3. `a3_declarative_not_manual` — `kubectl get database -n memory memory-db-vector -o jsonpath='{.spec.extensions}'` shows the declaration, proving it came from git. 4. `a4_argocd_synced` — the owning app is `Synced/Healthy`. 5. `a5_app_user_can_ddl` — as `app`, `CREATE TABLE t(v vector(768)); DROP TABLE t;` succeeds. 6. `a6_hnsw_available` — `CREATE INDEX ... USING hnsw` on that temp table succeeds, proving 0.7.0 has the index type the schema needs. **Command:** `bash verify/m2.2.sh | diff - expected/m2.2.txt` **False pass:** - Checking `pg_available_extensions` instead of `pg_extension`. Available means the files are on disk; installed means `CREATE EXTENSION` ran. The whole task is the second one. - Verifying after a manual `CREATE EXTENSION`. It passes and proves nothing about the declarative path, which is the actual deliverable. Assertion 3 is the guard. ## Traps - Forgetting the kustomization resource list. The file sits in git, ArgoCD reports Synced, and the objects never exist — silent, and the failure surfaces later as a connection error. - Adding `prune: true` semantics without thinking about operator-created children. CNPG creates Services, Secrets and PVCs owned by the Cluster; if ArgoCD's tracking label propagates to them, prune fights the operator. The `llm-serving` app already had to set `prune: false` for exactly this reason. --- Background: [DESIGN.md](../DESIGN.md) — pgvector · `k8s/infra/databases/temporal-db.yaml`