feat(go-services): Phase 2 foundation — isolated shadow DB + schema

Stands up the shadow-ingest substrate without touching production:
- schema.go: faithful replica of db_async.init_db_async (idempotent DDL),
  run only when an instance OWNS its DB (READ_ONLY=false). Fixes for a fresh DB:
  spawn_events has no sole-id PK (so it can be a hypertable), telemetry_events
  compression is enabled before its policy, and the portal unique index uses
  ROUND(..,1) to match main.py's ON CONFLICT. 35/35 statements OK.
- store.go: read-only transaction enforcement is now conditional (on for
  production read parity, off for ingest).
- main.go: READ_ONLY + SHADOW_INGEST_WS config; schema init on boot when owning
  the DB.
- compose override: a SEPARATE TimescaleDB `dereth-go-db` (isolated volume,
  127.0.0.1:5434) and a `dereth-tracker-go-shadow` instance (image reused via
  dereth-tracker-go:local) that owns it. Production DB never written.

Verified: dereth_go has all 13 tables; telemetry_events + spawn_events are
hypertables; the read-side instance still serves production read-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-24 10:18:30 +02:00
parent b6d2871cf0
commit 6a839e69bc
4 changed files with 277 additions and 12 deletions

View file

@ -22,6 +22,7 @@ services:
context: ./go-services/tracker-go
args:
BUILD_VERSION: ${BUILD_VERSION:-dev}
image: dereth-tracker-go:local
container_name: dereth-tracker-go
ports:
- "127.0.0.1:8770:8770"
@ -72,3 +73,53 @@ services:
options:
max-size: "10m"
max-file: "3"
# ---- Phase 2: shadow ingest (fully isolated; production never touched) ----
# A SEPARATE TimescaleDB the Go tracker owns for shadow ingest. Isolated
# volume + loopback port; the production dereth DB is never written.
dereth-go-db:
image: timescale/timescaledb:2.19.3-pg14
container_name: dereth-go-db
ports:
- "127.0.0.1:5434:5432"
environment:
POSTGRES_DB: "dereth_go"
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
volumes:
- dereth-go-data:/var/lib/postgresql/data
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Shadow tracker instance: same image, but OWNS dereth-go-db (read-write) and
# (once ingest lands) consumes the Python /ws/live firehose into it, so its
# ingest output can be compared against production without writing to it.
dereth-tracker-go-shadow:
image: dereth-tracker-go:local
container_name: dereth-tracker-go-shadow
ports:
- "127.0.0.1:8771:8771"
environment:
PORT: "8771"
DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD}@dereth-go-db:5432/dereth_go"
READ_ONLY: "false" # owns its DB; creates schema on boot
INVENTORY_SERVICE_URL: "http://inventory-service:8000"
SECRET_KEY: "${SECRET_KEY}"
# SHADOW_INGEST_WS: "ws://dereth-tracker:8765/ws/live" # enabled once ingest handlers land
LOG_LEVEL: "INFO"
depends_on:
- dereth-go-db
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
dereth-go-data: