How to Design Modern OLAP Architectures with ClickHouse and Apache Druid: A Complete Engineering Guide
Modern OLAP architectures combine ClickHouse for high-throughput batch analytics with Apache Druid for sub-second latency on streaming data, using shared Kafka ingestion pipelines and unified schema registries to handle petabyte-scale workloads.
Designing modern OLAP architectures with ClickHouse and Apache Druid requires understanding their complementary strengths within the DataExpert-io/data-engineer-handbook ecosystem. While both are columnar stores optimized for analytical workloads, ClickHouse excels at complex SQL aggregations across massive datasets, whereas Druid specializes in real-time ingestion with time-series pruning and low-latency filtering. This guide examines their architectural differences, provides a hybrid reference implementation, and includes production-ready Python code samples from the handbook's official repository.
Core Architectural Differences
Understanding the fundamental design philosophies of each engine is critical when designing modern OLAP architectures with ClickHouse and Apache Druid.
ClickHouse Design Philosophy
ClickHouse is a distributed columnar DBMS built on a vectorized execution engine and MergeTree storage architecture. Queries execute in parallel across shards, leveraging SIMD instructions for fast aggregations. Unlike traditional databases, ClickHouse is primary key-less by default, instead using data skipping indexes (min/max, bloom filters) and sorting keys for fast range scans. According to the README.md in the DataExpert-io/data-engineer-handbook repository, ClickHouse serves as the preferred engine for high-performance dashboards and ad-tech analytics.
Apache Druid Design Philosophy
Apache Druid operates as a real-time columnar store that combines a streaming ingestion pipeline with immutable segment files based on Apache Parquet/ORC formats. Druid employs a pluggable query engine supporting both SQL (via Apache Calcite) and native Druid JSON queries. Its architecture relies on multi-dimensional bitmap and inverted indexes, with segment granularity (hour/day) enabling aggressive time-based pruning. The projects.md file demonstrates Druid's practical application for real-time visualization pipelines using Apache Superset.
Ingestion Patterns and Indexing Strategies
Data Ingestion Architectures
Both engines support Kafka as a primary ingestion source, but their approaches differ significantly:
- ClickHouse: Optimized for bulk batch loads (
INSERT, CSV, Parquet) with secondary support for Kafka streams via table engines. Best suited for scenarios where data lands in S3/HDFS before analytical processing. - Apache Druid: Native Kafka and Kinesis real-time ingestion with exactly-once semantics. Batch imports occur via dedicated batch indexing jobs, making Druid ideal for event-driven architectures requiring immediate query availability.
Indexing Mechanisms
The indexing strategies define query performance characteristics:
- ClickHouse: Uses sparse primary indexes and secondary data skipping indexes. Data is physically sorted by the
ORDER BYclause defined in theMergeTreeengine configuration, enabling fast range scans without heavy memory overhead. - Druid: Automatically builds multi-dimensional bitmap indexes at ingestion time. The segment-based architecture creates inverted indexes for all dimensions, enabling microsecond-level filtering on high-cardinality columns.
Reference Architecture for Hybrid OLAP
The DataExpert-io/data-engineer-handbook recommends a dual-engine architecture that couples both stores in a single analytics platform, allowing teams to select the optimal engine per query pattern.
+-------------------+ +-------------------+
| Streaming Data | | Batch Data Lake |
| (Kafka / Kinesis) | | (S3 / HDFS) |
+--------+----------+ +--------+----------+
| |
+--------v----------+ +---------v-----------+
| Ingestion Layer | | Ingestion Layer |
| (Flink / Spark) | | (Spark / DBT) |
+--------+----------+ +----------+----------+
| |
+--------v----------+ +----------v----------+
| Real‑time store | | Columnar store |
| (Apache Druid) | | (ClickHouse) |
+--------+----------+ +----------+----------+
| |
+--------v----------+ +----------v----------+
| Query Engine | | Query Engine |
| (SQL / Druid JSON) | | (SQL) |
+--------+----------+ +----------+----------+
| |
+--------v----------+ +----------v----------+
| BI / Dashboard | | BI / Dashboard |
| (Superset, Tableau)| | (Metabase, PowerBI)|
+-------------------+ +--------------------+
Key Design Decisions
When implementing this architecture from the intermediate-bootcamp/materials/6-data-impact-training/README.md patterns, adhere to these principles:
-
Separate workloads – Use Druid for low-latency, high-cardinality, time-series queries (e.g., "active users per minute") and ClickHouse for heavy-aggregation, ad-hoc analytical queries (e.g., "monthly churn by segment").
-
Unified data model – Store raw events in an immutable lake (S3/HDFS). Both engines ingest from the same source, preserving a single source of truth as outlined in the handbook's pipeline guidelines.
-
Stream-first ingestion – Deploy a Flink job that fans out each event to both Kafka topics: one consumed by Druid's real-time indexing service, the other by a batch pipeline that writes to ClickHouse. The
intermediate-bootcamp/materials/3-spark-fundamentals/docker-compose.yamlprovides boilerplate infrastructure for testing this dual-stream pattern. -
Metadata governance – Leverage a schema registry (e.g., Confluent Schema Registry) to keep column definitions synchronized across both stores, preventing schema drift between the real-time and batch paths.
-
Caching & materialized views – Use ClickHouse materialized views for pre-aggregated tables; Druid automatically materializes segment-level indexes during the ingestion phase.
Implementation Guide
The following snippets illustrate a minimal Python-based pipeline that writes the same JSON event to both stores, adapted from the patterns in intermediate-bootcamp/materials/5-kpis-and-experimentation/README.md.
Setting Up Client Libraries
pip install clickhouse-driver pydruid
Writing to ClickHouse
from clickhouse_driver import Client
# Connect to ClickHouse
ch = Client(host='clickhouse.mycompany.com', port=9000,
user='default', password='')
# Create a simple table (run once)
ch.execute('''
CREATE TABLE IF NOT EXISTS analytics.events (
event_time DateTime,
user_id UInt64,
event_type String,
value Float64
) ENGINE = MergeTree()
ORDER BY (event_time, user_id);
''')
def write_clickhouse(event):
ch.execute(
'INSERT INTO analytics.events (event_time, user_id, event_type, value) VALUES',
[(event['timestamp'], event['user_id'],
event['type'], event['value'])]
)
Indexing into Apache Druid
from pydruid.client import PyDruid
from pydruid.ingestion import IndexSpec
# Druid endpoint
druid = PyDruid('http://druid.mycompany.com:8082', 'druid/v2')
def write_druid(event):
# Build a simple ingestion spec (batch example)
spec = IndexSpec(
dataSource='events',
timestampSpec={'column': 'timestamp', 'format': 'iso'},
dimensionsSpec={'dimensions': ['user_id', 'event_type']},
metricsSpec=[{'type': 'doubleSum', 'name': 'value', 'fieldName': 'value'}],
granularitySpec={'type': 'uniform', 'segmentGranularity': 'hour'}
)
# Post the spec – Druid will ingest the data (normally via a Kafka task)
druid.post_index(spec.to_dict())
Unified Producer Pattern
import json
import time
def produce(event):
# Write to both stores
write_clickhouse(event)
write_druid(event)
# Example event
sample = {
"timestamp": "2024-08-12T15:30:00Z",
"user_id": 12345,
"type": "click",
"value": 1.0
}
produce(sample)
Note: In production, replace the direct Druid post_index call with a Kafka ingestion task that continuously streams events, as recommended in the projects.md implementation examples.
Repository Structure and Resources
The DataExpert-io/data-engineer-handbook repository contains specific resources for implementing this architecture:
-
README.md– Lists ClickHouse and Druid under Modern OLAP and provides the official mention of both engines as part of the handbook's tech stack. -
projects.md– Contains example projects that ingest data into Druid and visualize with Superset, demonstrating practical pipeline construction using Druid as the OLAP layer. -
intermediate-bootcamp/materials/3-spark-fundamentals/docker-compose.yaml– Provides boilerplate configuration for running Spark, Kafka, and other services required for local testing of the dual-engine pipeline. -
intermediate-bootcamp/materials/6-data-impact-training/README.md– Walks through data pipeline assembly that can be extended to include ClickHouse and Druid ingestion patterns. -
intermediate-bootcamp/materials/5-kpis-and-experimentation/README.md– Demonstrates SQL patterns for KPI calculations that map directly to ClickHouse query optimization strategies.
Summary
- ClickHouse provides superior throughput for complex SQL aggregations and ad-hoc analytics using vectorized execution and MergeTree storage engines.
- Apache Druid delivers sub-second query latency for streaming data through bitmap indexing and time-based segment pruning.
- Hybrid architectures leverage both engines simultaneously, routing real-time dashboards to Druid and heavy analytical workloads to ClickHouse.
- Unified ingestion via Kafka and Flink ensures data consistency across both platforms while maintaining a single source of truth in your data lake.
- Schema registries and materialized views are essential for maintaining performance and consistency in dual-engine OLAP systems.
Frequently Asked Questions
When should I choose ClickHouse over Druid for my OLAP workload?
Choose ClickHouse when your workload involves complex SQL queries with heavy aggregations, joins, and sub-queries across large historical datasets. ClickHouse excels at batch analytics where query flexibility and ANSI-SQL compatibility are priorities. Choose Druid when you need sub-second latency on streaming data with high-cardinality filtering and time-series analysis, particularly for real-time monitoring dashboards and anomaly detection systems.
How do I handle schema evolution in a dual-engine OLAP architecture?
Implement a Confluent Schema Registry or similar metadata governance layer that both engines reference during ingestion. Store raw events in an immutable format (Parquet/ORC) in S3 or HDFS as your single source of truth. When schema changes occur, update the registry first, then modify ingestion specifications in both ClickHouse and Druid to accommodate new fields while maintaining backward compatibility for existing queries.
What hardware considerations apply when running both ClickHouse and Druid together?
Druid is memory-heavy due to its reliance on bitmap indexes and in-memory caching for real-time segments; allocate nodes with high RAM (256GB+) for historical and real-time services. ClickHouse is disk-optimized and benefits from fast SSD storage for MergeTree data parts, though it can run efficiently on commodity hardware with less memory. For cost efficiency, store hot recent data in Druid (high-memory instances) and archive older data to ClickHouse (disk-optimized instances).
Can I use ClickHouse alone instead of Druid for real-time streaming analytics?
While ClickHouse supports Kafka ingestion via the Kafka table engine and materialized views, it lacks Druid's native real-time indexing service and automatic time-based segment pruning. For true real-time OLAP requiring sub-second latency on high-cardinality dimensions, Druid's architecture is optimized specifically for this use case. However, for near-real-time analytics (seconds to minutes delay) with simpler filtering requirements, ClickHouse alone may suffice and reduce operational complexity.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →