From Batch Processing to Real-Time Analytics with ClickHouse
Moving from batch processing to real-time analytics with ClickHouse means replacing nightly ETL windows with a continuously ingesting, sub-second-query analytics tier. This engineering guide from ChistaDATA covers the architecture, the MergeTree data model, streaming ingestion from Kafka and CDC, the migration path off a batch warehouse, and the telemetry you use to prove the result in production.
- Ingest millions of rows per second while dashboards stay responsive
- Query data seconds after it lands, not the next morning
- Standard SQL on events, logs, metrics, and transactional CDC streams
- Native Kafka, Debezium, and object-storage integrations
- 100% open-source ClickHouse, operated with zero vendor lock-in
Talk to a ChistaDATA ClickHouse Engineer Plan a Batch-to-Real-Time Migration

Batch processing vs real-time analytics: what actually changes
Batch processing collects data over a window (hourly, nightly, weekly), then transforms and loads it in scheduled jobs. The design is optimized for throughput per job, not for time-to-insight. Every downstream consumer, from fraud rules to executive dashboards, inherits the schedule’s latency.
Real-time analytics inverts the contract. Events are ingested continuously, become queryable within seconds, and are served to many concurrent users and applications against a data set that is always current. The workload profile is different in every dimension: many small inserts instead of a few large loads, continuous background merging instead of a maintenance window, and interactive query concurrency instead of a handful of long-running reports.
The engine you pick therefore has to be built for that profile. Bolting streaming onto a batch warehouse produces micro-batches with warehouse-scale cost and warehouse-scale latency. ClickHouse was designed from the start for continuous ingestion and sub-second aggregation over columnar data, which is why it has become the default open-source engine for real-time analytics.
| Dimension | Batch warehouse | Real-time analytics on ClickHouse |
|---|---|---|
| Data freshness | Hours to a day | Seconds |
| Ingestion pattern | Few large loads | Continuous streams and micro-batches |
| Query concurrency | Tens of analysts | Hundreds to thousands of users and APIs |
| Typical p95 latency | Seconds to minutes | Sub-second on pre-aggregated paths |
| Cost driver | Compute reserved for load windows | Compression ratio and merge efficiency |
| Failure surface | Missed load window | Consumer lag, merge backlog, replication lag |
Where batch architectures break down
Latency is a blind spot, not a delay
A fraud rule that runs on last night’s data cannot stop this morning’s transaction. An SRE dashboard refreshed hourly hides a ten-minute outage. The cost of batch is not slowness; it is every decision made without the data that already existed.
Concurrency was never in the design
Batch warehouses assume a small number of heavy queries. Exposing them to customer-facing dashboards or product analytics creates queue depth, admission-control throttling, and unpredictable p99 latency.
Streaming sources arrive anyway
Kafka topics, CDC from PostgreSQL and MySQL, clickstreams, and telemetry are already continuous. Forcing them into hourly loads adds staging layers, duplicate handling, and late-arrival logic that the analytics engine should own natively.
Cost scales with the wrong variable
Reserved compute sized for the nightly peak sits idle the rest of the day. Real-time engines spread work continuously and win on compression, so cost tracks data volume rather than load-window size.
Why ClickHouse is the engine for real-time analytics
ClickHouse’s advantage is not one feature but the combination of four properties that a batch-to-real-time migration needs at the same time. Each maps to a specific internal mechanism you can inspect in system.* tables rather than a marketing claim.
Query performance at scale
Columnar storage in the MergeTree family, a sparse primary index with configurable granularity, vectorized execution, and codec-level compression (LZ4, ZSTD, Delta, DoubleDelta, Gorilla) let aggregations scan billions of rows with a fraction of the I/O. See why ClickHouse is so fast.
Native streaming ingestion
The Kafka table engine, the ClickHouse Kafka Connect sink, asynchronous inserts, and Debezium-based CDC deliver rows to MergeTree parts continuously without an external micro-batch scheduler.
High concurrency for interactive apps
Incremental materialized views, projections, the query cache, and workload-scoped settings profiles keep p95 predictable when hundreds of dashboards hit the same tables.
Open ecosystem, open license
Apache 2.0 licensed, deployable on bare metal, Kubernetes, or any cloud, with S3/object-storage tiering for cold data and first-class drivers for Grafana, Superset, Metabase, and every major language.
Ingest, transform, serve: the real-time analytics pipeline on ClickHouse
1. Ingestion plane
Events land from Kafka (or Redpanda, Kinesis, RabbitMQ), CDC streams from PostgreSQL and MySQL via Debezium, and application inserts over HTTP or native protocol. Target batch sizes of tens of thousands of rows per insert, or enable async_insert so the server coalesces small writes before creating a part.
2. Transformation plane
A raw MergeTree table holds immutable events. Incremental materialized views fan out on insert into SummingMergeTree or AggregatingMergeTree rollups by minute, hour, tenant, or dimension set. Transformations happen once, at write time, not per query.
3. Serving plane
Dashboards, embedded product analytics, alerting, and ML feature reads query the rollups through a proxy layer (chproxy or a load balancer) with per-workload quotas and settings profiles. Cold partitions tier to object storage under TTL rules.
The pipeline is stateless above ClickHouse: replay a Kafka offset range or re-snapshot a CDC source and the same materialized views rebuild the same rollups. That property is what makes the batch-to-real-time cutover reversible, which is the first question ChistaDATA asks in any ClickHouse consulting engagement.
MergeTree data modeling for freshness and query latency
The single most important decision in a real-time ClickHouse deployment is the ORDER BY key of the raw table, because it defines both the sparse primary index and the physical sort order inside each part. Lead with the low-cardinality columns that every query filters on, then the time column. Partition by a coarse time bucket (day or month) so that TTL moves and drops operate on whole partitions and so that insert traffic does not spray parts across many partitions.
The example below is a minimal but production-shaped pattern: a replicated raw events table, an hourly rollup, and the materialized view that keeps the rollup current on every insert. Engine parameters are explicit by ChistaDATA convention; never rely on defaults in customer-facing schemas.
Freshness is then a function of three things you can measure: Kafka consumer lag, the insert-to-part latency reported in system.part_log, and replication delay in system.replicas. Query latency on the serving path is a function of how much the rollup reduces the scanned row count, visible directly in read_rows in system.query_log.
-- Raw immutable events (ClickHouse 24.x+ syntax)
CREATE TABLE analytics.events_raw
(
event_time DateTime64(3, 'UTC'),
tenant_id UInt32,
event_type LowCardinality(String),
user_id UInt64,
amount Decimal(18, 4),
attrs Map(String, String)
)
ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/analytics/events_raw',
'{replica}')
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (tenant_id, event_type, event_time)
TTL toDateTime(event_time) + INTERVAL 90 DAY
TO VOLUME 'cold'
SETTINGS index_granularity = 8192;
-- Hourly rollup served to dashboards
CREATE TABLE analytics.events_1h
(
hour DateTime('UTC'),
tenant_id UInt32,
event_type LowCardinality(String),
events UInt64,
amount_sum Decimal(18, 4),
users AggregateFunction(uniq, UInt64)
)
ENGINE = ReplicatedAggregatingMergeTree(
'/clickhouse/tables/{shard}/analytics/events_1h',
'{replica}')
PARTITION BY toYYYYMM(hour)
ORDER BY (tenant_id, event_type, hour);
-- Maintained on every insert into events_raw
CREATE MATERIALIZED VIEW analytics.mv_events_1h
TO analytics.events_1h
AS
SELECT
toStartOfHour(event_time) AS hour,
tenant_id,
event_type,
count() AS events,
sum(amount) AS amount_sum,
uniqState(user_id) AS users
FROM analytics.events_raw
GROUP BY hour, tenant_id, event_type;Migrating from a batch warehouse to real-time analytics on ClickHouse
ChistaDATA runs batch-to-real-time migrations as staged, reversible programs. The source system keeps serving until ClickHouse has proven parity on both data and latency, and every phase has an explicit rollback: stop the read cutover and the batch warehouse is still authoritative.
Inventory queries, SLAs, and consumers. Classify workloads by required freshness and concurrency. Identify which streams (Kafka, CDC, files) already exist and which batch jobs must become streams.
Design sort keys, partitions, rollups, and TTL tiers from the actual query inventory. Size shards, replicas, and ClickHouse Keeper. Define the RPO/RTO and the replication topology before the first byte is loaded.
Attach ClickHouse as a second consumer of the same Kafka topics or a Debezium CDC stream. Backfill history from the warehouse in partition-sized batches. Nothing downstream changes yet.
Run parity checks (row counts, sums, distinct counts by partition) and latency comparisons against production query mixes. Load-test concurrency with the real dashboard fleet.
Move consumers workload by workload, starting with the highest-freshness need. Keep the batch warehouse warm until the last consumer moves and one full reporting cycle passes clean.
SLOs, error budgets, runbooks, quarterly restore and failover drills, and capacity planning under ChistaDATA managed services.
| Strategy | Best fit | Trade-off to plan for |
|---|---|---|
| Dual-write from the application | Greenfield event producers you control | Consistency between writes; prefer a broker in between |
| Kafka fan-out to ClickHouse | Existing event bus with topic history | Exactly-once needs Kafka Connect sink or idempotent keys |
| CDC via Debezium | OLTP tables in PostgreSQL / MySQL | Updates and deletes need ReplacingMergeTree with FINAL or dedup-at-read patterns |
| Gradual read migration | Large BI estates with many dashboards | Semantic-layer differences (SQL dialect, NULL handling, time zones) |
Deep dives: real-time stream processing with Kafka and ClickHouse · streaming data from PostgreSQL to ClickHouse · configuring asynchronous inserts · ClickHouse migration services.
Proving the outcome: the telemetry that defines “real-time”
A migration is finished when the numbers say so. ChistaDATA reports three metrics for every real-time analytics engagement, each sourced from a ClickHouse system table rather than an estimate.
End-to-end freshness. The gap between event timestamp and the moment the row is visible to SELECT. Instrument by comparing now() to max(event_time) on the serving table, and correlate with Kafka consumer lag and system.part_log insert events.
Serving latency. p95 and p99 of query_duration_ms from system.query_log per dashboard or API user, together with read_rows and read_bytes to show how much the data model reduced scan volume. See the p99 latency playbook.
Ingestion health. Parts per partition and merge backlog from system.parts and system.merges; too-many-parts errors are the classic symptom of under-batched inserts. Replication delay from system.replicas bounds the freshness of read replicas.
Illustrative only: a well-modeled hourly rollup typically reduces read_rows by two to four orders of magnitude versus scanning raw events, which is where sub-second p95 comes from. Actual ratios depend on cardinality and query shape; ChistaDATA measures them per workload during validation.
-- Serving latency by dashboard user, last 24 h
SELECT
user,
count() AS queries,
quantile(0.95)(query_duration_ms) AS p95_ms,
quantile(0.99)(query_duration_ms) AS p99_ms,
avg(read_rows) AS avg_read_rows
FROM system.query_log
WHERE type = 'QueryFinish'
AND event_time >= now() - INTERVAL 24 HOUR
AND query_kind = 'Select'
GROUP BY user
ORDER BY p99_ms DESC;
-- Ingestion health: parts per active partition
SELECT
database,
table,
partition,
count() AS active_parts,
sum(rows) AS rows,
max(modification_time) AS last_part_written
FROM system.parts
WHERE active
AND database = 'analytics'
GROUP BY database, table, partition
ORDER BY active_parts DESC
LIMIT 20;
-- Freshness: how far behind is the serving table?
SELECT
now() - max(hour) AS freshness_lag_seconds
FROM analytics.events_1h;Where batch-to-real-time delivers the most
Fraud, risk, and AML
Score transactions against rolling windows of behavior while the transaction is still in flight. ClickHouse holds months of history at full granularity and answers window aggregations in milliseconds. Read: real-time AML on ClickHouse.
Observability and log analytics
Replace hourly log rollups and expensive Elasticsearch clusters with a single MergeTree tier: high-cardinality metrics, traces, and logs queried together. Read: Elasticsearch to ClickHouse for observability.
Product and customer analytics
Embedded, customer-facing dashboards with per-tenant isolation, funnels, retention cohorts, and A/B results updated as sessions happen, at concurrency levels a batch warehouse cannot serve.
Ad-tech, gaming, and IoT telemetry
Billions of events per day from bidding, live game sessions, or sensor fleets, with sub-second rollups for monetization, matchmaking, and anomaly alerting.
How ChistaDATA delivers batch-to-real-time on ClickHouse
ChistaDATA is a full-stack ClickHouse infrastructure operations company: consulting, 24×7×365 consultative support, platform engineering, and managed services on 100% open-source ClickHouse. Every real-time analytics engagement is led by principal ClickHouse engineers and documented in decision-grade written deliverables.
The portfolio covers strategic ClickHouse consulting, 24×7 enterprise support, fully managed ClickHouse services, migration engineering from Redshift, Snowflake, BigQuery, Druid, Pinot, Vertica, Teradata, Hadoop, and Elasticsearch, performance audits, performance tuning, ClickHouse Cloud and DBaaS cost optimization, and Data SRE.
Support is governed by an enterprise SLA: Severity 1 response in 15 minutes, Severity 2 in 12 hours, Severity 3 in 24 hours, Severity 4 in 48 hours. Standing caveat for every recommendation on this page: test before applying to production, and maintain a robust, drilled disaster-recovery posture.
Assessment and architecture
Query inventory, freshness classification, sort-key and partition design, shard/replica/Keeper sizing, RPO/RTO definition, migration roadmap with rollback at every phase.
Proof of concept on your data
Representative workloads, real dashboard concurrency, parity and latency reports from system.query_log, and a cost model based on measured compression.
Production operations
24×7 monitoring, SLOs and error budgets, runbook automation, upgrade management, quarterly restore and failover drills, capacity planning.
ChistaDATA engineering resources on real-time analytics
Frequently asked questions
What is the difference between batch processing and real-time analytics?
Batch processing collects data over a window and processes it in scheduled jobs, so insight lags the event by hours. Real-time analytics ingests continuously and makes data queryable within seconds, serving many concurrent users against an always-current data set. The change is architectural: different ingestion patterns, different storage engine, different failure modes.
Why use ClickHouse for real-time analytics instead of a cloud data warehouse?
ClickHouse combines columnar MergeTree storage, native Kafka and CDC ingestion, incremental materialized views, and high query concurrency in one open-source engine. Cloud batch warehouses can approximate streaming with micro-batches, but at higher latency and with compute billed for load windows rather than data volume.
How fresh can data be in ClickHouse?
With batched or asynchronous inserts from Kafka, rows are typically queryable within single-digit seconds of the event. The bound is set by insert batching, merge health (system.parts), and replication delay (system.replicas), all of which are measurable and tunable.
Can we migrate from batch to real-time without downtime?
Yes. ChistaDATA runs ClickHouse as a parallel consumer of existing streams, backfills history, validates parity and latency, and moves read workloads incrementally. The batch warehouse stays authoritative until the last consumer has cut over, so rollback is always available.
How does ChistaDATA support real-time ClickHouse platforms in production?
Through 24×7×365 consultative support and managed services with a 15-minute Severity 1 SLA, covering monitoring, performance engineering, replication and ClickHouse Keeper operations, upgrades, DR drills, and capacity planning on 100% open-source ClickHouse.
Move from batch processing to real-time analytics with ClickHouse
Bring your query inventory, your streams, and your freshness targets. ChistaDATA engineers will map a staged, reversible migration and prove the result in your own telemetry.
Book a ClickHouse Architecture Review Explore 24×7 ClickHouse Support Download Corporate Overview (PDF)