Real-Time Analytics Stack 2026 — ClickHouse, Druid, Pinot

Photo: RDNE Stock project / Pexels
A real-time analytics stack is the set of tools that lets a dashboard, an API, or an alert reflect what happened seconds ago instead of yesterday. It usually has three layers: a log that carries events (often Apache Kafka), a processor that cleans and aggregates them (often Apache Flink), and a columnar database built to answer aggregate queries fast (ClickHouse, Apache Druid, or Apache Pinot). This guide explains what each layer does, how the three databases differ, where the sharp edges are, and how to decide whether you need any of it.
What "real-time" actually means
The phrase hides three different requirements, and mixing them up is the most common way teams overbuild. They are separate problems with separate tools.
Data freshness is how old the newest row is when a query runs. A dashboard whose newest data is 20 seconds old has 20 seconds of freshness lag, regardless of how fast the query itself is. Query latency is how long the database takes to answer once you ask. Concurrency is how many people or programs ask at the same time. A cloud warehouse can have excellent latency for one analyst and still struggle when thousands of customers load the same embedded dashboard at once.
Think of a restaurant. Freshness is how recently the kitchen restocked, latency is how quickly a cook plates an order, and concurrency is how many tables order at once. A kitchen can be well stocked and fast for one table and still collapse on a Saturday night.
| Requirement | Question it answers | Typical fix |
|---|---|---|
| Freshness | How old is the newest row? | Streaming ingestion (Kafka plus a streaming consumer) |
| Query latency | How long does one query take? | Columnar storage, indexes, pre-aggregation |
| Concurrency | How many queries run at once? | A serving database designed for many small queries |
| Event reaction | Can something trigger when an event arrives? | Stream processing or alerting rules |
The fourth row, reacting to events, is a different job again. Fraud scoring during a card transaction is a decision, not a chart. It belongs in a stream processor or a service, with the analytics database receiving the results afterward.
Most teams that say "we need real-time" actually mean "fresh enough that nobody notices a delay." That target is often one to five minutes, and it changes the architecture a great deal, because micro-batch loads into an ordinary warehouse can meet it.
When you need real-time analytics and when you do not
The test is simple: does a person or system make a decision, or take an action, that is worse if the data is an hour old? If yes, the extra machinery earns its keep. If no, it is cost without a payoff.
| Real-time tends to pay off | A warehouse and a schedule are usually enough |
|---|---|
| Dashboards embedded in a product that customers see | Monthly or weekly executive reports |
| Operational monitoring of systems and alerts | Exploratory analysis by data scientists |
| Fraud and abuse detection during a transaction | Finance close and audit reporting |
| Personalization based on current session behavior | Historical trend analysis |
| Live leaderboards, feeds, and counters | Model training on past data |
If your dashboard is looked at once a day, a nightly load into a warehouse is a perfectly good design. Our comparison of Snowflake, BigQuery, and Redshift covers that side of the decision, and the best data science tools roundup places both worlds in the wider stack.
There is also a middle path. If your data fits on one machine and the work is exploratory, an embedded engine such as DuckDB can answer questions quickly with no streaming infrastructure at all. The DuckDB explainer covers where that approach stops working.
The log at the center: Apache Kafka
Nearly every modern streaming design starts with a durable log. Kafka was created at LinkedIn by Jay Kreps, Neha Narkhede, and Jun Rao, open-sourced in early 2011, and graduated from the Apache Incubator on October 23, 2012, according to its Wikipedia history.
The core idea is an append-only log. Producers write events to named topics. Each topic is split into partitions, and Kafka guarantees ordering within a partition, not across the whole topic. Consumers read at their own pace by tracking an offset, which is just a position in the log. Because the log is retained rather than deleted on read, several independent consumers can read the same events, and a new consumer can start from the beginning.
An analogy helps. A traditional message queue is a mailbox: once someone takes a letter, it is gone. Kafka is a town ledger bolted to a table. Anyone can walk up and read from any page, and reading does not remove the entry.
# Producer (illustrative, using the kafka-python client)
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers='kafka:9092',
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
producer.send('user_events', {
'user_id': 12345,
'event_type': 'click',
'timestamp': '2026-05-14T10:00:00Z'
})
# Consumer
from kafka import KafkaConsumer
consumer = KafkaConsumer(
'user_events',
bootstrap_servers='kafka:9092',
value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)
for message in consumer:
process(message.value)
What "exactly once" does and does not promise
Duplicates are the quiet enemy of analytics. If a producer retries after a timeout and the first attempt actually succeeded, the same click is counted twice. Kafka added exactly-once semantics in release 0.11, according to Confluent's engineering write-up.
Two mechanisms do the work. The idempotent producer (enable.idempotence=true) attaches sequence numbers so the broker can recognize and drop a repeated batch within a partition. Transactions let a producer write to several partitions atomically and commit consumer offsets in the same step, with consumers choosing read_committed or read_uncommitted through isolation.level.
The scope matters. Confluent states that the guarantee for Kafka Streams applies to Kafka Streams' internal processing. A side effect outside Kafka, such as a call to a remote service or a write to a database through a custom client, is not covered. That is why the sink (the final write into your analytics database) needs its own duplicate protection, which later sections return to.
Turning raw events into useful rows: stream processing
Raw events are rarely ready to query. They need parsing, enrichment with reference data (turning a user ID into a plan tier), filtering, and often aggregation. That is the job of a stream processor.
Apache Flink is the most widely used dedicated engine for stateful stream processing. It reads from Kafka, keeps state between events, and writes results downstream. The part that separates it from a script that loops over a consumer is its handling of time.
Event time, processing time, and watermarks
Flink's documentation draws a precise line. Processing time is the system clock of the machine running an operator. Event time is when the event occurred on its producing device. Processing time is simpler and lower latency, but results change with arrival rates, operator speed, and outages. Event time gives consistent results even with out-of-order or reprocessed data, at the cost of waiting for stragglers.
Mobile apps make this concrete. A phone that loses signal in a tunnel may deliver a click 90 seconds after it happened. If you bucket by arrival time, that click lands in the wrong minute. If you bucket by event time, you need a way to know when a minute is "complete."
Watermarks are that mechanism. In the documentation's words, a Watermark(t) declares that event time has reached time t in that stream. Once the watermark passes the end of a window, the operator can close it and emit a result. Elements that arrive after the watermark has already moved past their timestamp are called late, and programs must decide whether to drop them, route them aside, or allow some lateness and update the result.
Picture a school bus driver. The bus leaves at 8:00, but the driver waits a few minutes for stragglers. A watermark is the written policy for how long to wait. Wait longer and fewer children are missed, but the bus runs later.
// Illustrative Flink 2.x job: count events per user in five-minute event-time windows
// imports: java.time.Duration,
// org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
DataStream<Event> events = env.fromSource(kafkaSource, watermarkStrategy, "user-events");
events
.keyBy(e -> e.userId)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(5)))
.aggregate(new EventCountAggregator())
.sinkTo(clickHouseSink);
Processing-time windows are easier to write but give the wrong bucket whenever events arrive late. For counts that someone will compare against billing or revenue, event time is usually the correct choice.
Alternatives to Flink
Flink is powerful and also demanding. Several tools cover simpler cases.
- Kafka Streams is a Java library that runs inside your application with no separate cluster. It suits straightforward transformations and aggregations.
- Spark Structured Streaming fits teams already running Spark. The Apache Spark explainer covers the engine.
- Materialize is a streaming SQL database. Its documentation describes incrementally maintained views, where results update as new data arrives instead of being recomputed from scratch, built on Timely Dataflow and Differential Dataflow. It speaks the PostgreSQL wire protocol, though it does not claim full PostgreSQL dialect coverage.
- RisingWave is another streaming SQL database in the same family.
A useful rule: if the transformation can be written as a SQL query over a stream, try a streaming SQL tool first. Reach for Flink's programmatic API when you need custom state, complex event patterns, or fine control over time and late data.
The serving layer: ClickHouse, Druid, and Pinot
The serving database is where the stack earns the name. It has to ingest continuously, keep recent data queryable almost immediately, and answer aggregate queries in a fraction of a second while many users hit it at once. Three open-source systems dominate this space, and they came from three different companies.
| Database | Origin | Open-sourced | Apache status | Source |
|---|---|---|---|---|
| ClickHouse | Yandex, development began 2009 | June 15, 2016 (Apache 2.0) | Not an Apache project; ClickHouse, Inc. formed September 2021 | Wikipedia |
| Apache Druid | Metamarkets, 2011 | October 2012 (GPL), Apache License February 2015 | Apache project | Wikipedia |
| Apache Pinot | LinkedIn, 2013 | June 2015 (Apache 2.0) | Incubation June 2019, top-level August 2021 | Wikipedia |
All three are columnar. Instead of storing each row together, they store each column together. A query that sums revenue by country reads only those two columns, not the dozens of others in the table. That single design choice explains most of their speed advantage over row-oriented databases for analytics.
Think of a filing cabinet. A row store keeps one folder per customer with everything inside. A column store keeps one drawer per attribute, so asking "what is the average order value?" means opening one drawer instead of every folder.
ClickHouse
ClickHouse began in 2009 as an experimental project at Yandex to test whether real-time analytical reports could be generated from continuously arriving data, according to Wikipedia. It is a column-oriented database for online analytical processing (OLAP, meaning large aggregate queries rather than transactions) and is queried with SQL.
That SQL point matters in practice. A team that already knows SQL can start querying immediately, and many BI tools can connect to it.
SELECT
toStartOfMinute(event_time) AS minute,
user_id,
countIf(event_type = 'click') AS clicks,
countIf(event_type = 'view') AS views
FROM events
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY minute, user_id
ORDER BY minute DESC
LIMIT 100
Where it fits: internal analytics, log and event analysis, and product dashboards for teams that want a SQL-first system and are comfortable running a database cluster or paying for a managed one.
Where it needs care: it is designed for appending data, so frequent updates and deletes are awkward, a topic the update section below covers.
Apache Druid
Druid began in 2011 when Eric Tschetter, Fangjin Yang, Gian Merlino, and Vadim Ogievetsky built it to power Metamarkets' analytics product. Its architecture is a set of specialized process types. Broker processes receive client queries and route them to the data servers holding the relevant segments, then merge the partial results. Coordinator processes manage where data lives. Druid also depends on external pieces: ZooKeeper for coordination, a metadata store such as PostgreSQL or MySQL, and deep storage (object storage or HDFS) as the permanent copy.
Druid's own design page is direct about its sweet spot: insert rates are very high but updates are less common, most queries are aggregations and reports, latency targets are 100 ms to a few seconds, and the data has a time component. It also lists poor fits: low-latency updates of existing records by primary key, workloads where latency does not matter, and large joins between two big fact tables.
Druid partitions data by time first, so a query over the last hour touches only the partitions in that range. It uses compressed bitmap indexes for fast filtering across many columns, and ingested data is queryable immediately. The cost of this design is operational weight. More process types and more dependencies mean more to deploy, monitor, and upgrade.
Apache Pinot
Pinot started inside LinkedIn in 2013 to power user-facing products, with the "Who's viewed your profile" feature as an early example. That origin explains its emphasis: many concurrent queries from end users, each needing a fast answer, rather than a few analysts running heavy queries.
Its signature feature is the star-tree index. As Pinot's documentation explains, it stores pre-aggregated results in a tree, so a matching query reads a small set of pre-aggregated rows instead of scanning raw documents. The trade-off is space against time. Higher-cardinality dimensions enlarge the pre-aggregated data, and queries that do not match the configured dimensions and functions get no benefit. Some predicates, including REGEXP_LIKE and exact DISTINCT_COUNT, are unsupported by the index.
Pinot 1.0, released in August 2023, added a multi-stage query engine with distributed joins, window functions, and set operations, which widened the range of SQL it can run. Uber, Microsoft, Stripe, Walmart, and Slack are among its active contributors and users, per Wikipedia.
Choosing among them
The table below compares design emphasis, not benchmark winners, because no neutral, current benchmark covers all three on identical workloads and hardware.
| Question | ClickHouse | Druid | Pinot |
|---|---|---|---|
| Primary emphasis | Fast SQL analytics, SQL-first | Time-series aggregation at high ingest rates | User-facing, high-concurrency queries |
| Signature mechanism | Sparse primary index over sorted parts | Time partitioning plus bitmap indexes | Star-tree and other pluggable indexes |
| Architecture weight | One main server type, plus coordination in clusters | Several process types plus external dependencies | Several components managed with Helix and ZooKeeper |
| Updates by key | Awkward, eventual deduplication | Listed as a poor fit | Upserts supported on real-time tables, with documented constraints |
| Managed offerings | ClickHouse Cloud | Imply | StarTree |
Take the "operational weight" row as a general tendency, not a law. A well-run Druid cluster beats a badly run ClickHouse one, and a managed service removes much of the difference. For teams starting fresh with SQL skills and no strong reason to choose otherwise, ClickHouse is a common first pick. For product features where thousands of customers hit the same dashboards, Pinot's design targets that case directly. For very high-volume event streams where time is the dominant filter, Druid's design targets that.
Why ClickHouse is fast: parts, granules, and sort order
Understanding the mechanism makes the schema decisions less mysterious. The MergeTree documentation describes the core: each insert creates a new table part, and a background process later merges parts together. Each part is sorted by the primary key, and parts from different partitions are never merged together.
The primary index is sparse. It does not point at individual rows. It points at blocks of 8,192 rows (the default) called granules, so the index stays small enough to live in memory, and a query reads only the granules that could match its filter. The sort order is therefore the main performance lever. Put the columns you filter on most often first in ORDER BY.
Imagine a library that receives books in boxes. Each box is sorted on arrival and shelved as a unit, and staff periodically merge small shelves into bigger sorted ones. To find a book, you check the sparse catalog, which lists the first title on each shelf, then scan only the right shelf.
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
page_url String,
country LowCardinality(String),
revenue Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (country, event_type, event_time);
INSERT INTO events VALUES
('2026-05-14 10:00:00', 12345, 'click', '/home', 'US', 0);
SELECT
country,
count() AS events,
uniq(user_id) AS unique_users,
sum(revenue) AS total_revenue
FROM events
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY country
ORDER BY events DESC;
Two details in that schema are deliberate. First, the partition is monthly. The documentation says partitioning is for data management, not query speed ("Partitioning does not speed up queries," in contrast to ORDER BY), recommends coarse partitions such as monthly, and warns against partitioning by something like client ID. Many teams overpartition and then wonder why merges and metadata become a burden.
Second, LowCardinality(String) is a ClickHouse type that stores a dictionary of distinct values for columns with few unique values, such as country codes or event types. It is useful for those columns and counterproductive for something like page URLs with millions of distinct values.
A related implication of "each insert makes a part": many tiny inserts create many small parts that the background process must merge. Guidance for ClickHouse therefore favors fewer, larger batch inserts over a stream of single-row writes. A streaming consumer should buffer events for a short interval and write them together.
Pre-aggregation with materialized views
The fastest query is one that reads less data. In ClickHouse, an incremental materialized view is, in the documentation's phrase, "just a trigger that runs a query on blocks of data as they're inserted into a table." Each time a block is inserted into the source table, the view's SELECT runs on that block and writes the result to a separate target table.
CREATE MATERIALIZED VIEW daily_revenue_mv
TO daily_revenue
AS SELECT
toDate(event_time) AS date,
country,
sum(revenue) AS revenue
FROM events
GROUP BY date, country;
The target table typically uses SummingMergeTree or AggregatingMergeTree, which combine rows with the same sort key in the background. Because those merges are asynchronous, queries against the target should still aggregate (for example sum(revenue) ... GROUP BY) rather than assume one row per key.
The materialized view documentation lists caveats that bite in production:
- Only the source table triggers the view. Changes to a table on the right side of a JOIN do not update existing results.
- Data in joined tables must exist before the source rows arrive, or the result can be wrong.
- Joins against large tables slow inserts significantly.
- Averages and similar measures need partial aggregation states. Averaging pre-computed averages gives the wrong answer.
- The documentation describes insert-triggered behavior only; do not assume updates or deletes to the source propagate.
Pinot's star-tree index and Druid's ingest-time summarization attack the same problem in their own ways. All three trade flexibility for speed. A pre-aggregated table answers the questions you planned for very quickly and the questions you did not plan for slowly or not at all.
The update problem
Analytics databases are happiest when data is written once and never changed. Real data changes. Orders get refunded, user profiles get edited, and a late-arriving correction rewrites last Tuesday.
ClickHouse offers several patterns, and the deduplication guide is candid about the limits.
- ReplacingMergeTree removes duplicate rows that share a sorting key during merges, which suits upsert-style behavior where the last inserted row should win.
- FINAL adds a query-time merge so results show deduplicated rows. The guide says this suits small datasets and is probably not the best choice for large ones.
- GROUP BY with max() can outperform FINAL for columns that only increase.
- Collapsing engines (CollapsingMergeTree and VersionedCollapsingMergeTree) cancel old rows using a sign column, with the versioned variant handling out-of-order inserts.
The key phrase is "eventual." Deduplication happens during part merges, not at insert time, so duplicates can exist at any moment and queries should allow for them. A non-idempotent expression such as a DEFAULT now() column makes rows look unique and defeats deduplication. Setting an insert_deduplication_token per batch helps stop the same batch being inserted twice.
Druid's documentation names low-latency updates by primary key as a poor fit. The practical lesson across all three systems is to model data as immutable events wherever possible. Record "order refunded" as a new event instead of editing the original order row, and compute the current state in the query or a view.
Architecture patterns: Lambda, Kappa, and streaming views
Three patterns appear repeatedly, and each answers the same question: how do you get both accurate history and fresh data?
Lambda architecture
Nathan Marz first described this paradigm in an October 2011 blog post, originally calling it a batch/realtime architecture, per Wikipedia. It has three layers. A batch layer recomputes views from the full dataset for accuracy. A speed layer processes streams to minimize latency and fills the gap until batch views are ready. A serving layer merges the two for queries.
The criticism is well known: batch and streaming code paths need separate implementations that must stay in sync. If the two disagree about how to count a "session," your dashboard flickers when the batch job overwrites the streaming estimate.
Kappa architecture
Jay Kreps proposed Kappa in a July 2014 O'Reilly article, "Questioning the Lambda Architecture." It treats everything as a stream with a single codebase. To reprocess history, you replay the log from the beginning through the new code. That depends on retaining enough history in the log, which is a real storage and cost decision.
Streaming SQL and incrementally maintained views
The newer approach uses databases that maintain results incrementally as events arrive. Materialize and RisingWave fit here, and ClickHouse's materialized views are a cousin. The mental model is simple: you write the query once, and the system keeps its answer current. The limitation is transformation breadth. Complex stateful logic, custom windowing, and heavy enrichment may still need Flink or application code.
| Pattern | Strength | Weakness | Reach for it when |
|---|---|---|---|
| Lambda | Accurate batch correction plus fast streaming | Two codebases to maintain | You must reconcile streaming numbers against audited batch totals |
| Kappa | One pipeline, replayable | Needs log retention, reprocessing can be slow | You can express all logic as stream processing |
| Streaming views | Simplest mental model | Narrower transformations | Most work is aggregation over fresh data |
A reference pipeline
Here is a typical end-to-end layout, with the real-time path and the historical path running side by side.
Application emits events
|
Kafka (one topic per event family)
|
Flink (parse, enrich, aggregate) Kafka Connect
| |
ClickHouse (fresh data, fast queries) Warehouse (full history)
| |
API (sub-second queries) BI tools, ML training
|
Live dashboard (polling or WebSocket)
The two destinations serve different jobs. The real-time store holds recent data, often days or weeks, and answers many small queries quickly. The warehouse holds everything, supports heavy joins and historical analysis, and feeds BI and model training. Trying to make one system do both is a common source of pain.
Getting data out of an existing database
If the events you want live in an operational database such as PostgreSQL or MySQL, you may not need application-level event emission at all. Change data capture (CDC) tools read a database's change log and publish each row change as an event. Debezium is a widely used open-source example that publishes changes to Kafka. This gives you a stream with no changes to application code, though it also means your stream carries database-shaped events (updates and deletes) that downstream systems must handle.
Contracts between producers and consumers
A streaming pipeline fails quietly when an upstream team renames a field. The dashboard does not crash; it just shows zeros. That is why many teams pair streaming with explicit schemas and agreements about what producers may change. The data contracts guide covers how to ship them, and the event-driven automation explainer shows the same event-first thinking applied to workflows. If your events arrive over HTTP rather than Kafka, API integration patterns explains webhooks, server-sent events, and streaming responses.
Cost and operations
Real-time stacks cost more than batch ones, but the amount depends so heavily on volume, retention, and vendor that published ranges mislead. A more useful way to think about it is by what you are buying.
- More components. A batch design might be an extract tool, a warehouse, and dbt. A streaming design adds a log, a processor, and a serving database, each with its own failure modes and upgrade cycle.
- Always-on compute. Batch jobs run and stop. Streaming consumers and serving clusters run continuously, so you pay for idle capacity between spikes.
- Operational skill. Running Kafka well is a discipline of its own, covering partition counts, retention, rebalancing, and monitoring. This is the main reason many small teams use managed services.
- Data duplication. Keeping a hot store and a historical warehouse means storing recent data twice.
The practical advice is to start managed and simple. Options include Confluent Cloud and AWS MSK for Kafka, ClickHouse Cloud and Tinybird (an analytics API layer built on ClickHouse), Imply for Druid, and StarTree for Pinot. Managed services cost more per unit of capacity and cost less in engineering time, which for small teams is usually the better trade. Self-hosting makes sense once the cluster is large enough that a platform team pays for itself.
Monitoring is not optional. A real-time dashboard that silently stops updating is worse than a nightly report, because people trust it. Track consumer lag (how far behind the newest event the consumers are), end-to-end freshness (the age of the newest row in the serving database), and error or dead-letter volumes. The AI observability explainer covers monitoring ideas that transfer to data pipelines.
Common mistakes
Building real-time when batch would do. Ask the people who use the data what staleness they would notice. Many answers are "an hour is fine," and the project shrinks.
Mixing hot and cold data in one store. Keep recent data in the serving database and history in the warehouse. A real-time database is not a data lake.
Counting by arrival time. Late events land in the wrong window and your numbers drift. Use event time with watermarks when accuracy matters.
Assuming exactly-once reaches the database. Kafka's guarantee covers the Kafka pipeline. Your sink still needs idempotent writes or deduplication keys.
Treating the serving database like a transactional one. Frequent row-level updates fight the design of all three systems. Model changes as events.
Skipping schema discipline. Unversioned events break dashboards silently. Agree on schemas and test them.
Over-engineering. A simple Kafka-to-ClickHouse pipeline, with a small consumer that batches inserts, beats a sprawling Flink job when no complex processing is required.
Pre-aggregating before knowing the questions. Materialized views and star-tree indexes accelerate planned queries. Start with raw data and pre-aggregate once the common queries are clear.
Questions people ask
Q: ClickHouse or Snowflake? A: They serve different freshness and concurrency needs. A cloud warehouse such as Snowflake handles broad analysis of large historical datasets, where data that is minutes old is acceptable. ClickHouse targets fast queries on recent data with many concurrent users. Many companies run both, with a hot store and a warehouse for history. The warehouse comparison covers the warehouse side.
Q: Do I need Kafka if I already have a database? A: Not always. Change data capture tools such as Debezium can stream changes from a database's log. Kafka becomes valuable when many producers and consumers share the same events, when you need replay, or when volumes exceed what direct connections handle comfortably.
Q: What is streaming SQL? A: Tools such as Materialize, RisingWave, and Flink SQL let you write SQL against streams, with results that update as events arrive. They are simpler than Flink's programmatic API for aggregation and joins, though less flexible for custom state.
Q: How fresh is "real-time"? A: It depends on the design. Seconds is a reasonable target for a well-built Kafka-to-serving-database pipeline. Sub-second end-to-end freshness is harder and needs careful tuning of batching, buffering, and merges. Druid's documentation frames its query latency target as 100 ms to a few seconds, which is a query measure, not a freshness one.
Q: Can I use a vector database or search engine instead? A: Those solve different problems. A vector database finds similar items by meaning, not aggregates over time. Some teams combine them, with an analytics store for counts and a vector store for retrieval.
Q: Where do machine learning features fit? A: Fresh features for online models often come from the same streams. A feature store manages serving them consistently between training and production, and MLOps practices cover the monitoring side.
Q: Is real-time worth it? A: For a specific decision or product feature where staleness has a cost, yes. For general reporting, usually not. Measure the cost of staleness before building.
The takeaway
A real-time analytics stack is not one product but a chain of design choices: a log to carry events, a processor to shape them, and a columnar store to serve them. The tools differ in emphasis. ClickHouse favors SQL-first analytics, Druid favors high-ingest time-series aggregation, and Pinot favors many concurrent user-facing queries. All of them are fast because they read less data, through column storage, sorted layouts, and pre-aggregation.
The harder decisions are not about which database wins. They are about time, duplicates, and updates: counting by event time instead of arrival time, protecting the sink from repeated writes, and modeling changes as events. Teams that settle those questions early, and that confirm someone actually needs fresher data, build stacks that stay trustworthy. Teams that skip them build fast dashboards that quietly disagree with finance.
Sources: Apache Kafka (Wikipedia), Lambda architecture (Wikipedia), ClickHouse documentation, Apache Druid design, Apache Flink time concepts, Materialize documentation.
Last verified: October 22, 2026
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

Apache Spark Explained — Big Data Processing 2026
How Apache Spark works: RDDs, lazy DAG execution, drivers and executors, Spark SQL, AQE, streaming, and when single-node tools like DuckDB or Polars fit better.

Data Engineering vs Data Science — Which Career Path 2026
Data engineering vs data science: what each role does, the skills and tools they need, how the work differs, and how to choose or switch between them.

Lakehouse Formats 2026 — Iceberg vs Delta Lake vs Hudi
Iceberg vs Delta Lake vs Hudi in 2026: how each lakehouse table format works, where each wins, how catalogs and migration fit, and how to choose.

Data Contracts 2026 — Why They Matter and How to Ship Them
Data contracts explained: what they contain, how dbt model contracts and Schema Registry enforce them, where they fail, and a rollout plan for small teams.

dbt Tutorial — Data Engineering for Analysts (2026 Guide)
dbt (data build tool) explained for data analysts: SQL-based transformations, models, tests, documentation. The modern way to build data pipelines.

What Is the Vercel AI SDK? LLM Toolkit
The Vercel AI SDK is a TypeScript toolkit for building LLM apps with React, Next.js, Svelte, and Vue. Unified API, streaming, tools, structured outputs.