# ============================================================================= # switch-pipeline configuration: the single place every setting lives. # # cp .env.example .env (or `make env`), then edit .env (git-ignored) # # src/switch_pipeline/settings.py declares and validates these variables but # holds no values; docker-compose.yml interpolates them from here. Every # service fails fast at startup, naming any variable that is missing/invalid. # Data-quality rules live in one place too: config/quality_rules.yaml. # ============================================================================= # --- Docker Compose ---------------------------------------------------------- COMPOSE_PROJECT_NAME=switch # Optional services, comma-separated: "emulator" (local Snowflake emulator), # "ui" (Redpanda Console on REDPANDA_CONSOLE_HOST_PORT). COMPOSE_PROFILES= # Pinned images (the application image is built from PYTHON_IMAGE + UV_IMAGE). PYTHON_IMAGE=python:3.12.14-slim-trixie UV_IMAGE=ghcr.io/astral-sh/uv:0.12.18 REDPANDA_IMAGE=redpandadata/redpanda:v26.2.3 REDPANDA_CONSOLE_IMAGE=redpandadata/console:v3.12.0 POSTGRES_IMAGE=postgres:18.6-alpine SNOWFLAKE_EMULATOR_IMAGE=ghcr.io/tekumara/fakesnow:0.11.18 # Ports published on your machine; change them if they clash with local services. API_HOST_PORT=8000 POSTGRES_HOST_PORT=55432 KAFKA_HOST_PORT=19092 REDPANDA_CONSOLE_HOST_PORT=8080 # A worker container is reported unhealthy after this long without a heartbeat. HEALTHCHECK_MAX_AGE_SECONDS=90 # Time the adapter and consumer get to finish the batch in flight when stopped, # before Docker kills them. Keep it above KAFKA_DELIVERY_TIMEOUT_MS / 1000. WORKER_STOP_GRACE_SECONDS=45 # --- Snowflake connection ---------------------------------------------------- # Account identifier - (Snowsight: Admin > Accounts). SNOWFLAKE_ACCOUNT= SNOWFLAKE_USER=SWITCH_PIPELINE # Authentication: set exactly ONE of (a) or (b). # (a) Key pair (JWT), required for TYPE=SERVICE users; `make snowflake-keypair` # creates ./secrets/snowflake_rsa_key.p8, mounted read-only at /run/secrets. SNOWFLAKE_PRIVATE_KEY_PATH=/run/secrets/snowflake_rsa_key.p8 SNOWFLAKE_PRIVATE_KEY_PASSPHRASE= # (b) Password or programmatic access token (PAT). SNOWFLAKE_PASSWORD= SNOWFLAKE_ROLE=SWITCH_PIPELINE_ROLE SNOWFLAKE_WAREHOUSE=SWITCH_WH SNOWFLAKE_DATABASE=SWITCH_DEMO SNOWFLAKE_SCHEMA=RAW SNOWFLAKE_LOGIN_TIMEOUT_SECONDS=60 SNOWFLAKE_NETWORK_TIMEOUT_SECONDS=120 # Leave empty for Snowflake; only the local emulator block below sets these. SNOWFLAKE_HOST= SNOWFLAKE_PORT= SNOWFLAKE_PROTOCOL= # --- Source table contract --------------------------------------------------- # The adapter can read any table with a unique key, a version that increases on # every write, and a UTC modified-timestamp column that writers maintain. SOURCE_TABLE=CUSTOMER_ORDERS SOURCE_ENTITY_TYPE=order SOURCE_KEY_COLUMN=O_ORDERKEY SOURCE_VERSION_COLUMN=ROW_VERSION SOURCE_UPDATED_AT_COLUMN=UPDATED_AT # --- Seeding and change simulation (make seed / make simulate) --------------- # sample_share: ORDERS joined with CUSTOMER from SEED_SAMPLE_SCHEMA # synthetic: TPC-H-shaped rows generated inside the warehouse (no share needed) SEED_STRATEGY=sample_share SEED_SAMPLE_SCHEMA=SNOWFLAKE_SAMPLE_DATA.TPCH_SF1 SEED_ROW_COUNT=20000 SIMULATE_INSERTS=20 SIMULATE_UPDATES=30 # Rows that deliberately break quality rules, to show quarantine in action. SIMULATE_INVALID_ROWS=3 # --- Adapter: Snowflake -> Kafka --------------------------------------------- ADAPTER_BATCH_SIZE=2000 ADAPTER_POLL_INTERVAL_SECONDS=10 # Rows modified in the last N seconds wait for the next cycle, so writer # transactions still in flight can commit. Must exceed the longest source write. ADAPTER_SETTLE_SECONDS=10 ADAPTER_RETRY_BACKOFF_INITIAL_SECONDS=2 ADAPTER_RETRY_BACKOFF_MAX_SECONDS=60 ADAPTER_HEARTBEAT_PATH=/tmp/switch-adapter.heartbeat # --- Kafka (Redpanda) -------------------------------------------------------- # The compose broker listens on redpanda:9092 inside the network (and on # localhost:KAFKA_HOST_PORT from your machine). KAFKA_BOOTSTRAP_SERVERS=redpanda:9092 KAFKA_TOPIC=switch.snowflake.customer_orders.v1 KAFKA_TOPIC_PARTITIONS=6 KAFKA_TOPIC_REPLICATION_FACTOR=1 # -1 keeps events forever: the topic is the log the sink is rebuilt from. KAFKA_TOPIC_RETENTION_MS=-1 KAFKA_CONSUMER_GROUP=switch-materializer # How long the producer retries a message internally before reporting failure. KAFKA_DELIVERY_TIMEOUT_MS=30000 # Re-sends of undelivered events before the batch fails (watermark not advanced). KAFKA_PUBLISH_MAX_ATTEMPTS=5 KAFKA_PUBLISH_BACKOFF_INITIAL_SECONDS=1 KAFKA_PUBLISH_BACKOFF_MAX_SECONDS=30 KAFKA_ADMIN_TIMEOUT_SECONDS=10 # --- Consumer: Kafka -> PostgreSQL ------------------------------------------- CONSUMER_BATCH_SIZE=500 CONSUMER_POLL_TIMEOUT_SECONDS=1.0 CONSUMER_DB_MAX_ATTEMPTS=5 CONSUMER_DB_BACKOFF_INITIAL_SECONDS=1 CONSUMER_DB_BACKOFF_MAX_SECONDS=20 CONSUMER_HEARTBEAT_PATH=/tmp/switch-consumer.heartbeat # Data-quality rules; ./config is mounted into the containers at /app/config. QUALITY_RULES_PATH=/app/config/quality_rules.yaml # --- PostgreSQL -------------------------------------------------------------- POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_DB=switch POSTGRES_USER=switch # Local development only; production injects this from a secret manager. POSTGRES_PASSWORD=switch-local-dev # Longest wait for a new connection, and for a free one from a pool. POSTGRES_CONNECT_TIMEOUT_SECONDS=5 POSTGRES_POOL_MIN_SIZE=1 POSTGRES_POOL_MAX_SIZE=5 # --- Event inspection API ---------------------------------------------------- API_HOST=0.0.0.0 API_PORT=8000 API_PAGE_SIZE_DEFAULT=50 API_PAGE_SIZE_MAX=500 API_ENTITY_HISTORY_LIMIT=100 # /stats computes lag percentiles over this many most recent events. API_LAG_SAMPLE_SIZE=1000 # When set (16+ characters), every endpoint except /healthz and /readyz requires # "Authorization: Bearer ". Leave empty only for local development. API_AUTH_TOKEN= # --- Logging ----------------------------------------------------------------- LOG_LEVEL=INFO # json (one object per line, for log shippers) or console (human-friendly) LOG_FORMAT=json # --- Local Snowflake emulator (optional: no Snowflake account needed) -------- # Uncomment this block to run everything against fakesnow, an open-source # Snowflake emulator backed by DuckDB. These lines override the values above. # It is a test double for development and CI, not a Snowflake replacement. # COMPOSE_PROFILES=emulator # SNOWFLAKE_ACCOUNT=fakesnow # SNOWFLAKE_USER=fake # SNOWFLAKE_PASSWORD=snow # SNOWFLAKE_PRIVATE_KEY_PATH= # SNOWFLAKE_HOST=snowflake-emulator # SNOWFLAKE_PORT=64616 # SNOWFLAKE_PROTOCOL=http # SEED_STRATEGY=synthetic