Engineering Note
What Is Apache Fluss? A Streaming Lakehouse Between Kafka, Flink, and Iceberg
Understand Apache Fluss by role: how it differs from Kafka, Flink, and Iceberg, and how its Log Tables, Primary Key Tables, and shared metadata connect real-time streams to a lakehouse.
Apache Fluss has enough overlap with Kafka, Flink, and Iceberg that it can initially look like a replacement for one of them. The four systems, however, occupy different positions in a data path.
This article first puts those roles side by side, then explains why Fluss connects a real-time layer to a lakehouse layer. The comparison is not about feature counts. It is about whether a system delivers data, computes it, stores it for low-latency access, or manages it for long-term analysis.
First, Position It: Kafka, Flink, Iceberg, and Fluss
| System | Role in the data path | Central concepts | Relationship to Fluss |
|---|---|---|---|
| Kafka | delivers events between services and retains replayable logs | topics, partitions, consumer groups | It resembles a Fluss Log Table through logs and offsets, but Fluss is closer to an analytical table store than to a message broker. |
| Flink | computes, transforms, and joins streaming and batch data | jobs, state, checkpoints | Fluss is a store that Flink jobs can read and write. It does not replace Flink’s compute role. |
| Iceberg | supplies a lakehouse table format and manages long-lived history | files, snapshots, catalogs | Fluss handles recent data at low latency, then tiers history to a lakehouse layer such as Iceberg. |
| Fluss | supplies streaming tables for real-time analytics | Log Tables, Primary Key Tables, buckets | It handles events and current state at low latency and tries to connect real-time and historical layers under one table. |
One line per role is even simpler.
Kafka delivers events.
Flink computes events.
Iceberg manages analytical history as files.
Fluss supplies real-time tables and connects them to lakehouse history.
The core idea of Fluss is therefore not “a cheaper way to store Kafka topics.” It is a storage system with one data model for streams, tables, and the lakehouse.
Why Fluss Exists: Copying Data Between Kafka and Iceberg
Real-time events often land in Kafka, while analytical data lands in Iceberg or Paimon. Flink jobs, connectors, separate stores, and separate retention policies usually sit between the two.
There is nothing inherently wrong with that shape. But as real-time analytics and AI feature computation grow, the cost of copying the same data across layers—and managing different metadata and freshness targets—grows with it.
Lakehouses are strong at large-scale analytics and long-lived storage. They have a tension around freshness, though: committing small Parquet files often creates a small-file problem, while accumulating data to write larger files increases data latency.
Low latency
-> frequent small-file commits
-> less efficient analytical reads
Efficient analytical files
-> accumulate data into larger files
-> less real-time freshness
Fluss places a real-time layer between those choices. The Fluss cluster reads and writes Arrow-based data at low latency, while a tiering service compacts that data into Parquet or ORC files for the lakehouse layer. The official Lakehouse documentation describes the former as a real-time layer retaining days of data with sub-second freshness, and the latter as a historical layer retaining months of data with minute-level freshness.
Fluss calls this a Streaming Lakehouse. Two details matter most.
- Shared metadata: the real-time and lakehouse layers use the same table metadata.
- Union reads: a query engine reads real-time and lakehouse data together. The 0.9 documentation explicitly names Flink as a supporting engine. The unreleased
Nextdocumentation lists Apache Flink and Apache Spark, while Trino and StarRocks read only the lakehouse layer through their native connectors.
One table refers to the logical model, not one physical location. Recent data lives in the Fluss real-time layer, while older data lives in the lakehouse layer. During tiering, data can temporarily exist in both layers as a safety measure. This makes Fluss different from merely adding another sink that copies data into a lake: it treats real-time and historical layers as different retention and access characteristics of one table.
The Central Model: Tables, Not Just Topics
The top-level Fluss data units are databases and tables. A table comes in two main forms.
| Table type | Write model | Read model | Good fit |
|---|---|---|---|
| Log Table | append only | sequential scan by offset | events, logs, audit history |
| Primary Key Table | upsert and delete by primary key | key lookup; snapshot followed by changes | current state, features, reference data |
A Log Table has no PRIMARY KEY, and written records are immutable. It naturally fits an event stream. A Primary Key Table declares a key. Writing a new value with the same key retains the latest state, while readers can look up one key or consume its change history.
Some terms will feel familiar to Kafka users.
- A bucket is the unit of table parallelism. It is similar to a Kafka partition, but Fluss also uses it as the basic unit for data migration and backup.
- An offset is a record position inside a Log Table bucket and tracks reading progress.
- A partition is a logical grouping by column values, such as date or region. Each partition contains its own buckets.
Each Primary Key Table bucket keeps both a change log and a structure for current key state. Fluss documentation describes the latter as a KvTablet backed by embedded RocksDB. That is why Fluss treats both sequential consumption and current-key lookup as first-class concerns.
Two Table Types in Flink SQL
Fluss exposes tables through a Flink Catalog. This compact example shows the difference between a Log Table and a Primary Key Table.
CREATE CATALOG fluss_catalog WITH (
'type' = 'fluss',
'bootstrap.servers' = 'coordinator-server:9123'
);
USE CATALOG fluss_catalog;
-- No PRIMARY KEY means an append-only Log Table.
CREATE TABLE payment_events (
payment_id STRING,
customer_id STRING,
amount DECIMAL(18, 2),
paid_at TIMESTAMP(3)
) WITH (
'bucket.num' = '8'
);
-- A PRIMARY KEY creates a Primary Key Table that keeps current state.
CREATE TABLE customer_features (
customer_id STRING,
plan STRING,
score DOUBLE,
PRIMARY KEY (customer_id) NOT ENFORCED
) WITH (
'bucket.num' = '4'
);
payment_events retains every payment event for a customer. customer_features updates the latest state when a new value arrives for the same customer_id. The $changelog virtual table exposes its change history, while $binlog includes before and after row images.
SELECT * FROM customer_features$changelog;
This moves a stream-processing path that rebuilds state from event history closer to the place that serves the latest state. It does not mean that Fluss automatically replaces a business database. A business system may still need a separate database for its transaction boundary, query patterns, recovery, and authorization model.
What a Union Read Looks Like
Assume that lakehouse tiering is enabled for payment_events. In Flink, reading the table name directly reads its real-time and historical layers together. Adding the $lake suffix reads only the lakehouse layer. This is the default behavior described in the Union Read documentation.
-- Default: read recent Fluss data and lakehouse history together.
SELECT * FROM payment_events;
-- Read only the historical layer.
SELECT * FROM payment_events$lake;
That difference is central to Fluss. Creating a Log Table or Primary Key Table alone does not create a Streaming Lakehouse. When tiering is enabled, the real-time and lakehouse layers share table metadata. Flink’s union read then presents the two layers as one result. The unreleased Next documentation also lists Spark as supporting Union Read, so verify support in the release you plan to use. Together, these capabilities let one logical table cover both freshness and long history.
When Each System Belongs in the Same Data Path
The four systems can coexist. Their boundaries are clearer when we follow one payment event.
| Need | Natural primary system | Where Fluss can add value |
|---|---|---|
| Deliver payment-completed events to multiple services and replay them | Kafka | Existing event contracts do not need to change. This alone is not a reason to add Fluss. |
| Clean, join, and derive data from the events | Flink | Fluss can be a source, sink, or place to look up state. Flink remains responsible for computation. |
| Query months of history from large-scale analytical engines | Iceberg or Paimon | It can be the destination for Fluss tiering. Fluss does not replace historical file analytics. |
| Work with recent events and current keyed state at low latency | Fluss | Choose a Log Table or Primary Key Table; Flink can then union-read its real-time and historical layers when needed. |
For example, Kafka can deliver payment events to services, while Flink cleans them and writes a payment_events Log Table and a per-customer customer_features Primary Key Table. Recent Fluss data can serve immediate analysis and lookup joins; older data can tier into Iceberg or Paimon. Flink can then read the two layers together.
Kafka payment-completed
-> Flink cleans and joins the event
-> Fluss payment_events: Log Table for ordered events
-> Fluss customer_features: Primary Key Table for current customer state
-> Iceberg or Paimon: compacted long-term history
-> Flink Union Read: analysis across recent and historical data
So “Fluss replaces Kafka” is too quick a conclusion. Kafka already owns established event contracts, Connect and Streams ecosystems, consumer-group operations, and replay paths. Fluss is worth evaluating particularly where real-time analytics, key-based lookup, and a streaming lakehouse are the center of a new data path.
Cluster and Storage Layers
A Fluss cluster has CoordinatorServers and TabletServers. CoordinatorServers coordinate TabletServers, metadata, rebalancing, and recovery. TabletServers manage and store the actual data.
Remote storage and lakehouse storage are optional layers around that core.
| Layer | Role | Examples in the current documentation |
|---|---|---|
| TabletServer local storage | low-latency real-time data | Arrow files |
| remote storage | Primary Key Table snapshots and tiered Log Table segments | HDFS, S3-like storage |
| lakehouse storage | compacted historical data and analytics | Paimon, Iceberg, Lance |
The current deployment documentation says that Fluss uses ZooKeeper for CoordinatorServer coordination and metadata management, while also stating an intention to remove ZooKeeper to simplify deployment. A serious evaluation should therefore consider deployment dependencies and version change, not only the feature list.
Fluss should not be treated as a code-level abstraction only. Its latency, local disks, remote-storage transfer, tiering delay, and cluster rebalancing need to be observed together. Konduo connects operational resources such as Kafka and databases through plugins so teams can inspect status, metric evidence, diagnostics, and alert response in one flow. A Fluss deployment still needs surrounding Kafka, storage, and metric signals to be read together; otherwise the bottleneck behind an apparent freshness problem is easy to miss.
Project Maturity: Still Incubating
It is too early to call Fluss an Apache Incubator graduate.
As of 2026-07-28, the Apache Incubator Fluss status page still lists Fluss as an incubating project. The April 2026 Incubator report does say that Fluss is “ready to graduate.” That is an assessment of graduation readiness, not confirmation that the formal graduation process has finished.
This distinction is more than a naming detail. APIs, deployment structure, and operational guidance can move quickly in an incubating project. The precise current posture is to treat Fluss as something to evaluate and validate, not as a settled platform default. The Incubator status page also records the 0.9.1 release on 2026-05-04.
Questions to Answer Before Adopting It
Fluss addresses an interesting problem, but it is not a default component for every Kafka or lakehouse environment.
- Do real-time and historical data genuinely need to be read as one table?
- Beyond sequential event consumption, are current-key lookups or lookup joins important?
- Is Flink already central to the compute path, or can it be introduced?
- Can an evaluation environment absorb API and deployment change while Fluss is still incubating?
- What remains at the boundary with existing Kafka topics, Schema Registry, Connect, Streams, and operational tools?
- Have local disk, remote storage, and lakehouse retention and cost been measured separately?
Fluss is most compelling not where a team wants to remove Kafka at any cost, but where the cost of repeatedly copying real-time analytical data into lakehouse history is already visible.
Conclusion
Apache Fluss is neither Kafka under a different name nor a replacement for Iceberg.
Use a Log Table for event flow.
Use a Primary Key Table for current state.
Use tiering and union reads to connect real-time and historical layers.
That combination is the core Fluss idea. As of 2026-07-28, Fluss has not graduated from the Apache Incubator. The Incubator’s assessment that it is ready to graduate is nevertheless a useful signal: the project has moved beyond a casual experiment and is worth serious validation.
Further Reading
- Where Kafka’s Disk Is Going: Object Storage and the New Streaming Layer - Places Fluss in the wider change in Kafka and object-storage design.
- Kafka Streams and the EIP Aggregator: Completing Is Harder Than Collecting - Explains how state and completion criteria differ in a streaming application.
- What Is Different About Pull and Push Metric Collection? - Compares observability and collection responsibility across real-time data paths.