# v0.85.0 — Kubernetes-Native Deployment > **Status:** Planned > **Scope:** Very Large > **Driven by:** [Assessment 16](../plans/PLAN_OVERALL_ASSESSMENT_16.md) — LT-1, LT-2, LT-6 ## Theme Enable pg_trickle to run as a fully managed, auto-scaling system on Kubernetes. A custom operator manages worker deployments, CDC consumers, and HPA policies via Custom Resource Definitions. An external change log backend (Kafka, NATS JetStream, or local WAL files) provides durable, distributed change event storage. Declarative SQL syntax makes stream table creation feel native to PostgreSQL. ## Items ### LT-1: Kubernetes Operator A Kubernetes operator (Rust, using `kube-rs` controller-runtime) that manages pg_trickle infrastructure: **Custom Resource Definitions:** ```yaml apiVersion: trickle-labs.io/v1alpha1 kind: StreamTableCluster metadata: name: production spec: postgresql: host: pg-primary.default.svc port: 5432 database: myapp secretRef: name: pg-credentials workers: min: 2 max: 32 targetCdcLagMs: 100 targetCpuPercent: 70 image: ghcr.io/trickle-labs/pg-trickle-worker:0.85.0 cdc: mode: external replicas: 1 logBackend: nats nats: url: nats://nats.default.svc:4222 stream: pgtrickle-changes monitoring: prometheus: true serviceMonitor: true otlp: endpoint: otel-collector.monitoring.svc:4317 ``` **Operator responsibilities:** - Deploys worker pods as a Deployment (stateless, horizontally scalable) - Deploys CDC consumer as a StatefulSet (one pod per replication slot) - Configures HPA based on CDC lag custom metric - Watches PostgreSQL catalog for new/dropped stream tables - Manages NATS/Kafka stream lifecycle - Health checks and automatic restart on failure - Graceful draining on pod termination (finish in-progress refresh) **Helm chart:** `charts/pg-trickle/` with values for common configurations. ### LT-2: External Change Log Backend Support pluggable change log backends between CDC consumers and DVM workers: | Backend | Durability | Ordering | Deployment | |---------|-----------|----------|------------| | **PostgreSQL tables** (default) | WAL-logged | Per-source total order | Zero infrastructure | | **Local WAL files** | fsync'd | Per-source total order | Single-node, no external deps | | **NATS JetStream** | Replicated | Per-subject (source) order | Lightweight, K8s-native | | **Apache Kafka** | Replicated | Per-partition order | Enterprise, existing infra | **Change event format** (common across all backends): ```json { "source_oid": 16384, "lsn": "0/1A3B4C0", "xid": 12345, "action": "INSERT", "columns": {"id": 1, "name": "Alice", "amount": 100.00}, "old_columns": null, "timestamp": "2026-05-30T12:00:00Z" } ``` **Backend interface trait** (in `crates/change-log/`): ```rust #[async_trait] pub trait ChangeLogBackend: Send + Sync { async fn publish(&self, source_oid: u32, events: &[ChangeEvent]) -> Result<()>; async fn consume(&self, source_oid: u32, from_lsn: Lsn) -> Result>; async fn acknowledge(&self, source_oid: u32, up_to_lsn: Lsn) -> Result<()>; async fn lag(&self, source_oid: u32) -> Result; } ``` ### LT-6: Declarative SQL Syntax Implement `CREATE STREAM TABLE` syntax via `ProcessUtility_hook`: ```sql -- Basic usage (mirrors CREATE MATERIALIZED VIEW) CREATE STREAM TABLE my_aggregates AS SELECT category, SUM(amount) as total FROM orders GROUP BY category WITH (schedule = '5s', refresh_mode = 'differential'); -- With explicit options CREATE STREAM TABLE IF NOT EXISTS realtime_dashboard AS SELECT ... WITH ( schedule = '1s', refresh_mode = 'auto', preset = 'real-time', cdc_mode = 'external' ); -- ALTER syntax ALTER STREAM TABLE my_aggregates SET (schedule = '10s'); ALTER STREAM TABLE my_aggregates SET QUERY AS SELECT ...; -- DROP syntax DROP STREAM TABLE my_aggregates; DROP STREAM TABLE IF EXISTS my_aggregates CASCADE; ``` Implementation: - `ProcessUtility_hook` intercepts unrecognized utility statements - Parses `CREATE STREAM TABLE` / `ALTER STREAM TABLE` / `DROP STREAM TABLE` - Translates to corresponding `pgtrickle.*` function calls - Reports errors with standard PostgreSQL error formatting - `pg_dump` support via custom `COMMENT` tags (same as current approach) - Tab-completion in psql via `CREATE STREAM` prefix ### Metrics Adapter for HPA A lightweight sidecar (`pg_trickle_metrics_adapter`) that: - Queries `pgtrickle.pg_stat_pgtrickle` and Prometheus metrics - Exposes them as Kubernetes custom metrics via the Custom Metrics API - Enables HPA to scale workers based on: - `pg_trickle_cdc_lag_p95_ms` (target: 100ms) - `pg_trickle_parallel_queue_depth` (target: 0) - `pg_trickle_worker_cpu_percent` (target: 70%) ### CDC Consumer StatefulSet The `pg_trickle_cdc` binary deployed as a Kubernetes StatefulSet: - One pod per logical replication slot (stable network identity for slot) - Exactly-once delivery to the change log via two-phase acknowledgment - Automatic slot creation/drop on pod start/stop - Graceful handling of PostgreSQL failover (reconnect to new primary) - Liveness probe: slot lag not exceeding threshold - Readiness probe: consuming events (not blocked)