# How to Design Modern OLAP Architectures with ClickHouse and Apache Druid: A Complete Engineering Guide

> Design modern OLAP architectures with ClickHouse and Apache Druid. Learn to combine batch and streaming analytics for petabyte-scale data processing using Kafka and schema registries.

- Repository: [DataExpert.io/data-engineer-handbook](https://github.com/DataExpert-io/data-engineer-handbook)
- Tags: architecture
- Published: 2026-08-12

---

**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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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 BY` clause defined in the `MergeTree` engine 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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/intermediate-bootcamp/materials/6-data-impact-training/README.md) patterns, adhere to these principles:

1. **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").

2. **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.

3. **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.yaml`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/intermediate-bootcamp/materials/3-spark-fundamentals/docker-compose.yaml) provides boilerplate infrastructure for testing this dual-stream pattern.

4. **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.

5. **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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/intermediate-bootcamp/materials/5-kpis-and-experimentation/README.md).

### Setting Up Client Libraries

```bash
pip install clickhouse-driver pydruid

```

### Writing to ClickHouse

```python
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

```python
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

```python
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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/projects.md) implementation examples.

## Repository Structure and Resources

The DataExpert-io/data-engineer-handbook repository contains specific resources for implementing this architecture:

- **[`README.md`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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`](https://github.com/DataExpert-io/data-engineer-handbook/blob/main/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.