Overview
Strait integrates with ClickHouse as an optional, append-only analytics store optimized for high-volume time-series queries. ClickHouse is a column-oriented OLAP database that excels at aggregating millions of rows in sub-second latency, making it well suited for run metrics, cost breakdowns, and event-level observability. PostgreSQL remains the source of truth for all operational data. ClickHouse is a read-optimized replica that receives data via an asynchronous export pipeline. Disabling ClickHouse has zero impact on workflow execution, scheduling, or run management. The engine continues to function normally using Postgres alone.Why ClickHouse?
Tables and Schema
Strait creates three ClickHouse tables on startup via idempotentCREATE TABLE IF NOT EXISTS statements.
run_events
Stores discrete lifecycle events emitted during run execution (e.g., step started, step failed, checkpoint saved). Used for event-level drill-down and log analytics.
- Engine:
MergeTree() - Partition key:
toDate(inserted_at)(daily partitions) - Order key:
(project_id, run_id, created_at) - TTL: 90 days from
inserted_at
run_analytics
One row per run attempt. Captures duration, queue wait, cost, and execution metadata for dashboard-level reporting.
- Engine:
MergeTree() - Partition key:
toDate(inserted_at)(daily partitions) - Order key:
(project_id, job_id, created_at) - TTL: 365 days from
inserted_at
compute_usage
Tracks individual machine sessions for granular compute billing and utilization analysis.
- Engine:
MergeTree() - Partition key:
toDate(inserted_at)(daily partitions) - Order key:
(project_id, started_at) - TTL: 365 days from
inserted_at
Data Pipeline
Data flows from PostgreSQL to ClickHouse through an asynchronous batch exporter that runs as a background goroutine within the Strait process.Export flow
-
Enqueue. When the engine completes a state transition (run started, run finished, cost recorded), it calls
Exporter.Enqueue()to place the record in an in-memory buffer. This call is non-blocking and safe for concurrent use. -
Buffer accumulation. Records accumulate in memory until one of two flush triggers fires:
- Batch size threshold — the buffer reaches
CLICKHOUSE_BATCH_SIZErecords (default: 1000). - Flush interval —
CLICKHOUSE_FLUSH_INTERVALelapses since the last flush (default: 5 seconds).
- Batch size threshold — the buffer reaches
-
Batch flush. The exporter swaps the buffer, releasing the lock immediately, then writes the batch to ClickHouse via a single
INSERTstatement. - Graceful shutdown. On process termination, the exporter performs a final flush to drain any remaining buffered records before closing.
Backpressure
If ClickHouse becomes unavailable or slow, the buffer grows up to 10x the configured batch size. Beyond that limit, the exporter drops the oldest records and logs a warning. This ensures the engine never blocks on analytics writes.Record types
Each enqueued record is dispatched to its corresponding table based on its Go type:Query Patterns
ClickHouse tables are designed around common analytics access patterns. All tables are ordered withproject_id as the leading key, so project-scoped queries benefit from primary key pruning.
Time-windowed aggregations
Cost analysis by preset
Event drill-down for a single run
Queue wait percentiles
Configuration
All ClickHouse settings are controlled via environment variables. Both the client connection and the export pipeline must be enabled independently.
Setting
CLICKHOUSE_ENABLED=true without providing CLICKHOUSE_URL is a configuration error and will prevent the application from starting.
Operational Considerations
ClickHouse is optional
The entire ClickHouse subsystem is gated behind feature flags. When disabled:- The
Clientconstructor returnsnil, and all methods on a nil client are safe no-ops. - The
Exporterconstructor returnsnil, andEnqueue()silently returnsfalse. - No ClickHouse driver is loaded, no connections are opened, and no goroutines are spawned.
Data retention and TTL
ClickHouse enforces TTL at the partition level:run_events: 90-day retention. High-volume event data is expired first to manage storage.run_analytics: 365-day retention. Run summaries are kept for year-over-year reporting.compute_usage: 365-day retention. Billing records are retained for annual cost analysis.
Connection pool
The client maintains a connection pool with sensible defaults:- Max open connections: 10
- Max idle connections: 5
- Connection max lifetime: 30 minutes
MaxOpenConns via the Config struct when constructing the client programmatically.
Performance characteristics
- Write path: Fully asynchronous. The engine never blocks on ClickHouse writes. Failed flushes are logged and the batch is discarded to prevent memory growth.
- Read path: Queries benefit from ClickHouse columnar compression and the
MergeTreeprimary index. Project-scoped queries with time-range filters are the most efficient access pattern. - LowCardinality optimization: Columns with a small set of distinct values (
event_type,status,machine_preset,triggered_by,level,execution_mode) useLowCardinality(String), which applies dictionary encoding for reduced storage and fasterGROUP BY.