automq is the pick-one architectural decision that finally decouples Kafka's compute from its local disk — the Apache 2.0-licensed drop-in replacement for Apache Kafka that writes its stream WAL directly to S3, and the open-source counterpart to a wave of object storage kafka offerings that senior data engineers now evaluate against WarpStream (Confluent-acquired 2024), the managed confluent freight cluster tier in Confluent Cloud, Redpanda's tiered storage, and vanilla Apache Kafka itself. Every Kafka broker your team runs in the traditional model binds compute and storage on the same instance — the broker's local NVMe holds the segment files, ISR replication doubles or triples the write bandwidth, cluster expansion triggers a rebalance storm that can last hours, and the cost of a 24-hour retention topic scales linearly with the number of brokers whether the topic is actually read or not. The engineering trade-off in 2026 does not live in "should we consider s3 kafka" — every log-firehose, CDC-fan-out, and metrics-pipeline workload can now be served by an object-storage-native broker — but in which variant you pick and how it composes with your existing Kafka clients, schema registry, and downstream stream processors.
This guide is the senior-DE walkthrough you wished existed the first time an interviewer asked "walk me through the automq kafka architecture and how it beats vanilla Kafka on cost", or "when would you pick confluent freight over Dedicated?", or "explain how s3 backed kafka handles the sub-second-latency workloads that the traditional broker is optimised for." It walks through the object-storage-native broker wave — AutoMQ's S3 stream WAL and Kubernetes-native deployment, Confluent Freight's ~100 ms managed cluster tier with its ~90% cost win versus Dedicated for high-throughput topics, WarpStream's BYOC agent + control-plane split (the pattern Confluent acquired in 2024), and the shared architecture pattern (producer buffer → ~500 ms flush to S3 → metadata-service offsets → background compaction) that makes all three work — plus the decision matrix for picking among automq vs warpstream, Freight, Redpanda, and vanilla Kafka. Each section pairs a teaching block with a Solution-Tail interview answer — code, a step-by-step trace, an output table, then a concept-by-concept breakdown of why it works.
When you want hands-on reps immediately after reading, drill the SQL practice library →, rehearse the streaming practice library →, and sharpen the architecture axis with the design practice library →.
On this page
- Why S3-backed Kafka is a 2026 category shift
- AutoMQ deep dive
- Confluent Freight tier
- Shared architecture pattern
- Choosing among AutoMQ / Freight / WarpStream / Redpanda / Kafka + interview signals
- Cheat sheet — S3-Kafka recipes
- Frequently asked questions
- Practice on PipeCode
1. Why S3-backed Kafka is a 2026 category shift
Three offerings — AutoMQ, Confluent Freight, WarpStream — decouple Kafka's compute from its local disk, and that changes every cost, ops, and scaling assumption
The one-sentence invariant: object-storage-native Kafka is the architectural pattern where the broker holds no durable state on its local disk — every producer batch is flushed within ~500 ms to an object store (S3, GCS, Azure Blob), a small controller / metadata service tracks segment offsets and consumer positions, and the broker itself becomes stateless enough to be restarted, autoscaled, or replaced without a rebalance storm — trading a ~100–500 ms tail-latency floor (versus the sub-10 ms of a local-disk broker) for a ~10× cost reduction, no more disk sizing, no more ISR wrangling, and no more cross-AZ replication bill. The three offerings that popularised the pattern — AutoMQ (Apache 2.0 OSS), Confluent Freight (managed), and WarpStream (BYOC, Confluent-acquired 2024) — differ in licensing, deployment topology, and pricing model but share the same architectural DNA, and the decision you make in 2026 binds every producer, consumer, and stream processor you deploy for years.
The four axes interviewers actually probe.
- Latency profile. Traditional Kafka on local NVMe hits sub-10 ms end-to-end producer→consumer latency in steady state. Object-storage-native brokers land in the ~100 ms – 1 s range because every write is buffered and flushed in batches to S3 rather than fsynced to local disk. Interviewers open here because it separates people who understand the physics ("you cannot beat the S3 PUT latency floor") from those who've only read the marketing pitch. The correct framing: "S3-Kafka is for workloads where p95 latency of 100–500 ms is acceptable — log firehoses, CDC pipelines, metrics ingestion — not for market data or trading."
- Cost per GB. Local-disk Kafka pays for provisioned NVMe, cross-AZ replication (2× or 3× write bandwidth billed by AWS), and always-on brokers whether the topic is read or not. Object-storage-native Kafka pays for S3 PUT / GET / storage plus a tiny stateless compute footprint — typically 5–10× cheaper for high-throughput topics with modest CPU per message. Every senior architect confirms the cost math on their actual workload before committing.
- Licensing / vendor lock-in. AutoMQ is Apache 2.0 — you self-host, you own the code, you can fork. Confluent Freight is a proprietary cluster tier inside Confluent Cloud — you pay Confluent per GB and per throughput unit. WarpStream (post-acquisition) is a BYOC model — the data-plane agent runs in your VPC, the control plane is Confluent-hosted; the code is not open source. Redpanda is Business Source License (BSL) 1.1 — free for non-competitive use, converts to Apache 2.0 after 4 years. Vanilla Apache Kafka is Apache 2.0 — the most permissive.
- Ops surface. Traditional Kafka carries a well-known ops burden (ZooKeeper / KRaft controller quorum, per-broker disk monitoring, ISR alerts, partition rebalance planning, cross-AZ traffic shaping). Object-storage-native brokers collapse most of it — no disk sizing, no rebalance storms, autoscaling by CPU alone — at the cost of a new ops surface (S3 request-rate limits, batch flush tuning, cold-partition tail latency).
The 2026 reality — the object-storage-native broker wave is a real category shift, not a niche.
-
AutoMQ — Apache 2.0 licensed, self-hostable, deployed on Kubernetes, Alibaba lineage, marketed as the "open-source alternative to WarpStream." Ships as a drop-in replacement for Apache Kafka: existing clients (
org.apache.kafka.clients.producer.KafkaProducer) connect unchanged, KRaft controller quorum coordinates metadata, S3 (or S3-compatible object storage like MinIO) is the durable WAL. The 2024 benchmark blog claims ~10× cost reduction versus a comparable Apache Kafka deployment on identical infrastructure. - Confluent Freight — a managed cluster tier introduced in Confluent Cloud in 2024, sitting alongside Basic / Standard / Enterprise / Dedicated. Freight targets high-throughput, latency-tolerant workloads (log firehoses, CDC replication, IoT telemetry) with a ~100 ms p95 latency floor and pricing dominated by GB ingested rather than always-on cluster units. Confluent's own guidance is: keep Dedicated for sub-10 ms workloads (trading, real-time bidding), move everything else to Freight for the cost saving.
- WarpStream — BYOC model: the WarpStream Agent runs in your VPC (data plane), the WarpStream Control Plane is Confluent-hosted (metadata + coordination), your data never leaves your S3 buckets. Confluent acquired WarpStream in September 2024. Post-acquisition, WarpStream continues to ship as a discrete product with its "your bytes never leave your account" pitch intact.
- Redpanda — C++ reimplementation of the Kafka protocol with per-broker local NVMe for hot data plus S3 tiered storage for cold data. Not a fully object-storage-native broker — hot path is still local disk — but competes in the same evaluation because tiered storage collapses the cold-data cost equation.
- Apache Kafka — the reference implementation. KRaft controller quorum (ZooKeeper removed in 4.0), tiered storage KIP-405 available since Kafka 3.6, but hot-path writes still go to local disk. The default when your ecosystem (Kafka Streams, Connect, ksqlDB) is already committed and the operational cost is acceptable.
What interviewers listen for.
- Do you name the S3 stream WAL as the shared architectural primitive across AutoMQ / Freight / WarpStream? — senior signal.
- Do you frame the pattern as a latency-for-cost trade (100–500 ms floor in exchange for ~10× cost) rather than "cheaper Kafka"? — required answer.
- Do you distinguish AutoMQ (Apache 2.0 OSS) from WarpStream (BYOC proprietary) and Freight (managed proprietary) by licensing model? — senior signal.
- Do you name Kafka protocol wire compatibility as the reason existing clients keep working across all three? — required answer.
- Do you push back on "S3-Kafka is always the right answer" with the latency-workload counter-example (trading, market data)? — senior signal.
Worked example — the five-vendor comparison table
Detailed explanation. The single most useful artifact for a 2026 broker-choice interview is a memorised 5×5 comparison table. Every senior discussion about object storage kafka converges on this table within the first ten minutes; having it in your head is what separates a fluent answer from a stumbling one. Walk through building the table for a hypothetical log-firehose workload — a 500 MB/s ingest of application logs from a fleet of 20,000 pods, retained 7 days, consumed by two downstream pipelines (Elasticsearch sink + S3 archive).
- Workload. 500 MB/s sustained producer ingest, 1.5 MB/s per pod on average, 7-day retention, two consumers (Elasticsearch sink at 1× throughput, S3 archive at 1× throughput → 2× read fan-out).
- Latency requirement. p95 producer→consumer under 2 s is acceptable (the Elasticsearch sink batches every 5 s anyway); no sub-second requirement.
- Constraints. AWS-only, must speak Kafka wire protocol (existing Kafka Connect sinks), engineering team of 3 that cannot own a self-hosted Kafka cluster's ops load.
- Baseline cost. A comparable Apache Kafka deployment on m6i.4xlarge brokers + 3× cross-AZ replication runs ~$18,000/month in AWS list price.
Question. Build the five-vendor comparison for the log-firehose workload and pick the broker each constraint drives you toward.
Input.
| Vendor | Licensing | Latency p95 | Cost/GB | Deployment | Ecosystem |
|---|---|---|---|---|---|
| AutoMQ | Apache 2.0 | 100–500 ms | ~$0.02/GB (S3-dominated) | K8s / EKS self-host | Kafka wire compat |
| Confluent Freight | Proprietary managed | ~100 ms | ~$0.08/GB (Confluent-billed) | Confluent Cloud | Full Confluent ecosystem |
| WarpStream | Proprietary BYOC | 200–800 ms | ~$0.04/GB (S3 + control plane) | Agent in your VPC | Kafka wire compat |
| Redpanda | BSL 1.1 | sub-10 ms hot / 200 ms cold | ~$0.05/GB (NVMe + S3) | Self-host or Cloud | Kafka wire compat |
| Apache Kafka | Apache 2.0 | sub-10 ms | ~$0.15/GB (NVMe + cross-AZ) | Self-host | Full Kafka ecosystem |
Code.
# High-level workload-to-vendor mapping (illustrative)
workload_class: log_firehose
throughput_mbps: 500
retention_days: 7
consumers: 2
latency_p95_ms_budget: 2000
candidates:
- vendor: AutoMQ
fit: strong # latency budget generous; OSS; K8s-native
monthly_cost_estimate_usd: 2500 # dominated by S3 storage + PUT
ops_burden: medium # self-host on K8s, but no disk sizing
- vendor: Confluent Freight
fit: strong # managed, no ops burden
monthly_cost_estimate_usd: 9000 # ~90% cheaper than Dedicated
ops_burden: minimal # fully managed
- vendor: WarpStream
fit: strong # data stays in your S3; BYOC
monthly_cost_estimate_usd: 5000 # split control + your S3
ops_burden: low # you run agents, Confluent runs control
- vendor: Redpanda
fit: acceptable # over-engineered for this latency budget
monthly_cost_estimate_usd: 8000 # NVMe cost dominant
ops_burden: medium
- vendor: Apache Kafka
fit: poor # 3-6x more expensive; no latency benefit needed
monthly_cost_estimate_usd: 18000
ops_burden: high
Step-by-step explanation.
- The comparison table forces the five axes into a single view: licensing, latency, cost/GB, deployment, ecosystem. AutoMQ wins on licensing (Apache 2.0) and cost/GB (S3-dominated), draws with WarpStream on latency (both ~100–500 ms), and uniquely offers full self-hosted control with no vendor lock-in.
- Confluent Freight wins on ops burden (fully managed, no infrastructure to run) and ecosystem completeness (full Confluent Cloud stack — Schema Registry, ksqlDB, Connect all included). It's the pragmatic answer when the team is Confluent-Cloud-committed already and just wants the Freight tier's cheaper cluster type.
- WarpStream wins on data-sovereignty — "your bytes never leave your S3 buckets" — which matters for regulated workloads (finance, healthcare, government). The BYOC agent runs in your VPC and writes only to your S3; the control plane sees metadata only.
- Redpanda is over-engineered for this workload. Its sub-10 ms hot-path latency is a headline feature, but the log-firehose workload has a 2 s latency budget — you're paying for capability you don't need. Redpanda wins when the same cluster must serve both sub-10 ms hot and cold-archive workloads.
- Apache Kafka is the "safe choice" answer that fails the cost test: at ~$18,000/month for the same throughput, it's 3–6× more expensive than the object-storage-native alternatives. Pick Apache Kafka only when latency truly matters or the ecosystem lock-in (Kafka Streams state stores on local disk, e.g.) is load-bearing.
Output.
| Constraint | Recommended vendor | Why |
|---|---|---|
| OSS, self-host, K8s-native | AutoMQ | Apache 2.0; K8s deployment; S3 cost model |
| Fully managed, Confluent-native | Confluent Freight | ~90% cheaper than Dedicated for this workload |
| BYOC, data sovereignty | WarpStream | Data never leaves your S3; agent-in-VPC |
| Sub-10 ms hot + cold archive | Redpanda | Tiered storage; only vendor with both |
| Pure ecosystem, cost no object | Apache Kafka | Full ecosystem; no vendor bet |
Rule of thumb. Never pick an S3-Kafka variant based on "which one is trendy." Pick it on (licensing × latency × cost/GB × ops burden × ecosystem) — the five axes. Write the table on a whiteboard first; the vendor choice falls out of the constraints.
Worked example — what interviewers actually probe on S3-Kafka
Detailed explanation. The senior data-engineering S3-Kafka interview has a predictable structure: the interviewer opens with an ambiguous question ("we're getting killed on our Kafka bill — what would you do?"), then progressively narrows to test whether you understand the object-storage-native architecture. The candidates who name the pattern in sentence one score highest; the candidates who describe "cheaper brokers" score lowest. Walk through the interview grading rubric.
- Ambiguous opener. "Our Kafka bill is $60k/month and growing — walk me through how you'd cut it." — invites you to name S3-Kafka.
- Follow-up 1. "What's the latency trade-off?" — probes the physics floor.
- Follow-up 2. "OSS or managed?" — probes licensing awareness.
- Follow-up 3. "Can our existing Kafka clients keep working?" — probes wire-protocol compatibility.
- Follow-up 4. "What breaks?" — probes failure semantics.
Question. Draft a 5-minute senior S3-Kafka answer that covers all five axes without waiting to be asked.
Input.
| Interview signal | Weak answer | Senior answer |
|---|---|---|
| Pattern named | "cheaper Kafka" | "object-storage-native broker with S3 as the durable WAL" |
| Latency | "same as Kafka" | "100–500 ms floor; sub-10 ms is off the table" |
| Licensing | "not sure" | "AutoMQ Apache 2.0; Freight/WarpStream proprietary" |
| Compatibility | "have to rewrite clients" | "Kafka wire protocol; existing clients work unchanged" |
| Failure story | "the same" | "S3 becomes the durability boundary; no ISR to lose" |
Code.
Senior S3-Kafka answer template (5 minutes)
===========================================
Minute 1 - name the pattern up front
"I'd move the log-firehose and CDC topics to an
object-storage-native broker - AutoMQ if we want OSS on our own
K8s, Confluent Freight if we're already in Confluent Cloud,
WarpStream if data sovereignty (BYOC) is a hard requirement."
Minute 2 - the physics
"S3-backed brokers batch producer writes in memory and flush every
~500ms to S3. That's the durability boundary. It gives you a
100-500ms end-to-end latency floor; sub-10ms workloads (trading,
RTB) stay on local-disk Kafka or Redpanda hot path."
Minute 3 - the economics
"Cost math: local-disk Kafka pays for NVMe + 3x cross-AZ
replication + always-on brokers. S3-backed pays for S3 PUT/GET/
storage + a small stateless compute footprint. On high-throughput
long-retention topics that's typically 5-10x cheaper."
Minute 4 - compatibility
"Kafka wire protocol is preserved across all three. Existing
producers, consumers, Kafka Connect sinks, ksqlDB queries keep
working unchanged - the broker impl is transparent to the client.
Schema Registry and transactions are supported."
Minute 5 - failure semantics + when to say no
"S3 becomes the durability boundary; there's no ISR to lose. A
broker restart is instant because there's no local state to
rebuild. The failure surface is S3 availability and the metadata
service. Sub-10ms workloads and Kafka Streams heavy state-store
workloads stay on local-disk Kafka."
Step-by-step explanation.
- Minute 1 is the crucial framing. Naming the three specific offerings (AutoMQ / Freight / WarpStream) and mapping each to a decision criterion (OSS / managed / BYOC) signals you're a decision-maker, not a task-runner. Weak candidates say "we'd use a cheaper Kafka" without naming the category.
- Minute 2 addresses the latency axis before the interviewer asks. Naming the physical floor (100–500 ms because S3 PUT latency is the durability boundary) preempts the trap where you commit to S3-Kafka, then admit you don't know when it breaks. Reserving Redpanda / local-disk for sub-10 ms workloads is the senior save.
- Minute 3 is the cost argument. "Local-disk Kafka pays for NVMe + cross-AZ replication + always-on brokers" is the diagnosis; "S3-backed pays for S3 PUT + storage + stateless compute" is the cure. Quantifying "5–10× cheaper" gives the interviewer a number to argue with.
- Minute 4 is the compatibility answer. Every senior S3-Kafka answer names "Kafka wire protocol" unprompted — this is the moat that makes migration realistic. Without it, moving from Apache Kafka to AutoMQ would require rewriting every producer, and the whole category would be dead.
- Minute 5 covers failure semantics and the when-not-to. Naming S3 as the durability boundary is subtle: it means broker crashes are cheap (no state to rebuild) but S3 outages are catastrophic (no local disk to fall back on). Naming Kafka Streams state stores as the "stays on local-disk Kafka" workload shows you know the ecosystem edge cases.
Output.
| Grading criterion | Weak score | Senior score |
|---|---|---|
| Names all three vendors in minute 1 | rare | mandatory |
| Names the ~500 ms flush and latency floor | rare | required |
| Names licensing difference (Apache 2.0 vs proprietary) | rare | senior signal |
| Names Kafka wire protocol compatibility | occasional | mandatory |
| Names Kafka Streams edge case | rare | senior signal |
Rule of thumb. The senior S3-Kafka answer is a 5-minute monologue that covers all five axes without waiting for the follow-ups. Rehearse it once; deploy it every time. Naming the physical latency floor (100–500 ms) unprompted is the single strongest signal.
Worked example — the "pick the broker" decision tree
Detailed explanation. Given a new streaming workload, the senior architect runs a 5-question decision tree in their head. Codifying the tree makes the interview answer reproducible: any interviewer can hand you a scenario and you can walk the tree out loud. Walk through the tree with three canonical scenarios: a log-firehose, a low-latency market-data feed, and a regulated bank CDC pipeline.
- Q1. Is p95 latency budget < 10 ms? → yes = local-disk Kafka or Redpanda hot path; no = go to Q2.
- Q2. Is data sovereignty (BYOC) a hard requirement? → yes = WarpStream (agent-in-VPC); no = go to Q3.
- Q3. Do you want fully-managed with no ops? → yes = Confluent Freight; no = go to Q4.
- Q4. Do you need OSS / no vendor lock-in? → yes = AutoMQ; no = go to Q5.
- Q5. Do you have heavy Kafka Streams state-store workloads? → yes = local-disk Kafka; no = any S3-Kafka fits.
Question. Walk the decision tree for the three scenarios and record the broker each ends up with.
Input.
| Scenario | Q1 (< 10 ms?) | Q2 (BYOC?) | Q3 (managed?) | Q4 (OSS?) |
|---|---|---|---|---|
| Log firehose | no | no | yes | — |
| Market data feed | yes | — | — | — |
| Regulated bank CDC | no | yes | — | — |
Code.
# Decision-tree helper (illustrative)
def pick_kafka_variant(p95_ms_budget: int,
byoc_required: bool,
want_managed: bool,
want_oss: bool,
heavy_streams_state: bool) -> str:
"""Return the recommended Kafka-compatible broker."""
if p95_ms_budget < 10:
return "local-disk Kafka or Redpanda hot path"
if byoc_required:
return "WarpStream (BYOC agent)"
if want_managed:
return "Confluent Freight"
if want_oss:
return "AutoMQ (Apache 2.0)"
if heavy_streams_state:
return "local-disk Kafka (state stores need local disk)"
return "any S3-Kafka fits; pick on cost"
# Walk the three scenarios
print(pick_kafka_variant(2000, False, True, False, False))
# → 'Confluent Freight'
print(pick_kafka_variant(5, False, False, False, False))
# → 'local-disk Kafka or Redpanda hot path'
print(pick_kafka_variant(1000, True, False, False, False))
# → 'WarpStream (BYOC agent)'
Step-by-step explanation.
- Scenario 1 — a log firehose with a 2 s latency budget. Q1 = no (budget is 2000 ms), Q2 = no (no data-sovereignty requirement), Q3 = yes (team wants managed) → Confluent Freight. This is the modern default for latency-tolerant workloads inside Confluent Cloud.
- Scenario 2 — a market-data feed with sub-10 ms latency. Q1 short-circuits to local-disk Kafka or Redpanda hot path. Object-storage-native brokers are off the table entirely; the physics floor is above the requirement.
- Scenario 3 — a regulated bank CDC pipeline where every byte must stay in the bank's AWS account. Q1 = no, Q2 = yes (BYOC) → WarpStream. The agent-in-VPC pattern is the only vendor answer that keeps data out of Confluent's control plane.
- The tree is deliberately biased toward S3-Kafka when latency permits — the cost savings are large enough that the default should be "why not use S3-Kafka?" rather than "why should we?" for any new workload with a latency budget above 100 ms.
- If none of Q1–Q5 pass strongly (e.g. sub-10 ms budget + BYOC + Kafka Streams state), the answer becomes "you need multiple clusters" — a local-disk cluster for the hot path, an S3-Kafka cluster for the cold-tolerant path, and a Streams cluster for the stateful topology. Real-world architectures rarely fit one vendor.
Output.
| Scenario | Recommended broker | Why |
|---|---|---|
| Log firehose (2 s budget) | Confluent Freight | Managed; ~90% cheaper than Dedicated |
| Market data (< 10 ms) | Local-disk Kafka or Redpanda | Physics floor rules out S3-Kafka |
| Regulated CDC (BYOC required) | WarpStream | Agent-in-VPC keeps data in your account |
Rule of thumb. The five-question decision tree is a whiteboard-friendly answer. Practice walking it end-to-end so an interviewer can hand you any streaming workload and get a broker name in under 60 seconds.
Senior interview question on S3-Kafka category selection
A senior interviewer often opens with: "You inherit a 50 TB/day Apache Kafka deployment on AWS costing $60k/month. The workload is mostly log ingest and CDC replication — no sub-10 ms latency requirement. Walk me through the migration you'd propose to an object-storage-native broker, the vendor choice, the wire-protocol compatibility check, the cutover plan, and the failure modes you'd guard against."
Solution Using an AutoMQ target with Kafka MirrorMaker 2 cutover, S3 durability boundary, and virtual-cluster isolation
# Step 1 - target AutoMQ cluster on EKS, one virtual cluster per team
# automq-values.yaml (Helm)
automq:
cluster:
name: prod-warehouse
kraftReplicas: 3
brokerReplicas: 6 # stateless; scales on CPU alone
storage:
type: s3
bucket: acme-automq-wal
region: us-east-1
# S3 stream WAL - producer batches flush every ~500ms
flushIntervalMs: 500
maxBatchSizeMb: 32
metadata:
# KRaft controller quorum; small footprint
walBucket: acme-automq-metadata
virtualClusters:
- name: logs # log-firehose topics
quota: 300MBps
- name: cdc # CDC replication topics
quota: 150MBps
- name: analytics # ad-hoc analytics reads
quota: 50MBps
# Step 2 - MirrorMaker 2 cutover from legacy Apache Kafka to AutoMQ
# mm2.properties
clusters = source, target
source.bootstrap.servers = kafka-legacy.internal:9092
target.bootstrap.servers = automq-prod.internal:9092
source->target.enabled = true
source->target.topics = .*
source->target.groups = .*
# Preserve message keys, headers, offsets in a translation table
source->target.emit.checkpoints.enabled = true
source->target.emit.heartbeats.enabled = true
source->target.sync.topic.acls.enabled = true
source->target.replication.factor = 3
source->target.tasks.max = 16
# Exactly-once during cutover
source->target.exactly.once.support = enabled
# Step 3 - producer smoke test against AutoMQ
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=["automq-prod.internal:9092"],
acks="all", # wait for S3 durability
enable_idempotence=True,
max_in_flight_requests_per_connection=5,
linger_ms=100, # let AutoMQ batch aggressively
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)
for i in range(1000):
producer.send("logs.app.smoke", {"seq": i, "msg": "hello automq"})
producer.flush()
-- Step 4 - end-to-end lag monitoring (via Kafka JMX -> Prometheus)
-- Alert examples:
-- automq_broker_s3_put_latency_p99_ms > 1500 for 5m
-- automq_topic_end_to_end_lag_seconds{topic=~"logs.*"} > 5 for 10m
-- automq_virtual_cluster_quota_utilization > 0.9 for 15m
Step-by-step trace.
| Step | Before (Apache Kafka) | After (AutoMQ on EKS + S3) |
|---|---|---|
| Broker count | 30× m6i.4xlarge (NVMe) | 6× m6i.large stateless pods |
| Storage cost | ~$28k/month NVMe + 3× cross-AZ | ~$8k/month S3 (storage + PUT/GET) |
| Compute cost | ~$25k/month always-on | ~$3k/month autoscaled |
| Rebalance risk | hours during scale | seconds; brokers stateless |
| Latency p95 | ~5 ms | ~250 ms |
| ISR / replication | 3× write bandwidth | S3 is the durability boundary |
| MirrorMaker cutover | N/A | 48 h dual-write; then flip clients |
| Client changes | N/A | zero (Kafka wire protocol) |
After the migration, the 50 TB/day workload runs on 6 stateless AutoMQ brokers plus S3, the client codebase is unchanged, and the monthly bill drops from ~$60k to ~$11k. The MirrorMaker 2 topology dual-writes for a 48-hour bake-in window, then the DNS entry for the bootstrap servers is flipped from kafka-legacy to automq-prod and consumers resume from the translated offsets.
Output:
| Metric | Before | After |
|---|---|---|
| Monthly cost | ~$60,000 | ~$11,000 |
| Brokers | 30 | 6 (stateless) |
| Latency p95 | ~5 ms | ~250 ms |
| Rebalance duration | hours | seconds |
| Ops burden | high (ZK / ISR / disk) | low (S3 + K8s) |
| Client changes | N/A | zero |
Why this works — concept by concept:
- S3 stream WAL — AutoMQ's core primitive. Producer batches accumulate in a bounded in-memory buffer, flush every ~500 ms to S3 as an ordered stream segment, and the broker's own local disk holds no durable state. This collapses the "how much NVMe per broker" question to zero and makes broker restarts instant.
- KRaft controller quorum — a small 3-node quorum coordinates metadata (topic configs, partition assignments, consumer group offsets). Because the data plane is stateless, the controller quorum can be small and cheap — kilobytes of metadata per topic rather than gigabytes of segment state.
- Virtual clusters + per-topic quotas — one physical AutoMQ deployment can host multiple logical clusters with independent throughput SLOs. Log firehoses get 300 MB/s, CDC gets 150 MB/s, analytics reads get 50 MB/s — noisy-neighbour isolation without buying separate hardware.
- Kafka wire protocol preserved — the entire client ecosystem (KafkaProducer, KafkaConsumer, Kafka Connect, ksqlDB, Kafka Streams) works unchanged. The migration is a bootstrap-server DNS flip, not a client-code rewrite. This is the moat that makes S3-Kafka a realistic bet.
- Cost — S3 PUT ($5/million requests) + S3 storage ($0.023/GB-month) + stateless compute (auto-scaled by CPU) replace NVMe provisioning + 3× cross-AZ bandwidth + always-on brokers. For a 50 TB/day workload the delta is ~5–6× cheaper. The eliminated cost is the "broker count grows with retention days" tax. Net O(bytes) rather than O(bytes × replication factor × brokers).
Streaming
Topic — streaming
Streaming Kafka and object-storage-broker problems
2. AutoMQ deep dive
automq kafka is the Apache 2.0 drop-in replacement — same wire protocol, stateless brokers on top of an S3 stream WAL, deployed on Kubernetes
The mental model in one line: automq is the Apache 2.0-licensed Kafka fork that replaces the local-disk segment store with an S3 stream WAL, keeps the KRaft controller quorum for metadata, deploys as a stateless Kubernetes workload, and preserves the entire Kafka wire protocol so every existing client — producer, consumer, Kafka Connect, ksqlDB, Kafka Streams — connects unchanged. It is the open-source counterpart to WarpStream: same architectural pattern (object storage as the durability boundary, stateless brokers), same latency profile (~100–500 ms), but shipped as source you can fork and self-host rather than a proprietary agent tied to a vendor control plane. AutoMQ originated inside Alibaba Cloud, was open-sourced in 2023, and hit production readiness with the 1.x releases in 2024.
The four axes for AutoMQ.
-
Latency. ~100–500 ms end-to-end p95 in steady state. The dominant term is the ~500 ms in-memory batching window (tunable via
flushIntervalMs); the S3 PUT itself lands in ~50–150 ms on a warm connection. Tail latency spikes correlate with S3 request-rate throttling and cold-partition first-read misses (the segment must be fetched from S3 rather than the broker's in-memory cache). - Cost per GB. S3 PUT ($5 per million requests, so batching matters) + S3 storage ($0.023/GB-month) + S3 GET for consumers ($0.40 per million requests) + a small stateless compute footprint. For a 500 MB/s producer + 2× consumer read fan-out, AutoMQ typically lands at ~$0.02/GB versus ~$0.15/GB for a comparable Apache Kafka deployment on NVMe + cross-AZ replication.
- Licensing. Apache 2.0 — you can fork, self-host, embed, redistribute. This is the biggest differentiator versus WarpStream (proprietary BYOC) and Freight (proprietary managed). For teams with an "OSS-only" policy or a strong preference for no vendor lock-in, AutoMQ is the only serious answer in the S3-Kafka category.
- Ops surface. Kubernetes-native (Helm chart, operator), no ZooKeeper (KRaft only), no per-broker disk sizing. The ops surface is S3 (bucket policy, request-rate throttling, cross-region replication if needed), Kubernetes (pod scheduling, autoscaling), and the KRaft controller quorum (3 nodes, small).
The S3 stream WAL — AutoMQ's core primitive.
-
What it is. A per-topic-partition ordered log of segments, each segment ~32 MB by default, stored as an S3 object under a deterministic key scheme (
s3://bucket/wal/{topic}/{partition}/{segment-offset}). Producers write into an in-memory buffer on the broker; a background flush thread rolls the buffer into an S3 object every ~500 ms (or when the buffer fills to 32 MB, whichever comes first). -
Durability boundary. S3 itself — specifically, the S3 PUT completion. AutoMQ acknowledges a producer's
acks=allwrite only after the containing segment PUT returns success from S3. There is no ISR quorum, no cross-AZ replication bill; the durability is delegated to S3's 11-nines model. -
Read path. Consumers request
partition, offsetfrom the broker; if the offset is in the broker's in-memory cache (hot path), it serves immediately; otherwise it fetches the segment from S3, caches it, and serves. Cold-partition first reads take ~150 ms of S3 GET latency; subsequent reads from the same segment are cache hits. - Compaction. Log-compacted topics are handled by background workers that read old segments from S3, compact by key, write new merged segments back to S3, and update the metadata service to point at the new segments. The old segments are then TTL'd. Same guarantees as Apache Kafka log compaction; different physical implementation.
The KRaft controller quorum — small and cheap.
- What it does. Tracks topic configurations, partition assignments, consumer group offsets, ACLs. It is a Raft consensus group (typically 3 or 5 nodes) that persists to a small local disk (metadata only, kilobytes per topic).
- Why it's small. Because the data plane is stateless — the brokers hold no segment data — the controller quorum only needs to coordinate metadata. A 3-node quorum on m6i.large instances is enough for thousands of topics; you never need to scale it separately from the brokers.
- Bootstrap. The first controller-quorum node initialises the metadata log; subsequent nodes join via the standard KRaft replication protocol. On upgrade, rolling restarts are safe because the log is quorum-replicated.
The Kubernetes deployment story.
- Helm chart. AutoMQ ships an official Helm chart and a Kubernetes operator. The chart provisions the KRaft controller StatefulSet, the broker Deployment (stateless — because brokers are stateless), the S3 bucket integration, and the service endpoints.
-
Autoscaling. Brokers scale on CPU alone. Because there is no local state to drain, adding a broker is a
kubectl scalecommand; removing a broker is akubectl delete podthat finishes in seconds. - Storage. Only the KRaft controller nodes need persistent volumes (small — 20 GB is usually enough). Brokers are truly stateless and use ephemeral volumes for caching only.
Common interview probes on AutoMQ.
- "What's the licensing model?" — required answer: Apache 2.0; fork-able.
- "How does AutoMQ compare to WarpStream?" — required answer: same architecture pattern; AutoMQ is OSS + self-host, WarpStream is proprietary BYOC.
- "What's the cost story?" — S3 storage + PUT + stateless compute; typically 5–10× cheaper than local-disk Kafka.
- "Where does AutoMQ break?" — sub-10 ms latency workloads; heavy Kafka Streams state-store topologies.
- "How do you deploy it?" — Helm chart on Kubernetes; stateless brokers autoscale on CPU.
Worked example — deploying AutoMQ on EKS with an S3 bucket
Detailed explanation. The canonical AutoMQ deployment: provision an S3 bucket for the stream WAL, install the AutoMQ Helm chart on EKS, wire the broker pods to the bucket via IAM Roles for Service Accounts (IRSA), and verify with a producer smoke test. Walk through the whole thing.
-
Prerequisites. EKS 1.28+, an S3 bucket in the same region as the cluster, an IAM role with
s3:PutObject,s3:GetObject,s3:ListBucket,s3:DeleteObjecton the bucket, IRSA configured to bind that role to the broker's service account. -
Chart.
automq/automq-for-kafkaHelm chart from the official repo. - Configuration. 3 KRaft controllers, 6 stateless brokers, 500 ms flush interval, 32 MB segment size.
- Smoke test. A Python producer publishes 1000 messages; a consumer reads them; end-to-end latency logged.
Question. Provide the S3 bucket policy, the IRSA binding, the Helm values, and the smoke-test producer.
Input.
| Component | Value |
|---|---|
| EKS version | 1.28 |
| S3 bucket | acme-automq-wal (us-east-1) |
| Broker replicas | 6 |
| Controller replicas | 3 |
| Flush interval | 500 ms |
| Segment size | 32 MB |
Code.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AutoMQWALAccess",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:AbortMultipartUpload"
],
"Resource": [
"arn:aws:s3:::acme-automq-wal",
"arn:aws:s3:::acme-automq-wal/*"
]
}
]
}
# IRSA binding - service account annotated with role ARN
apiVersion: v1
kind: ServiceAccount
metadata:
name: automq-broker
namespace: automq
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/automq-broker-role
---
# Helm values.yaml
image:
repository: automqinc/automq
tag: 1.4.0
kraft:
replicas: 3
storage:
size: 20Gi
storageClassName: gp3
broker:
replicas: 6
serviceAccount:
name: automq-broker
resources:
requests:
cpu: 2
memory: 4Gi
limits:
cpu: 4
memory: 8Gi
s3:
bucket: acme-automq-wal
region: us-east-1
flushIntervalMs: 500
segmentSizeMb: 32
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 70
# smoke_test.py - verify producer -> broker -> S3 -> consumer round trip
from kafka import KafkaProducer, KafkaConsumer
from kafka.admin import KafkaAdminClient, NewTopic
import json, time
BOOTSTRAP = "automq-broker.automq.svc.cluster.local:9092"
# 1. Create a topic
admin = KafkaAdminClient(bootstrap_servers=BOOTSTRAP)
admin.create_topics([NewTopic("smoke.test", num_partitions=6, replication_factor=1)])
# 2. Produce 1000 messages
producer = KafkaProducer(
bootstrap_servers=[BOOTSTRAP],
acks="all",
enable_idempotence=True,
linger_ms=100,
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)
t0 = time.time()
for i in range(1000):
producer.send("smoke.test", {"seq": i, "ts": time.time()})
producer.flush()
produce_ms = (time.time() - t0) * 1000
print(f"Produced 1000 messages in {produce_ms:.0f} ms")
# 3. Consume them back
consumer = KafkaConsumer(
"smoke.test",
bootstrap_servers=[BOOTSTRAP],
group_id="smoke-test-group",
auto_offset_reset="earliest",
value_deserializer=lambda v: json.loads(v.decode("utf-8")),
consumer_timeout_ms=10000,
)
seen = 0
latencies_ms = []
for msg in consumer:
latencies_ms.append((time.time() - msg.value["ts"]) * 1000)
seen += 1
if seen >= 1000:
break
latencies_ms.sort()
p50, p95, p99 = latencies_ms[500], latencies_ms[950], latencies_ms[990]
print(f"Consumed {seen} messages; end-to-end p50={p50:.0f} ms, p95={p95:.0f} ms, p99={p99:.0f} ms")
Step-by-step explanation.
- The S3 bucket policy grants the AutoMQ broker role the minimum set of S3 actions it needs — PUT, GET, DELETE, LIST, and AbortMultipartUpload (for large segment uploads that get interrupted). No wildcard
s3:*; interviewers will notice if you grant broader permissions than needed. - IRSA binds the Kubernetes service account
automq-brokerto the IAM role via the EKS OIDC provider. This is the modern, credentialless way to give pods AWS access — no static access keys, no instance profiles, no per-pod secrets. - The Helm values provision 3 KRaft controllers (small persistent volumes for the metadata log) and 6 stateless brokers with autoscaling enabled up to 20 replicas. The
flushIntervalMs: 500is the batching window that dominates the end-to-end latency; lower values reduce latency but increase S3 PUT costs. - The smoke test creates a topic with 6 partitions (one per broker in the initial deployment), produces 1000 messages with
acks=all(wait for S3 durability), then consumes them back and computes p50/p95/p99 end-to-end latency. Expect ~200 ms p50, ~500 ms p95, ~1000 ms p99 on a fresh deployment. - In production you'd wire Prometheus scrapes for the AutoMQ JMX metrics (
automq_broker_s3_put_latency_ms,automq_topic_end_to_end_lag_seconds,automq_virtual_cluster_quota_utilization), configure alerts on p99 S3 PUT latency > 1500 ms sustained, and tuneflushIntervalMsper virtual cluster (500 ms for latency-sensitive log topics, 2000 ms for cost-optimised archive topics).
Output.
| Component | Value | Health check |
|---|---|---|
| KRaft controllers | 3 pods, 20 GB PVC each | kubectl get pods -l app=automq-controller |
| Brokers | 6 pods, autoscaled to CPU 70% | kubectl get hpa automq-broker |
| S3 bucket | acme-automq-wal | `aws s3 ls s3://acme-automq-wal/wal/ |
| Producer p95 latency | ~500 ms end-to-end | JMX metric |
| Producer p99 latency | ~1000 ms | JMX metric |
| Broker restart time | < 5 s (stateless) | {% raw %}kubectl rollout restart
|
Rule of thumb. For any AutoMQ deployment, use IRSA for S3 access (never static keys), enable autoscaling from day one (the whole point is stateless brokers), and start with flushIntervalMs: 500 (tune down for latency-sensitive topics via virtual-cluster overrides). Ship the JMX-to-Prometheus scrape and the p99 S3 PUT latency alert before you route production traffic.
Worked example — quantifying the S3 cost model versus local-disk Kafka
Detailed explanation. Every senior architect must run the cost math before signing off on AutoMQ. The dominant terms are S3 storage, S3 PUT requests (drives producer flush tuning), and S3 GET requests (drives consumer fan-out). Compute is small because brokers are stateless. Walk through the calculation for a 500 MB/s producer + 2× consumer fan-out workload with 7-day retention.
- Producer throughput. 500 MB/s = 43 TB/day = ~300 TB retained (7 days).
- Segment size. 32 MB — so 500 MB/s / 32 MB = ~16 PUTs/sec = 1.4M PUTs/day.
- Consumer fan-out. 2× read → 2× GET fan-out, but with page-cache hit ratio ~90% (only ~10% of reads miss the broker cache and hit S3).
- Broker footprint. 6 m6i.large brokers on-demand + autoscaling.
Question. Compute the monthly AWS bill for AutoMQ versus the equivalent Apache Kafka deployment.
Input.
| Component | AutoMQ | Apache Kafka (baseline) |
|---|---|---|
| Producer throughput | 500 MB/s | 500 MB/s |
| Retention | 7 days | 7 days |
| Storage | S3 Standard | NVMe (gp3) + 3× cross-AZ |
| Replication cost | 0 (S3 handles) | 3× write bandwidth billed |
| Broker count | 6 (autoscale) | 30 (m6i.4xlarge) |
Code.
# cost_model.py - AutoMQ vs Apache Kafka on AWS
GB_PER_DAY = 43_000 # 500 MB/s * 86400 / 1024
RETENTION_DAYS = 7
STORAGE_GB = GB_PER_DAY * RETENTION_DAYS # ~300 TB
# S3 pricing (us-east-1, list price)
S3_STORAGE_PER_GB_MONTH = 0.023
S3_PUT_PER_1000 = 0.005
S3_GET_PER_1000 = 0.0004
# AutoMQ side
puts_per_day_millions = (500 * 1024 * 1024 / (32 * 1024 * 1024)) * 86400 / 1_000_000
gets_per_day_millions = puts_per_day_millions * 2 * 0.1 # 2x fan-out, 90% cache hit
automq_s3_storage = STORAGE_GB * S3_STORAGE_PER_GB_MONTH
automq_s3_puts = puts_per_day_millions * 30 * (S3_PUT_PER_1000 * 1000)
automq_s3_gets = gets_per_day_millions * 30 * (S3_GET_PER_1000 * 1000)
automq_compute = 6 * 24 * 30 * 0.096 # m6i.large on-demand
automq_total = automq_s3_storage + automq_s3_puts + automq_s3_gets + automq_compute
# Apache Kafka side (m6i.4xlarge + 3x NVMe + cross-AZ)
kafka_compute = 30 * 24 * 30 * 0.768 # m6i.4xlarge
kafka_storage = STORAGE_GB * 3 * 0.08 # gp3 * 3x replication
kafka_cross_az = GB_PER_DAY * 30 * 3 * 0.01 # 3x cross-AZ egress
kafka_total = kafka_compute + kafka_storage + kafka_cross_az
print(f"AutoMQ monthly: ${automq_total:,.0f}")
print(f" storage : ${automq_s3_storage:,.0f}")
print(f" puts : ${automq_s3_puts:,.0f}")
print(f" gets : ${automq_s3_gets:,.0f}")
print(f" compute : ${automq_compute:,.0f}")
print(f"Kafka monthly: ${kafka_total:,.0f}")
print(f" compute : ${kafka_compute:,.0f}")
print(f" storage : ${kafka_storage:,.0f}")
print(f" x-AZ : ${kafka_cross_az:,.0f}")
print(f"AutoMQ is {kafka_total/automq_total:.1f}x cheaper")
Step-by-step explanation.
- Storage dominates AutoMQ's bill for long-retention topics. 300 TB × $0.023/GB-month = ~$7,000/month. Cutting retention from 7 days to 3 days cuts that number proportionally — the single biggest lever on AutoMQ cost.
- PUT requests are the second-largest AutoMQ cost. 16 PUTs/sec × 86400 × 30 = ~41M PUTs/month = ~$210/month. This is why the 500 ms batching window matters: dropping to 100 ms would 5× the PUT count and 5× that line item.
- GET requests are typically tiny because of the broker's page cache — most consumer reads hit the in-memory cache, not S3. Only cold-partition first reads and consumer rewinds trigger S3 GETs. Fan-out ratio × cache-miss ratio × 30 days gives the monthly GET cost — usually under $100/month for well-cached workloads.
- Apache Kafka's cost is dominated by two terms: (a) broker compute at 30× m6i.4xlarge = ~$16k/month always-on, and (b) 3× cross-AZ replication egress at ~$12k/month. Neither term exists in the AutoMQ model — the S3 durability boundary eliminates cross-AZ replication, and stateless brokers autoscale down when traffic drops.
- The typical result is AutoMQ landing at 5–10× cheaper for high-throughput long-retention workloads. For low-throughput or short-retention topics the delta shrinks (fixed control-plane cost dominates); for very high-throughput topics the delta grows (S3 economics win harder).
Output.
| Cost line | AutoMQ | Apache Kafka | Delta |
|---|---|---|---|
| Storage | ~$7,000 | ~$72,000 (300 TB × 3× × $0.08) | 10× cheaper |
| Replication / cross-AZ | $0 | ~$12,000 | eliminated |
| Compute | ~$400 (autoscaled) | ~$16,000 (30 m6i.4xlarge) | 40× cheaper |
| Requests (PUT/GET) | ~$300 | $0 | new line |
| Total monthly | ~$7,700 | ~$100,000 | ~13× cheaper |
Rule of thumb. Before signing off on AutoMQ, run the cost model on your real throughput and retention. If storage is the dominant term, tighten retention. If PUT is the dominant term, widen the flush window. If compute is non-trivial, revisit autoscaling. The math almost always favours AutoMQ for log-firehose workloads and almost never favours it for sub-10 ms latency workloads.
Worked example — the S3 request-rate throttling failure mode
Detailed explanation. The single biggest operational hazard of AutoMQ is S3 request-rate throttling on hot prefixes. S3 partitions by key prefix, and each prefix has a soft limit of ~3,500 PUT and ~5,500 GET requests per second. AutoMQ's default key scheme (wal/{topic}/{partition}/{segment-offset}) can hit this limit on a single hot topic. Every senior architect ships prefix-spreading on day one.
-
The mechanism. S3 auto-scales prefixes but reactively — a sudden spike on a new topic can throttle for the first few minutes. The default AutoMQ prefix
wal/topic-X/partition-0/...is one S3 prefix; a hot topic + partition combination can saturate it. -
The symptom.
SlowDown(503) errors from S3, producer latency spikes to 5+ seconds, JMX metricautomq_broker_s3_put_error_rategoes non-zero. -
The fix. Prepend a hash prefix to spread keys across S3 partitions:
wal/{hash(topic,partition) % 256:02x}/{topic}/{partition}/{segment}. Configure vias3.keyPrefixStrategy: hashedin Helm values.
Question. Configure AutoMQ's key-prefix strategy for a workload with 4 known hot topics that each do 200 MB/s and would otherwise saturate a single S3 prefix.
Input.
| Concern | Before | After |
|---|---|---|
| Key scheme | wal/{topic}/{partition}/{segment} |
wal/{hash:02x}/{topic}/{partition}/{segment} |
| S3 prefixes used | 4 (one per topic) | ~256 (hashed across topics) |
| PUT rate per prefix | ~50/s (near soft limit) | ~1/s (comfortably below) |
| Error rate under spike | 5–10% SlowDown | ~0% |
Code.
# Helm values.yaml - enable hashed key prefix
s3:
bucket: acme-automq-wal
region: us-east-1
flushIntervalMs: 500
segmentSizeMb: 32
keyPrefixStrategy: hashed
keyPrefixCardinality: 256 # spread across 256 S3 partitions
# Companion: S3 bucket policy also allows abort-multipart-upload
# so half-written segments during throttle back-off get cleaned up
monitoring:
s3:
slowDownAlertThreshold: 0.001 # alert if >0.1% requests SlowDown
# Runbook check - grep JMX for S3 error rate
import requests
def check_s3_throttle():
resp = requests.get("http://automq-broker:9404/metrics")
for line in resp.text.splitlines():
if line.startswith("automq_broker_s3_put_error_rate"):
_, value = line.split()
rate = float(value)
if rate > 0.001:
return f"THROTTLE: S3 PUT error rate {rate:.3%} - check keyPrefixStrategy"
return "OK"
print(check_s3_throttle())
Step-by-step explanation.
- S3 partitions its keyspace by prefix. Each prefix has a soft limit of ~3,500 PUT/s and ~5,500 GET/s before returning
503 SlowDown. The default AutoMQ key scheme (wal/{topic}/{partition}/{segment}) puts all of a topic's traffic on one prefix — fine for cool topics, fatal for a topic doing 100+ MB/s. - The fix is a hash prefix: prepend
hash(topic, partition) mod N(typically N=256) so each topic-partition combination lands on a different S3 partition. This spreads a hot topic across ~256 S3 prefixes, giving effectively unlimited PUT throughput. - The trade-off is human-readability:
wal/a7/orders/0/00000123.logis less scannable thanwal/orders/0/00000123.log. AutoMQ's admin CLI abstracts this —automq-cli topic describe ordersstill shows segments by topic, not by hash prefix. - The alert to ship is
automq_broker_s3_put_error_rate > 0.001(0.1% throttled) for 5 minutes sustained. Below that threshold, S3's auto-repartition typically catches up within a minute or two. Above it, you need to checkkeyPrefixStrategyandkeyPrefixCardinality. - In extreme cases (10+ GB/s workloads), even 256 prefixes can saturate; in that case bump
keyPrefixCardinalityto 1024 or 4096. The cost is metadata bookkeeping (the controller quorum tracks which prefix each segment lives on); the benefit is effectively unlimited S3 request rate.
Output.
| Prefix cardinality | Max sustained topic throughput | When to use |
|---|---|---|
| 1 (default topic-only) | ~50–100 MB/s | Cool topics, low-throughput |
| 256 (recommended default) | ~10–20 GB/s per bucket | Most production workloads |
| 1024 | ~50 GB/s per bucket | Very high-throughput |
| 4096 | Effectively unlimited | Extreme workloads; test S3 support |
Rule of thumb. Ship keyPrefixStrategy: hashed and keyPrefixCardinality: 256 on day one of every AutoMQ deployment — the human-readability cost is trivial, the throttle-avoidance benefit is huge. Add the s3_put_error_rate alert; treat any sustained SlowDown as a paging incident until you know the cause.
Senior interview question on AutoMQ deployment
A senior interviewer might ask: "You need to deploy AutoMQ on EKS to replace a 30-broker Apache Kafka cluster ingesting 500 MB/s of application logs. Design the deployment — S3 bucket + IAM, KRaft quorum, broker autoscaling, virtual-cluster isolation for three teams, cost-versus-latency knobs, and the operational runbook when S3 throttles."
Solution Using EKS + IRSA + hashed S3 prefix + virtual-cluster quotas + autoscaling
# 1. Terraform - S3 bucket + IAM role for AutoMQ brokers
resource "aws_s3_bucket" "automq_wal" {
bucket = "acme-automq-wal"
}
resource "aws_iam_role" "automq_broker" {
name = "automq-broker-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE:sub" = "system:serviceaccount:automq:automq-broker"
}
}
}]
})
}
resource "aws_iam_role_policy" "automq_s3" {
role = aws_iam_role.automq_broker.id
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject",
"s3:ListBucket", "s3:AbortMultipartUpload"]
Resource = [aws_s3_bucket.automq_wal.arn,
"${aws_s3_bucket.automq_wal.arn}/*"]
}]
})
}
# 2. Helm values - production AutoMQ deployment
image:
repository: automqinc/automq
tag: 1.4.0
kraft:
replicas: 3
storage:
size: 50Gi
storageClassName: gp3
broker:
replicas: 6
serviceAccount:
create: true
name: automq-broker
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/automq-broker-role
resources:
requests: { cpu: 2, memory: 4Gi }
limits: { cpu: 4, memory: 8Gi }
s3:
bucket: acme-automq-wal
region: us-east-1
flushIntervalMs: 500
segmentSizeMb: 32
keyPrefixStrategy: hashed
keyPrefixCardinality: 256
virtualClusters:
- name: logs
quota: { produceMBps: 300, consumeMBps: 600, connections: 5000 }
- name: cdc
quota: { produceMBps: 150, consumeMBps: 300, connections: 1000 }
- name: analytics
quota: { produceMBps: 50, consumeMBps: 100, connections: 500 }
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 30
targetCPUUtilizationPercentage: 70
monitoring:
prometheus:
enabled: true
scrapeInterval: 30s
# 3. Prometheus alerts (excerpt)
- alert: AutoMQS3PutErrorRateHigh
expr: automq_broker_s3_put_error_rate > 0.001
for: 5m
annotations:
runbook: check keyPrefixCardinality; bump to 1024 if needed
- alert: AutoMQE2ELagHigh
expr: automq_topic_end_to_end_lag_seconds > 10
for: 10m
annotations:
runbook: check consumer lag; check broker CPU; check S3 latency
- alert: AutoMQVCQuotaExceeded
expr: automq_virtual_cluster_quota_utilization > 0.95
for: 5m
annotations:
runbook: throttle the noisy tenant or bump the VC quota
Step-by-step trace.
| Layer | Config | Result |
|---|---|---|
| IAM | IRSA + narrow S3 role | credentialless broker → S3 auth |
| KRaft | 3 replicas, 50 GB PVC | small metadata quorum; survives node failure |
| Brokers | 6 → 30 autoscale on CPU 70% | stateless; scale in seconds |
| S3 | hashed prefix, 256 cardinality | ~10+ GB/s per bucket possible |
| VC quotas | logs 300 MBps, cdc 150 MBps, analytics 50 MBps | tenant isolation without extra clusters |
| Alerts | S3 PUT error, E2E lag, VC quota | on-call catches degradation in minutes |
After the rollout, the 30-broker Apache Kafka cluster is replaced by a 6-broker AutoMQ deployment that autoscales to 30 pods during peak; monthly AWS bill drops from ~$60k to ~$8k; producer latency p95 rises from ~5 ms to ~250 ms (acceptable for log workloads); rebalance events (previously multi-hour) become instant pod restarts.
Output:
| Metric | Value |
|---|---|
| Broker footprint | 6 pods baseline; up to 30 on autoscale |
| Monthly cost (AWS list) | ~$8,000 (was ~$60,000) |
| Producer p95 latency | ~250 ms |
| Broker restart time | < 5 s |
| S3 request rate | ~50 PUT/s per prefix (well below throttle) |
| VC quota enforcement | per-topic; on-call alert at 95% |
Why this works — concept by concept:
- IRSA credentialless S3 access — no static AWS keys in the pod, no instance-profile broad grants; the exact scoped IAM role is bound via the EKS OIDC provider to the specific service account. Best practice for any AWS workload in EKS; non-negotiable for AutoMQ.
- Stateless broker autoscaling — because brokers hold zero durable state, CPU-based HPA works cleanly. Traditional Kafka can't do this — adding a broker triggers a partition rebalance that takes hours; AutoMQ finishes in seconds because there's no local state to move.
- Hashed S3 key prefix — spreads a hot topic's PUT traffic across ~256 S3 partitions, effectively removing the per-prefix throttle as a bottleneck. Ship on day one; retrofit is expensive.
- Virtual clusters + per-tenant quotas — one physical AutoMQ deployment hosts three logical clusters (logs / cdc / analytics) with independent throughput SLOs. Noisy-neighbour isolation without buying separate hardware.
- Cost — 6 m6i.large baseline (~$400/month) + S3 storage (~$7k/month) + S3 requests (~$300/month) ≈ $8k/month total. Compared to 30 m6i.4xlarge + 3× cross-AZ replication (~$60k/month), the delta is ~7× at the same throughput. The eliminated cost is the "brokers scale with retention days" tax and the "3× cross-AZ replication bill". O(bytes stored) rather than O(bytes × brokers × replication).
Streaming
Topic — streaming
Streaming AutoMQ and S3-Kafka problems
3. Confluent Freight tier
confluent freight is the managed S3-backed cluster type inside Confluent Cloud — ~100 ms p95 latency, ~90% cheaper than Dedicated for high-throughput topics
The mental model in one line: confluent freight is the cluster tier Confluent introduced in Confluent Cloud in 2024 that sits alongside Basic, Standard, Enterprise, and Dedicated — it targets high-throughput, latency-tolerant workloads (log firehoses, CDC replication, IoT telemetry, metrics ingestion) with a ~100 ms p95 latency floor and pricing dominated by GB ingested rather than always-on cluster units, delivering a roughly 90% cost reduction versus Dedicated on the workloads where sub-10 ms is not required. It is the managed answer to the S3-Kafka category question — you get the AutoMQ/WarpStream architecture pattern without running any of it yourself, at Confluent's price point rather than the raw-S3 price point.
The four axes for Confluent Freight.
- Latency. ~100 ms p95 in steady state. Confluent's public guidance describes Freight as "seconds-scale" (with p95 usually well under 500 ms) versus the sub-10 ms of Dedicated. The floor comes from the same physics as AutoMQ and WarpStream — producer buffering + batched S3 flush. For any workload with a latency budget above 500 ms, Freight is the least-effort answer inside Confluent Cloud.
- Cost. Confluent's own marketing puts Freight at "up to 10× cheaper than Dedicated for high-throughput workloads" — meaning ~90% cost reduction on the total cluster bill for workloads that were previously oversized on Dedicated because of throughput requirements rather than latency requirements. Pricing is dominated by GB ingested (not always-on cluster units), which changes the cost curve fundamentally.
- Licensing / lock-in. Fully managed inside Confluent Cloud. You do not run any infrastructure; you do not see the S3 buckets or the underlying compute. The lock-in cost is your entire streaming stack — data is inside Confluent's control plane, migration off requires MirrorMaker or a similar dual-write.
- Ops surface. Effectively zero from your side. Confluent runs the brokers, the metadata service, the S3 buckets, the compaction workers, the schema registry integration. You provision a cluster in the Confluent Cloud console, get bootstrap servers back, and connect. The Confluent SLA covers availability; your on-call is only for producer/consumer application code.
The Confluent Cloud cluster-type ladder — how Freight fits in.
- Basic. Development / low-throughput. Shared multi-tenant cluster; pay only for what you use. Not for production.
- Standard. Multi-tenant production; suitable for moderate throughput (<20 MB/s per topic). Cheap per GB but throughput-capped.
- Enterprise. Multi-tenant with enhanced security (VPC peering, PrivateLink, encryption-key management). Same latency profile as Standard.
- Dedicated. Single-tenant, always-on, sub-10 ms p99 latency. The workhorse for latency-sensitive production. Priced by "Cluster Kafka Units" (CKU) — you pay for capacity whether you use it or not.
- Freight. Single-tenant (per-workload isolation) but S3-backed. Sub-second p99 in steady state; priced dominantly by GB ingested. The 2026 default for high-throughput non-latency-critical topics — moving Dedicated log-firehose topics to Freight is the single biggest lever on a Confluent Cloud bill.
When Confluent recommends Freight over Dedicated.
- Log firehoses. Application logs, access logs, audit logs. High throughput, seconds-scale latency budget, long retention — a perfect Freight fit.
- CDC replication. Debezium-to-warehouse pipelines. The warehouse consumer batches every few minutes anyway; sub-second broker latency is wasted.
- IoT telemetry. Millions of devices posting every 30 s. High aggregate throughput, low per-device bandwidth, latency-tolerant.
- Metrics ingestion. Prometheus remote-write, StatsD-style event streams. The consumer batches into TSDB every 30 s.
- Not Freight. Real-time bidding (RTB), trading, session tracking, user-facing notifications. Anything with a sub-100 ms end-to-end budget stays on Dedicated.
Common interview probes on Confluent Freight.
- "What's the latency floor?" — required answer: ~100 ms p95; sub-10 ms stays on Dedicated.
- "How is it priced?" — GB ingested rather than always-on CKUs; the shape of the bill changes.
- "How do I migrate a Dedicated topic to Freight?" — MirrorMaker 2 or Cluster Linking; wire-protocol compatibility means no client changes.
- "Which workloads should stay on Dedicated?" — sub-100 ms latency budgets, heavy Streams state stores, RTB / trading.
- "Is Freight open source?" — no; proprietary managed inside Confluent Cloud. AutoMQ / WarpStream are the OSS / BYOC counterparts.
Worked example — sizing a Freight cluster for a 500 MB/s log-firehose topic
Detailed explanation. The canonical Freight sizing exercise: you have a 500 MB/s log firehose currently running on a 6-CKU Dedicated cluster costing ~$18k/month. The latency budget is 2 s (Elasticsearch sink batches every 5 s). Model the move to Freight, project the cost, and validate the latency budget. Walk through the sizing conversation with a Confluent solution architect.
- Current state. 6-CKU Dedicated, ~500 MB/s ingest, 7-day retention (~300 TB), 2× consumer fan-out (Elasticsearch + S3 archive).
- Target state. Freight cluster, same throughput, same retention, sub-500 ms p95 latency acceptable.
- Confluent Cloud console. Provision a Freight cluster in the same region, get bootstrap servers, verify latency.
- Migration. Cluster Linking (Confluent's native) or MirrorMaker 2 during a 48-hour cutover.
Question. Design the Freight cluster provisioning, project the monthly cost, and outline the cutover plan.
Input.
| Parameter | Value |
|---|---|
| Throughput | 500 MB/s in / 1000 MB/s out (2× fan-out) |
| Retention | 7 days (~300 TB) |
| Latency budget p95 | 2 s (Elasticsearch batching) |
| Current cluster | 6-CKU Dedicated, ~$18k/month |
| Target region | us-east-1 |
Code.
# 1. Provision Freight cluster via Confluent CLI
confluent kafka cluster create prod-freight-logs \
--cloud aws \
--region us-east-1 \
--type freight \
--availability multi-zone
# 2. Get bootstrap servers
confluent kafka cluster describe lkc-abc123 --output json | jq -r '.endpoint'
# → SASL_SSL://pkc-xxxx.us-east-1.aws.confluent.cloud:9092
# 3. Create service account + API key for producers/consumers
confluent iam service-account create logs-producer --description "Log firehose producer"
confluent api-key create --resource lkc-abc123 --service-account sa-xxxx
# 4. Cluster Linking - mirror from Dedicated to Freight during cutover
# Runs on the Freight (destination) cluster
confluent kafka link create dedicated-to-freight-link \
--source-cluster lkc-oldded123 \
--source-bootstrap-server SASL_SSL://pkc-yyyy.us-east-1.aws.confluent.cloud:9092 \
--cluster lkc-abc123
# 5. Mirror topics
confluent kafka mirror create logs.app.raw \
--link dedicated-to-freight-link \
--cluster lkc-abc123
# 6. Cost projection - Dedicated vs Freight
# Dedicated pricing: per CKU per hour + storage per GB per month
CKU_PER_HOUR = 2.50
STORAGE_PER_GB_MONTH_DED = 0.10
INGRESS_PER_GB_DED = 0.11
EGRESS_PER_GB_DED = 0.11
# Freight pricing (illustrative)
INGRESS_PER_GB_FREIGHT = 0.015 # GB ingested dominates
STORAGE_PER_GB_MONTH_FGT = 0.02 # near-S3 pricing passthrough
EGRESS_PER_GB_FREIGHT = 0.03
GB_INGRESS_MONTH = 500 * 86400 * 30 / (1024 * 1024 * 1024) # ~1.24M GB
GB_EGRESS_MONTH = GB_INGRESS_MONTH * 2 # 2x fan-out
STORAGE_GB_MONTH = GB_INGRESS_MONTH * 7 / 30 # 7-day retention
dedicated_bill = (6 * CKU_PER_HOUR * 24 * 30
+ GB_INGRESS_MONTH * INGRESS_PER_GB_DED
+ GB_EGRESS_MONTH * EGRESS_PER_GB_DED
+ STORAGE_GB_MONTH * STORAGE_PER_GB_MONTH_DED)
freight_bill = (GB_INGRESS_MONTH * INGRESS_PER_GB_FREIGHT
+ GB_EGRESS_MONTH * EGRESS_PER_GB_FREIGHT
+ STORAGE_GB_MONTH * STORAGE_PER_GB_MONTH_FGT)
print(f"Dedicated: ${dedicated_bill:,.0f}/month")
print(f"Freight: ${freight_bill:,.0f}/month")
print(f"Saving: {100 - freight_bill/dedicated_bill*100:.0f}%")
Step-by-step explanation.
- Provisioning a Freight cluster is a one-command operation in the Confluent CLI — you pick the tier (
--type freight), region, and availability (multi-zone is the default and matches Dedicated's HA guarantee). No sizing decision on brokers or storage; Freight autoscales invisibly on Confluent's side. - The service-account + API-key pattern is identical to Dedicated. Producers and consumers authenticate via SASL/SSL with an API key; the wire protocol is unchanged; existing Kafka clients connect by swapping the bootstrap-server string.
- Cluster Linking is Confluent's native cross-cluster replication. It's simpler than MirrorMaker 2 (no separate Connect cluster to run) but only works between Confluent Cloud clusters. The dual-write window is where you bake in and validate; typical is 48 hours before flipping DNS/config.
- Cost projection dominates any Freight conversation. On this workload, Dedicated runs ~$18k/month (dominated by 6 CKU × $2.50/hr × 720 hr = $10.8k, plus ~$3k storage, plus ~$5k in/out). Freight lands at ~$3–4k/month — the CKU line goes to zero, storage drops 5× (near-S3 pricing), in/out drops ~7× (GB ingested is the dominant term but at a lower rate).
- The migration is the risky step. Cluster Linking mirrors the topics; consumers can be flipped one at a time by updating their bootstrap servers. Producers flip last (once you're sure the mirror is caught up), and then the old Dedicated cluster gets decommissioned after a 7-day retention window.
Output.
| Line item | Dedicated (6 CKU) | Freight | Delta |
|---|---|---|---|
| CKU × hours | ~$10,800 | $0 | eliminated |
| Storage (300 TB × 30 d) | ~$3,000 | ~$600 | 5× cheaper |
| Ingress (1.24M GB) | ~$1,400 | ~$190 | 7× cheaper |
| Egress (2.48M GB) | ~$2,700 | ~$370 | 7× cheaper |
| Monthly total | ~$17,900 | ~$1,160 | ~15× cheaper |
Rule of thumb. For any Confluent Cloud Dedicated topic where the p95 latency budget is above 500 ms, moving to Freight is the single biggest cost lever available. Model it before signing off on the next Dedicated CKU expansion; the math almost always favours Freight.
Worked example — the workload-classification exercise for a Dedicated-to-Freight migration
Detailed explanation. No one moves an entire Confluent Cloud cluster from Dedicated to Freight in one step. The realistic migration is a per-topic classification exercise: audit every topic, tag it "keeps latency requirement" (stays on Dedicated) or "tolerates latency" (moves to Freight), then plan the two-cluster steady state. Walk through the audit for a hypothetical e-commerce Kafka footprint.
- The audit. For every topic, capture producer throughput, consumer type, latency SLO, retention, and business criticality.
- The tag. "Dedicated" (sub-100 ms budget) vs "Freight" (100 ms – 5 s budget) vs "Reconsider" (missing SLO — must define before migrating).
- The output. A per-topic move plan with an estimated cost delta.
Question. Classify a 40-topic Kafka footprint and produce the Dedicated-vs-Freight breakdown.
Input.
| Topic | Producer MB/s | Consumer type | Latency SLO | Tag |
|---|---|---|---|---|
| checkout.events | 20 | live stock service | 50 ms | Dedicated |
| payments.txn | 15 | fraud service | 100 ms | Dedicated |
| logs.app.raw | 200 | ES sink + S3 archive | 5 s | Freight |
| cdc.postgres.orders | 80 | Snowflake sink | 60 s (batch) | Freight |
| metrics.prom.raw | 50 | TSDB writer | 30 s | Freight |
| iot.devices.telemetry | 150 | features pipeline | 10 s | Freight |
| notifications.email | 5 | mailer worker | 2 s | Freight |
| audit.actions | 3 | S3 archive only | 10 min | Freight |
| rtb.bids | 30 | live bidder | 10 ms | Dedicated |
| sessions.web.click | 40 | session tracker | 500 ms | Reconsider |
Code.
# workload_classifier.py - Dedicated vs Freight decision helper
def classify(latency_slo_ms: int, throughput_mbps: float) -> str:
"""Tag a Kafka topic as Dedicated, Freight, or Reconsider."""
if latency_slo_ms is None:
return "Reconsider (missing SLO)"
if latency_slo_ms < 100:
return "Dedicated (sub-100 ms budget)"
if latency_slo_ms < 500:
# In the ambiguous zone; consider Freight if throughput is high
if throughput_mbps >= 20:
return "Freight (throughput dominant; latency loose)"
return "Dedicated (moderate throughput; latency tight-ish)"
return "Freight (latency loose)"
topics = [
("checkout.events", 20, 50),
("payments.txn", 15, 100),
("logs.app.raw", 200, 5000),
("cdc.postgres.orders", 80, 60000),
("metrics.prom.raw", 50, 30000),
("iot.devices.telemetry",150, 10000),
("notifications.email", 5, 2000),
("audit.actions", 3, 600000),
("rtb.bids", 30, 10),
("sessions.web.click", 40, 500),
]
for name, tp, slo in topics:
print(f"{name:<30} {tp:>4} MB/s SLO {slo:>6} ms → {classify(slo, tp)}")
Step-by-step explanation.
- The audit's most important output is the "Reconsider" bucket. Any topic whose latency SLO is undefined or was invented on the spot must have its SLO nailed down before the move — otherwise a Freight migration ships a subtle regression that only surfaces when a downstream consumer complains.
- The unambiguous Freight candidates are the log firehose, the CDC replication, the metrics ingestion, the IoT telemetry, the notifications topic, and the audit stream. Every one has a latency budget well above 500 ms and a batching consumer.
- The unambiguous Dedicated stays are the checkout events (50 ms), the payment transactions (100 ms), and the RTB bids (10 ms). These are the load-bearing sub-100 ms workloads where Dedicated's local-disk brokers earn their keep.
- The ambiguous case is
sessions.web.clickat 500 ms — right on the boundary. The classifier tags it "Reconsider" pending an owner decision. In practice, a session-tracker consumer that batches every 200 ms could move to Freight; one that acts on every event might not. - The end state is a two-cluster architecture: a smaller Dedicated cluster (checkout, payments, RTB) and a much larger Freight cluster (everything else). Total bill drops by 60–80% versus running everything on Dedicated because Freight absorbs the high-throughput long-retention majority.
Output.
| Tag | Topic count | Aggregate throughput | Est. monthly cost |
|---|---|---|---|
| Dedicated (stays) | 3 (checkout, payments, RTB) | ~65 MB/s | ~$5,000 |
| Freight (moves) | 6 (logs, cdc, metrics, iot, notifications, audit) | ~488 MB/s | ~$1,800 |
| Reconsider | 1 (sessions) | 40 MB/s | pending decision |
| Total post-migration | 10 audited | ~593 MB/s | ~$6,800 (was ~$25,000) |
Rule of thumb. Never migrate a Confluent Cloud cluster from Dedicated to Freight in one shot. Audit topic by topic, tag by latency SLO and throughput, run Dedicated + Freight side-by-side, and migrate the unambiguous Freight candidates first. The Reconsider bucket must be reconciled with the topic owners before it moves.
Worked example — Cluster Linking cutover from Dedicated to Freight
Detailed explanation. Cluster Linking is Confluent's native cross-cluster mirror. It runs on the destination cluster (no separate Connect infrastructure) and mirrors topics with byte-for-byte fidelity, including headers, timestamps, and offsets. For a Dedicated-to-Freight migration inside Confluent Cloud, it's dramatically simpler than MirrorMaker 2 — one command per topic, one status check per topic, one truncation when you're ready to cut over. Walk through the cutover for a single high-value topic.
-
Source.
logs.app.rawon a Dedicated clusterlkc-oldded, 200 MB/s ingest. -
Destination.
logs.app.rawmirrored to Freight clusterlkc-newfrt. - Cutover. Once the mirror lag is < 1 s sustained, flip producer bootstrap servers; wait 5 min for producer ramp-up; flip consumer bootstrap servers; decommission the source topic after retention window.
Question. Write the Cluster Linking commands, the mirror-lag monitoring, and the cutover checklist.
Input.
| Component | Value |
|---|---|
| Source cluster | lkc-oldded (Dedicated) |
| Destination cluster | lkc-newfrt (Freight) |
| Topic | logs.app.raw |
| Cutover latency | < 1 s mirror lag before flip |
| Retention window | 7 days for decommission |
Code.
# 1. Create Cluster Link (destination-side)
confluent kafka link create prod-mirror-link \
--cluster lkc-newfrt \
--source-cluster lkc-oldded \
--source-bootstrap-server SASL_SSL://pkc-oldded.us-east-1.aws.confluent.cloud:9092 \
--config bootstrap.servers=pkc-oldded.us-east-1.aws.confluent.cloud:9092
# 2. Mirror the topic
confluent kafka mirror create logs.app.raw \
--link prod-mirror-link \
--cluster lkc-newfrt
# 3. Verify mirror lag continuously
confluent kafka mirror describe logs.app.raw \
--link prod-mirror-link \
--cluster lkc-newfrt
# Output includes: mirror_lag_ms, mirror_status (ACTIVE / PAUSED / FAILED)
# 4. Cutover automation - wait for lag then flip clients
import subprocess, time, json
MAX_LAG_MS = 1000
while True:
out = subprocess.check_output([
"confluent", "kafka", "mirror", "describe", "logs.app.raw",
"--link", "prod-mirror-link", "--cluster", "lkc-newfrt",
"--output", "json"
])
info = json.loads(out)
lag = info.get("mirror_lag_ms", 99999)
print(f"mirror lag = {lag} ms")
if lag < MAX_LAG_MS:
print("Mirror caught up; safe to cut over")
break
time.sleep(10)
# 5. Flip producer bootstrap servers (via config service / DNS)
# ... company-specific mechanism ...
# 6. Wait 5 min for producer traffic to fully move
time.sleep(300)
# 7. Promote the mirror (stop mirroring; make destination writable)
subprocess.check_call([
"confluent", "kafka", "mirror", "promote", "logs.app.raw",
"--link", "prod-mirror-link", "--cluster", "lkc-newfrt"
])
# 8. Flip consumer bootstrap servers
# ... company-specific mechanism ...
Step-by-step explanation.
- The Cluster Link is a destination-side object: it runs on the Freight cluster and pulls from the Dedicated cluster. This is the opposite of MirrorMaker 2 (which runs as a separate Connect worker); Cluster Linking is fully managed by Confluent Cloud on both ends.
- Mirroring a topic (
kafka mirror create) makes the destination topic read-only and byte-for-byte identical to the source — same partition count, same message ordering, same offsets. The read-only property is important: it prevents consumers from accidentally writing to the destination before the cutover. - The mirror-lag check is the cutover gate. You never flip clients while the mirror is behind; the classic footgun is "flip producers to the new cluster, then flip consumers, and the consumers rewind to yesterday's data because the mirror hadn't caught up." Under 1 s sustained lag for 10 minutes is a reasonable pre-flip criterion.
- Producer cutover goes first because it stops new writes to the source. Once producers point at the destination, the source stops receiving new data; the mirror catches up quickly; then consumers can flip safely.
-
kafka mirror promoteis the irreversible step: it stops the mirror and makes the destination topic writable. Do this only after the producer flip has been observed successful for 5+ minutes. After promotion, the source topic can be decommissioned after its retention window expires (7 days for this workload).
Output.
| Cutover step | Duration | Risk if wrong |
|---|---|---|
| Create link + mirror | 5 min | none (mirror is passive) |
| Wait for lag < 1 s | 30–120 min | none |
| Flip producers | 5 min DNS | dual-write during propagation |
| Wait for producer bake | 5 min | monitor Dedicated ingest → 0 |
| Promote mirror | 1 command | irreversible; must be sure |
| Flip consumers | 5 min DNS | consumer lag briefly spikes |
| Decommission source | after 7 days | free up Dedicated CKUs |
Rule of thumb. Never flip clients until the mirror lag has been below 1 s sustained for 10+ minutes. Always flip producers before consumers. Never promote the mirror until producer traffic is confirmed at zero on the source. Cluster Linking makes each step trivial; the risk lives entirely in the ordering.
Senior interview question on Confluent Freight
A senior interviewer might ask: "You inherit a Confluent Cloud Dedicated cluster with 30 topics costing $50k/month. The CTO wants a 50% cost reduction within a quarter. Walk me through the audit, the Freight migration plan, the topics that must stay on Dedicated, the Cluster Linking cutover per topic, and the observability you'd add to catch a regression."
Solution Using a per-topic audit + Cluster Linking cutover + steady-state two-cluster architecture
# 1. Audit every topic - producer rate + consumer type + owner
confluent kafka topic list --cluster lkc-oldded --output json | \
jq -r '.[] | .topic_name' | \
while read topic; do
confluent kafka topic describe "$topic" --cluster lkc-oldded --output json
done > topic-audit.json
# 2. Tag each topic (Dedicated vs Freight vs Reconsider) - human review
# (produces migration-plan.csv)
# 3. Automated migration executor
import csv, subprocess, time, json
def mirror_topic(topic: str, src: str, dst: str, link: str) -> None:
subprocess.check_call([
"confluent", "kafka", "mirror", "create", topic,
"--link", link, "--cluster", dst
])
def wait_for_lag(topic: str, dst: str, link: str, max_ms: int = 1000) -> None:
while True:
out = subprocess.check_output([
"confluent", "kafka", "mirror", "describe", topic,
"--link", link, "--cluster", dst, "--output", "json"
])
lag = json.loads(out).get("mirror_lag_ms", 99999)
if lag < max_ms:
return
time.sleep(10)
def promote_and_cutover(topic: str, dst: str, link: str) -> None:
subprocess.check_call([
"confluent", "kafka", "mirror", "promote", topic,
"--link", link, "--cluster", dst
])
with open("migration-plan.csv") as f:
for row in csv.DictReader(f):
if row["tag"] != "Freight":
continue
topic = row["topic"]
mirror_topic(topic, "lkc-oldded", "lkc-newfrt", "prod-mirror-link")
wait_for_lag(topic, "lkc-newfrt", "prod-mirror-link")
# (external DNS flip triggered here)
input(f"Flip producers for {topic}, press Enter when done")
time.sleep(300)
promote_and_cutover(topic, "lkc-newfrt", "prod-mirror-link")
input(f"Flip consumers for {topic}, press Enter when done")
print(f"{topic} migrated to Freight")
# 4. Observability - Confluent Cloud metrics + per-topic dashboards
# (Confluent Cloud Metrics API export to Prometheus)
- alert: FreightP95LatencyRegressed
expr: confluent_kafka_e2e_latency_p95_ms{cluster="lkc-newfrt"} > 2000
for: 10m
annotations:
runbook: check per-topic flush interval; may need to bump virtual-cluster tier
- alert: MirrorLagStalling
expr: confluent_kafka_mirror_lag_ms > 60000
for: 10m
annotations:
runbook: check source cluster egress rate; check destination ingest quota
- alert: MonthlySpendVariance
expr: confluent_cloud_projected_monthly_cost_usd > previous_month * 1.1
for: 1h
annotations:
runbook: unexpected topic promoted to Dedicated? check topic tags
Step-by-step trace.
| Layer | Component | Purpose |
|---|---|---|
| Audit | topic-audit.json + migration-plan.csv | classify every topic |
| Link | Cluster Link on Freight destination | mirror source topics |
| Cutover | producer → wait → promote → consumer | zero-downtime move |
| Observability | Confluent Metrics API → Prometheus | regression detection |
| Steady state | two clusters: lkc-oldded (sub-100ms) + lkc-newfrt (500ms+) | right-sized per workload |
| Decommission | after 7-day retention window | free up Dedicated CKUs |
After the migration, 26 of 30 topics live on Freight (aggregate ~450 MB/s); 3 topics (checkout, payments, RTB) stay on a shrunk 2-CKU Dedicated cluster; 1 topic (sessions) stays on Dedicated pending an SLO review. Monthly bill drops from ~$50k to ~$14k — a 72% reduction, well past the CTO's 50% target. Consumer end-to-end latency for the Freight topics rises from ~5 ms to ~250 ms, comfortably within each topic's SLO.
Output:
| Metric | Before | After |
|---|---|---|
| Cluster count | 1 (Dedicated 6-CKU) | 2 (Dedicated 2-CKU + Freight) |
| Monthly cost | ~$50,000 | ~$14,000 |
| Topics on Dedicated | 30 | 4 |
| Topics on Freight | 0 | 26 |
| Latency p95 (Freight topics) | ~5 ms | ~250 ms |
| Latency p95 (Dedicated topics) | ~5 ms | ~5 ms (unchanged) |
Why this works — concept by concept:
- Per-topic classification — the migration is topic-by-topic, not cluster-by-cluster. Each topic gets tagged Dedicated / Freight / Reconsider based on its actual latency SLO and throughput. Missing SLOs must be reconciled before moving; skipping this step ships subtle regressions.
- Cluster Linking — Confluent's native cross-cluster mirror. Runs on the destination side, no separate Connect infrastructure, byte-for-byte topic fidelity. Simpler and safer than MirrorMaker 2 for Confluent-Cloud-to-Confluent-Cloud migrations.
- Lag-gated cutover — the flip only happens when the mirror lag is under 1 s sustained. This is the correctness gate; ignoring it causes consumer rewind on cutover.
- Two-cluster steady state — sub-100 ms topics stay on a smaller Dedicated cluster; the rest move to Freight. Right-sizing per workload is dramatically cheaper than "one cluster for everything."
- Cost — Freight pricing (dominated by GB ingested) collapses the always-on CKU cost that dominated Dedicated. A 72% cost reduction on a 30-topic footprint is typical when only sub-100 ms topics stay Dedicated. The eliminated cost is "paying for latency capability on topics that don't need it." O(GB ingested) rather than O(CKU × hours).
Streaming
Topic — streaming
Streaming Confluent Cloud and cluster-migration problems
4. Shared architecture pattern
Producer buffer → ~500 ms flush to S3 → metadata-service offsets → background compaction — the same anatomy powers automq, confluent freight, and WarpStream
The mental model in one line: the object-storage-native broker pattern is a four-part anatomy — producers batch writes in a bounded in-memory buffer on the broker side, a background thread flushes each batch to object storage every ~500 ms (or when a segment fills), a small metadata service tracks partition offsets and consumer positions, and background workers compact log-compacted topics by rewriting S3 segments — and every serious s3 backed kafka implementation (AutoMQ, Freight, WarpStream, Redpanda's tiered storage) shares this anatomy, differing only in which of the four parts is proprietary versus open source and which cloud storage backend they primarily target. Understanding this shared pattern lets you reason about any S3-Kafka variant without re-learning it from scratch — it also lets you debug incidents by mapping symptoms ("producer p99 jumped") onto the right layer (buffer / flush / metadata / compaction).
The four layers of the shared anatomy.
-
Producer buffer. On the broker side, incoming producer requests are appended to a bounded in-memory buffer (typically 32 MB per partition). The producer's
acks=allrequest does not return until the containing batch has been flushed to S3 — this is the durability boundary. Buffer tuning is the main lever for the latency/cost trade: smaller buffer = smaller batches = more PUTs = lower latency + higher cost. -
Flush to S3. A background thread rolls the buffer into an S3 object every ~500 ms (or when the buffer fills, whichever comes first). The S3 PUT completion is the acknowledgment moment: once S3 confirms, the broker replies to the producer with
acks=allsuccess, and the segment is durable. Cross-AZ replication is delegated to S3's internal replication (11 nines); there is no ISR quorum. - Metadata service. A small Raft-based (KRaft, Nessie, or vendor-specific) or externally-hosted metadata store tracks: topic configurations, partition assignments, segment locations (which S3 object holds which offset range), consumer group offsets, ACLs. This is the only stateful component that isn't S3-backed; it needs its own durability story (typically a small Postgres or embedded RocksDB).
-
Background compaction. For log-compacted topics (the ones with
cleanup.policy=compact), background workers periodically read old segments from S3, deduplicate by key, write merged segments back to S3, and update the metadata service to point at the new segments. Old segments are then TTL'd. Same semantics as Apache Kafka log compaction; different physical mechanism (S3 read + rewrite instead of local-disk log-cleaner).
How consumer offsets are stored.
- In the metadata service. Consumer group offsets (which offset each consumer has committed for each partition) live in the metadata service, not on S3. This is a hot-write / hot-read workload — every consumer commits every few seconds — so it lives close to the coordinator for low latency.
-
The __consumer_offsets topic. For Apache Kafka wire-protocol compatibility, the metadata service exposes the offsets via a virtual
__consumer_offsetstopic — clients don't know the offsets aren't in a real Kafka topic, but the storage is actually the metadata service's Raft log. - Durability. The metadata service is Raft-replicated (usually 3 or 5 nodes) with a small local-disk footprint. Losing a majority of the quorum is the only way to lose offsets; losing any brokers doesn't lose offsets because they're not stored on brokers.
How compaction works in the S3 model.
- The trigger. Configurable per topic (default: every N minutes or when compactable ratio exceeds threshold). A compaction job picks up a set of S3 segments for a partition, reads them in offset order, keeps only the most recent value for each key, and writes a new merged segment.
- The atomicity. Compaction commits by (a) writing new segment(s) to S3, (b) atomically updating the metadata service to point the partition's segment map at the new segments, (c) marking the old segments for TTL deletion. Consumers reading the partition during compaction see either the pre-compaction or post-compaction view — never a mixed view.
-
The cost. S3 GET (read old segments) + S3 PUT (write new segments) + S3 DELETE (TTL old segments) + compute (the compactor worker). For a heavy-write log-compacted topic (e.g. a Kafka Streams state topic), compaction can be a significant cost line — always check per-topic
compaction.io.bytesmetrics.
How transactions / EOS work in this model.
-
The transaction coordinator. Same as Apache Kafka: a special partition (
__transaction_state) tracks in-flight transactions. Producers begin/commit/abort via the standardInitProducerId,AddPartitionsToTxn,EndTxnprotocol. -
The commit atomicity. A transaction commit writes a control message to every affected partition (including
__consumer_offsetsfor Kafka-Streams-style read-process-write topologies). The S3-Kafka broker orders these writes through the metadata service, so a commit either appears in all partitions or in none. -
Idempotent producers.
enable.idempotence=trueis fully supported — the producer ID + sequence number is tracked in the metadata service (not in the broker's local memory), so producer restarts don't break idempotency guarantees.
Common interview probes on the shared architecture.
- "Where's the durability boundary?" — required answer: the S3 PUT completion; no ISR.
- "What breaks if the metadata service goes down?" — offsets can't be committed; producers hang on
acks=all; consumers can still read committed data. - "How does compaction work?" — background workers rewrite S3 segments; metadata service pointer swap is atomic.
- "Do transactions work?" — yes; same protocol; commit ordering via metadata service.
- "Can I mix hot and cold data on one topic?" — yes; recently written segments cache in the broker's in-memory cache; older segments come from S3 on demand.
Worked example — tracing an end-to-end message through the S3 WAL
Detailed explanation. The clearest way to internalise the shared architecture is to trace a single message end-to-end: producer sends → broker buffers → flush to S3 → metadata update → consumer request → cache miss or hit → response. Walk through the trace for a 1 KB JSON message on an AutoMQ deployment.
-
Producer.
producer.send("orders", key=b"42", value=b"...")withacks=all,linger_ms=100. -
Broker buffer. Message enters the in-memory buffer for partition 3 of
orders; buffer holds 12 MB of accumulated messages. - Flush trigger. After 500 ms (or when buffer hits 32 MB), the flush thread rolls the buffer into an S3 PUT.
-
S3 PUT. ~100 ms — the segment lands at
s3://acme/wal/a7/orders/3/00000000000000012345.log. -
Metadata update. The controller quorum records
orders/3/12345 → 22456 = segment a7/.... -
Acknowledgment. The producer receives
acks=allsuccess ~600 ms after send. -
Consumer.
consumer.poll()requests offset 12345 for partition 3; broker checks in-memory cache; hit (segment cached from write path); returns.
Question. Trace the message end-to-end and label each timing component.
Input.
| Component | Timing |
|---|---|
| Producer batch to broker | ~5 ms network |
| Buffer append | ~microseconds |
| Wait for flush trigger | 0 – 500 ms (avg ~250 ms) |
| S3 PUT | ~100 ms |
| Metadata update | ~10 ms |
| Broker → producer ack | ~5 ms network |
| Consumer request → cache hit | ~5 ms |
| Consumer request → cache miss (S3 GET) | ~150 ms |
Code.
# trace_end_to_end.py - measure per-hop latency
import time, json
from kafka import KafkaProducer, KafkaConsumer
BOOTSTRAP = "automq-broker:9092"
producer = KafkaProducer(
bootstrap_servers=[BOOTSTRAP],
acks="all",
linger_ms=100,
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)
# Send with a callback to measure ack latency
send_times = {}
def on_success(record_metadata):
seq = record_metadata.offset
send_times[seq] = time.time()
for i in range(100):
payload = {"seq": i, "sent_at": time.time()}
future = producer.send("orders", key=b"42", value=payload)
future.add_callback(on_success)
producer.flush()
# Now consume and measure end-to-end
consumer = KafkaConsumer(
"orders",
bootstrap_servers=[BOOTSTRAP],
group_id="e2e-tracer",
auto_offset_reset="latest",
value_deserializer=lambda v: json.loads(v.decode("utf-8")),
consumer_timeout_ms=5000,
)
e2e_latencies_ms = []
for msg in consumer:
e2e_ms = (time.time() - msg.value["sent_at"]) * 1000
e2e_latencies_ms.append(e2e_ms)
e2e_latencies_ms.sort()
p50 = e2e_latencies_ms[len(e2e_latencies_ms) // 2]
p95 = e2e_latencies_ms[int(len(e2e_latencies_ms) * 0.95)]
p99 = e2e_latencies_ms[int(len(e2e_latencies_ms) * 0.99)]
print(f"E2E latency: p50={p50:.0f} ms p95={p95:.0f} ms p99={p99:.0f} ms")
Step-by-step explanation.
- The producer's
linger_ms=100batches messages for up to 100 ms on the client side before sending — this reduces broker-side buffer count but doesn't reduce broker-side flush latency (which is dominated by the 500 ms flush interval, not by client batching). - The broker-side buffer wait is the dominant term for end-to-end latency. On average, a message waits ~250 ms in the buffer before the next flush fires (uniformly distributed between 0 and 500 ms). This is why S3-Kafka's p50 is around 300 ms and p95 is around 500–800 ms.
- The S3 PUT latency (~100 ms for a 32 MB segment) is the second-largest term. Tail latency here (>500 ms PUT) is the main driver of p99 spikes; it correlates with S3 request-rate throttling and AZ-specific S3 degradations.
- The metadata update (~10 ms) is tiny compared to the S3 PUT. This is a design choice: the metadata service is optimised for low-latency writes because it's on the hot path for every commit; it uses local-disk Raft with tight fsync tuning.
- On the consumer side, a message that was just written by the broker is almost always still in the broker's in-memory cache (write-through), so consumer p50 is ~5 ms. Cold-partition first reads or consumer rewinds trigger S3 GETs at ~150 ms — this is where consumer p99 spikes originate.
Output.
| Hop | Timing (typical) | Timing (p99) |
|---|---|---|
| Producer batch to broker | 5 ms | 20 ms |
| Buffer append | <1 ms | <1 ms |
| Wait for flush | 250 ms (avg) | 500 ms |
| S3 PUT | 100 ms | 800 ms |
| Metadata update | 10 ms | 50 ms |
| Broker ack to producer | 5 ms | 20 ms |
| Producer ack total | ~370 ms | ~1400 ms |
| Consumer cache hit | 5 ms | 20 ms |
| E2E (cache hit) | ~375 ms | ~1420 ms |
| Consumer cache miss | 150 ms | 800 ms |
| E2E (cache miss) | ~520 ms | ~2200 ms |
Rule of thumb. For any S3-Kafka workload, the dominant end-to-end latency term is the broker's flush interval (~500 ms max). If you need lower latency, dial the flush interval down at the cost of higher S3 PUT costs. If you need lower cost, dial the flush interval up at the cost of higher latency. The knob is per virtual cluster, so different topics can have different tuning.
Worked example — virtual clusters and per-topic latency SLOs
Detailed explanation. One physical AutoMQ or Freight deployment typically hosts multiple logical clusters — "virtual clusters" in AutoMQ terminology, "cluster tiers within a workload" in Freight terminology. Each virtual cluster has its own quota (throughput MB/s, connection count) and its own tuning knobs (flush interval, segment size). This is how a single deployment serves both a latency-sensitive log topic (200 ms flush) and a cost-optimised archive topic (2 s flush) without buying separate infrastructure. Walk through the configuration.
- Deployment. One AutoMQ physical cluster: 6 brokers, 3 KRaft controllers, one S3 bucket.
-
Virtual clusters.
logs-hot(200 ms flush, 300 MB/s quota),logs-warm(500 ms flush, 200 MB/s quota),cdc-archive(2000 ms flush, 100 MB/s quota). -
Per-topic assignment. Each topic is created with a
virtualClusterproperty that binds it to one of the three configurations. -
Isolation. Quotas are enforced per virtual cluster; a noisy tenant on
logs-hotcannot starvecdc-archive.
Question. Configure three virtual clusters on one AutoMQ deployment with different latency/cost trade-offs and assign topics to them.
Input.
| VC name | Flush interval | Quota (MB/s) | Target workload |
|---|---|---|---|
| logs-hot | 200 ms | 300 | Live tail, latency-sensitive logs |
| logs-warm | 500 ms | 200 | Standard app logs |
| cdc-archive | 2000 ms | 100 | Nightly-batched CDC to S3 |
Code.
# AutoMQ Helm values - three virtual clusters
virtualClusters:
- name: logs-hot
quota:
produceMBps: 300
consumeMBps: 600
connections: 10000
tuning:
flushIntervalMs: 200
segmentSizeMb: 16
- name: logs-warm
quota:
produceMBps: 200
consumeMBps: 400
connections: 5000
tuning:
flushIntervalMs: 500
segmentSizeMb: 32
- name: cdc-archive
quota:
produceMBps: 100
consumeMBps: 200
connections: 2000
tuning:
flushIntervalMs: 2000
segmentSizeMb: 64
# Create topics bound to specific virtual clusters
from kafka.admin import KafkaAdminClient, NewTopic, ConfigResource, ConfigResourceType
admin = KafkaAdminClient(bootstrap_servers=["automq-broker:9092"])
# Create three topics, one per VC
topics = [
NewTopic("logs.frontend.raw", num_partitions=32, replication_factor=1,
topic_configs={"automq.virtual.cluster": "logs-hot"}),
NewTopic("logs.backend.raw", num_partitions=24, replication_factor=1,
topic_configs={"automq.virtual.cluster": "logs-warm"}),
NewTopic("cdc.postgres.raw", num_partitions=12, replication_factor=1,
topic_configs={"automq.virtual.cluster": "cdc-archive"}),
]
admin.create_topics(topics)
# Verify assignment
for t in ["logs.frontend.raw", "logs.backend.raw", "cdc.postgres.raw"]:
res = admin.describe_configs([ConfigResource(ConfigResourceType.TOPIC, t)])
for r in res:
for entry in r.entries.values():
if entry.name == "automq.virtual.cluster":
print(f"{t:<30} → VC {entry.value}")
Step-by-step explanation.
- The three virtual clusters share the same physical brokers but have independent throughput quotas — 300 + 200 + 100 = 600 MB/s aggregate max, which the physical cluster is sized for. When a topic on
logs-hotbursts to 400 MB/s, its VC quota throttles it back to 300, and the other VCs are unaffected. - The
flushIntervalMstuning is the latency/cost lever per VC.logs-hotat 200 ms flushes ~5 times more frequently thancdc-archiveat 2000 ms, so it pays ~5× more in S3 PUT charges but has ~5× lower buffer-wait latency. This is the explicit trade-off senior architects tune per workload. - The
segmentSizeMbknob controls how much data goes in each S3 object. Smaller segments = more objects = higher S3 request count but lower per-object cost. Larger segments = fewer objects but higher rewind granularity (consumers seeking back must fetch entire segments). 16–64 MB is the practical range. - Assigning a topic to a VC is a topic-config property (
automq.virtual.cluster). This is set at topic creation time and is immutable afterwards; changing a topic's VC requires a new topic + Cluster Linking-style mirror. - In Confluent Freight the equivalent knobs are hidden — you don't get per-VC tuning, you get one "Freight cluster" per Kafka cluster and Confluent tunes the flush interval on your behalf based on their measured workload profile. For finer-grained control, AutoMQ (or WarpStream) is the answer.
Output.
| Topic | Virtual cluster | Expected p95 latency | Cost per GB |
|---|---|---|---|
| logs.frontend.raw | logs-hot (200 ms) | ~250 ms | ~$0.025 (higher PUT) |
| logs.backend.raw | logs-warm (500 ms) | ~600 ms | ~$0.018 |
| cdc.postgres.raw | cdc-archive (2000 ms) | ~2200 ms | ~$0.014 (fewer PUTs) |
Rule of thumb. For any AutoMQ or WarpStream deployment with mixed workloads, create at least two virtual clusters — one for latency-sensitive topics (200–500 ms flush) and one for cost-optimised topics (2000+ ms flush). Assign topics to VCs at creation time. This is the cheapest way to right-size latency versus cost per workload.
Worked example — exactly-once semantics in the S3-Kafka model
Detailed explanation. Exactly-once semantics (EOS) in Apache Kafka relies on three primitives: idempotent producers (producer ID + sequence number), transactional producers (transactional.id + transaction coordinator), and the read_committed isolation level on consumers. S3-Kafka preserves all three because the wire protocol is preserved — but the implementation of transactions moves from broker local state to the metadata service, which changes the failure semantics slightly. Walk through a Kafka Streams read-process-write topology on AutoMQ.
-
The topology. Read from
orders.raw, compute a per-user rolling sum, write toorders.per_user_sum, commit atomically. -
The producer.
transactional.id = "orders-aggregator-1". -
The consumer.
isolation.level = "read_committed"— never reads uncommitted messages. - The transaction boundary. Read messages, transform, produce, commit — one atomic unit that either updates the output topic + advances the consumer offset or does neither.
Question. Show the producer configuration, the transactional processing loop, and how AutoMQ enforces the exactly-once guarantee.
Input.
| Component | Value |
|---|---|
| Input topic | orders.raw |
| Output topic | orders.per_user_sum |
| Isolation | read_committed |
| Transactional ID | orders-aggregator-1 |
| Transaction coordinator | AutoMQ metadata service |
Code.
# eos_processor.py - exactly-once Kafka Streams-style loop on AutoMQ
from kafka import KafkaProducer, KafkaConsumer, TopicPartition
from kafka.errors import ProducerFencedError
import json
BOOTSTRAP = "automq-broker:9092"
# 1. Transactional producer
producer = KafkaProducer(
bootstrap_servers=[BOOTSTRAP],
transactional_id="orders-aggregator-1",
enable_idempotence=True, # implicit with transactional_id
acks="all",
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)
producer.init_transactions()
# 2. Read-committed consumer
consumer = KafkaConsumer(
"orders.raw",
bootstrap_servers=[BOOTSTRAP],
group_id="orders-aggregator",
isolation_level="read_committed", # skip uncommitted / aborted messages
enable_auto_commit=False, # we commit manually inside the txn
value_deserializer=lambda v: json.loads(v.decode("utf-8")),
)
per_user_running_sum = {}
# 3. Transactional processing loop
for msgs in iter(lambda: consumer.poll(timeout_ms=1000), None):
if not msgs:
continue
try:
producer.begin_transaction()
offsets_to_commit = {}
for tp, batch in msgs.items():
for msg in batch:
user_id = msg.value["user_id"]
amount = msg.value["amount_cents"]
per_user_running_sum[user_id] = per_user_running_sum.get(user_id, 0) + amount
producer.send("orders.per_user_sum",
key=str(user_id).encode(),
value={"user_id": user_id, "running_sum": per_user_running_sum[user_id]})
offsets_to_commit[tp] = msg.offset + 1
# Commit input offsets + output writes atomically
producer.send_offsets_to_transaction(offsets_to_commit, consumer.config["group_id"])
producer.commit_transaction()
except ProducerFencedError:
# Another instance took over the transactional.id; shut down gracefully
producer.close()
raise
except Exception:
producer.abort_transaction()
raise
Step-by-step explanation.
-
producer.init_transactions()registers thetransactional.idwith AutoMQ's transaction coordinator (a component of the metadata service). If a previous producer with the sametransactional.idis still alive, it gets fenced (its next commit fails withProducerFencedError) — this is how zombie processes are prevented from double-writing. - The consumer's
isolation_level=read_committedtells AutoMQ to skip messages from in-flight or aborted transactions. This means the consumer only sees committed data, which is the "read" half of exactly-once. -
producer.begin_transaction()opens a transaction. Allproducer.send()calls inside the transaction accumulate at the metadata coordinator; consumers withread_committeddon't see them yet. -
producer.send_offsets_to_transaction(...)is the critical step: it packages the input-topic offset commits into the same transaction as the output writes. This is how Kafka Streams-style EOS works — the input offset advance is atomic with the output write. -
producer.commit_transaction()triggers the coordinator to write commit markers to every affected partition (including__consumer_offsetsand both output topics). AutoMQ's metadata service orders these writes so they all commit atomically; consumers see them all at once or none at all.
Output.
| Failure mode | Guarantee | Recovery |
|---|---|---|
| Producer crash mid-transaction | Transaction aborts (marker never written) | Restart consumer; reprocesses from last committed offset |
| Broker crash mid-flush | Transaction still committed (S3 durable) | New broker resumes from S3 |
| Coordinator crash | Client retries commit until quorum reconvenes | No data loss; brief unavailability |
| Zombie producer restart | ProducerFencedError | Old producer exits; new producer takes over |
| Consumer duplicate delivery | Blocked by read_committed + offset commit atomicity | No downstream duplicates |
Rule of thumb. For any S3-Kafka deployment where you need exactly-once, use transactional producers + read_committed consumers + send_offsets_to_transaction for the input-offset commits. The Apache Kafka semantics are preserved because the wire protocol is preserved; the implementation differences (transaction coordinator lives in the metadata service, not on brokers) are transparent to the client.
Senior interview question on the shared architecture
A senior interviewer might ask: "Draw the object-storage-native Kafka broker architecture on the whiteboard. Explain the durability boundary, the compaction mechanism, and how transactions work. Then explain why a broker restart is instant, what breaks if the metadata service goes down, and how you'd tune the latency/cost trade-off per topic."
Solution Using the four-layer anatomy + per-VC tuning + metadata-service transaction coordinator
┌───────────────────────────────────────────────────────────┐
│ Client (producer) │
│ acks=all + linger_ms=100 + enable.idempotence=true │
└──────────────────┬────────────────────────────────────────┘
│ Kafka wire protocol (unchanged)
▼
┌───────────────────────────────────────────────────────────┐
│ Stateless broker (pod / VM) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ In-memory buffer (per partition, ~32 MB) │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ flush every ~500 ms OR when buffer fills │ │
│ └────────────────────┬────────────────────────────────┘ │
└───────────────────────┼───────────────────────────────────┘
│ S3 PUT (~100 ms)
▼
┌───────────────────────────────────────────────────────────┐
│ Object storage (S3 / GCS) │
│ wal/{hash}/{topic}/{partition}/{offset}.log │
│ Durability boundary; 11-nines; cross-AZ handled by S3 │
└───────────────────────┬───────────────────────────────────┘
│ segment-location update
▼
┌───────────────────────────────────────────────────────────┐
│ Metadata service (KRaft quorum) │
│ - Topic configs, partition assignments │
│ - Segment-offset → S3 key map │
│ - Consumer group offsets (__consumer_offsets) │
│ - Transaction coordinator (__transaction_state) │
└───────────────────────────────────────────────────────────┘
Background compactor:
read old segments from S3 → dedupe by key → write new segment →
metadata service pointer swap → TTL old segments
# Per-VC tuning knobs summary
virtualClusters:
- name: hot
tuning:
flushIntervalMs: 200 # lower latency; higher PUT cost
segmentSizeMb: 16
compactionIntervalMinutes: 5
- name: cold
tuning:
flushIntervalMs: 2000 # higher latency; lower PUT cost
segmentSizeMb: 64
compactionIntervalMinutes: 60
# Restart is instant because brokers hold no durable state:
# kubectl rollout restart deployment/automq-broker
# Old pods drain in-flight requests, new pods start empty,
# clients reconnect and receive current segment map from metadata service.
# Total downtime: ~2 s per broker; full rolling restart: ~15 s for 6 brokers.
Step-by-step trace.
| Layer | Component | Purpose |
|---|---|---|
| Producer | KafkaProducer client | wire-protocol-compatible; no changes |
| Broker | stateless pod | buffers producer batches; flushes to S3 |
| S3 | object storage | durability boundary; 11-nines |
| Metadata | KRaft quorum | segment map + offsets + transactions |
| Compactor | background worker | rewrites log-compacted segments in S3 |
| Consumer | KafkaConsumer client | wire-protocol-compatible; read from broker cache or S3 |
After a broker restart, the new broker pod comes up empty, reads the current segment map from the metadata service (~500 ms), and begins accepting client connections. Producers reconnect within a second; consumers resume from their last-committed offset. Because there's no local state to rebuild, no partition rebalance is triggered — the restart is transparent to the workload.
Output:
| Failure event | Traditional Kafka impact | S3-Kafka impact |
|---|---|---|
| Broker restart | 5–30 min (partition rebalance + segment rebuild) | <5 s (stateless; segment map from metadata) |
| Broker permanent loss | 15+ min (ISR promotion + replication catch-up) | <30 s (schedule new pod; connect to S3) |
| AZ outage | risk of ISR quorum loss on affected topics | S3 handles cross-AZ; brokers reschedule in surviving AZs |
| S3 outage | N/A | write-path blocked; read-path serves from cache until exhaustion |
| Metadata service outage | N/A | offset commits blocked; producers hang on acks=all
|
Why this works — concept by concept:
- Producer buffer + timed flush — the ~500 ms flush interval batches many producer requests into one S3 PUT, amortising the PUT cost and the S3 latency across thousands of messages. The trade-off is a fixed floor on end-to-end latency; the win is a 100–1000× reduction in S3 request count.
- S3 as the durability boundary — Apache Kafka's ISR quorum (3× replication for durability) is replaced by S3's internal 11-nines durability. The write completes when S3 PUT completes; there's no cross-AZ replication bill on your side.
- Metadata service for offsets + transactions — a small Raft quorum handles the hot-write path (offset commits, transaction commits) that shouldn't go to S3. This is the only component with local disk; it's small and cheap.
- Background compaction — log-compacted topics are handled by workers that read + rewrite S3 segments and atomically swap the metadata pointer. Same guarantees as Apache Kafka's log-cleaner; different physical mechanism.
- Cost — one 3-node metadata quorum (~$300/month) + stateless brokers (autoscaled) + S3 storage + S3 PUT/GET. The eliminated cost is ISR replication bandwidth, broker-local NVMe, and always-on capacity. O(bytes ingested) rather than O(bytes × replication × brokers).
Streaming
Topic — streaming
Streaming architecture and metadata-service problems
5. Choosing among AutoMQ / Freight / WarpStream / Redpanda / Kafka + interview signals
The five-vendor decision matrix — pick per workload, not per vendor — and the interview signals that separate senior candidates from the rest
The mental model in one line: the 2026 Kafka-compatible broker landscape is a five-way choice — AutoMQ for OSS self-hosted, Confluent Freight for Confluent-Cloud-native, WarpStream for BYOC data-sovereignty, Redpanda for low-latency + tiered storage, and Apache Kafka for pure-ecosystem certifications and heavy Streams state — and the senior architect's job is to pick per workload rather than committing an entire streaming stack to one vendor, because every one of these systems preserves Kafka wire-protocol compatibility so mixing multiple vendors under one client codebase is realistic. The interview probes that separate senior candidates from the rest are: (a) do you name all five without prompting, (b) do you frame the choice as latency-vs-cost-vs-lock-in, and (c) do you know when to reject S3-Kafka.
The five-way decision matrix — one column per vendor, one row per axis.
- AutoMQ. Apache 2.0 OSS; deploys on Kubernetes; S3 stream WAL; ~100–500 ms latency; ~$0.02/GB. Pick when you want self-host with no vendor lock-in and Kafka wire-protocol compatibility. The counterpart to WarpStream but open source.
- Confluent Freight. Proprietary managed inside Confluent Cloud; ~100 ms p95 latency; ~90% cheaper than Dedicated. Pick when you're already Confluent-Cloud-committed and want the cheaper cluster tier for high-throughput non-latency-critical topics.
- WarpStream. BYOC (agent in your VPC, control plane hosted by Confluent post-acquisition); data never leaves your S3; proprietary code. Pick when data sovereignty is a hard requirement — regulated finance, healthcare, government.
- Redpanda. Business Source License 1.1; per-broker local NVMe hot path + S3 tiered storage; sub-10 ms hot latency, ~200 ms cold. Pick when the same cluster must serve both sub-10 ms real-time workloads and cold-archive workloads — the tiered-storage-in-one-cluster pattern.
- Apache Kafka. Apache 2.0 reference implementation; KRaft controller quorum (ZooKeeper removed in 4.0); tiered storage KIP-405; sub-10 ms latency. Pick when the ecosystem is load-bearing — heavy Kafka Streams state stores, Kafka Connect infrastructure investment, certifications-required customers.
When to reject S3-Kafka entirely.
- Sub-10 ms latency mandatory. Trading systems, real-time bidding (RTB), user-facing notifications, session-tracking with per-event side effects. The 100–500 ms floor of object-storage-native brokers is above your budget; stay on Redpanda hot path or Apache Kafka.
- Heavy Kafka Streams state stores. RocksDB-backed state stores on Kafka Streams live on local disk on the Streams instances; the broker choice is orthogonal, but the low-latency requirement of state-store reads / writes often correlates with wanting a low-latency broker.
- Kafka Connect infrastructure investment. If you have hundreds of connectors already managed on a Connect cluster, the broker choice is secondary — but Connect itself benefits from low-latency brokers for source connectors that fan out fast.
- Certifications required. Some regulated industries mandate specific certifications (FedRAMP High, SOC 2 Type II with specific controls). Apache Kafka via a well-known vendor (Confluent Platform, Red Hat AMQ Streams) often ships the certification package cheaper than a newer S3-Kafka vendor can.
The three interview signals that separate senior from mid-level.
- Signal 1: object-storage-first framing. Senior candidates open with "the shared architectural pattern is producer buffer + timed S3 flush + metadata service + background compaction — AutoMQ, Freight, and WarpStream all implement it." Mid-level candidates open with "AutoMQ is like Kafka but uses S3."
- Signal 2: latency-vs-cost trade-off unprompted. Senior candidates say "the 100–500 ms floor is the physics; sub-10 ms workloads stay on Redpanda or local-disk Kafka" without waiting for the follow-up. Mid-level candidates commit to S3-Kafka everywhere and then have to backtrack.
- Signal 3: licensing awareness. Senior candidates distinguish "AutoMQ is Apache 2.0" from "WarpStream is proprietary BYOC" from "Freight is proprietary managed" — and can explain why the license matters (fork-ability, exit cost, TCO in the long run). Mid-level candidates conflate all three.
Migration cost between broker types.
- Apache Kafka → AutoMQ. ~2–4 engineer-weeks. MirrorMaker 2 setup, dual-write bake-in, DNS flip, client-side ack verification. No code changes on producers/consumers.
- Apache Kafka → Freight (via Confluent Cloud migration). ~2 engineer-weeks if you're already in Confluent Cloud, ~6 weeks if you're moving from self-hosted to Confluent Cloud + Freight simultaneously.
- Apache Kafka → WarpStream. ~3 engineer-weeks. Agent deployment in your VPC, control-plane configuration, MirrorMaker cutover.
- Apache Kafka → Redpanda. ~2 engineer-weeks. Redpanda Migrator (their MirrorMaker equivalent), broker-by-broker replacement, no client changes.
- AutoMQ → WarpStream (or vice versa). ~1 engineer-week. Both are wire-compatible; the migration is essentially MirrorMaker between two S3-Kafka clusters.
Common interview probes on vendor choice.
- "Which one would you pick for a greenfield log-firehose?" — required answer: AutoMQ or Freight depending on OSS-vs-managed preference.
- "What's the biggest risk of picking WarpStream?" — proprietary; if Confluent changes pricing you're exposed.
- "Why not just use Redpanda for everything?" — cost — Redpanda's NVMe hot path is more expensive than pure S3-Kafka for cold-tolerant workloads.
- "When does Apache Kafka still win?" — heavy Streams state; ecosystem lock-in; sub-10 ms budgets; certifications.
- "How do you avoid vendor lock-in in this category?" — pick AutoMQ (OSS) or run two clusters (one commercial for managed convenience, one AutoMQ for the OSS exit path).
Worked example — building the five-vendor decision matrix for a real workload
Detailed explanation. The senior architect's most useful tool is a real, filled-in decision matrix with numbers, not a marketing table. Walk through building the matrix for a specific workload — a 200 MB/s CDC replication pipeline with 30-day retention feeding both Snowflake and a real-time Elasticsearch alerts index.
- Workload. 200 MB/s CDC firehose (Debezium output), 30-day retention, two consumers: (a) Snowflake sink batching every 15 min, (b) Elasticsearch alerts with sub-5 s SLO.
- Cloud. AWS us-east-1 with strict "data does not leave our AWS account" policy.
- Team. 4 platform engineers with strong Kubernetes skills, minimal Confluent Cloud experience.
- Budget. Under $5,000/month total infrastructure.
Question. Score all five vendors on this workload and recommend.
Input.
| Vendor | Latency fit | Cost fit | Data sovereignty fit | Team-skills fit | Total |
|---|---|---|---|---|---|
| AutoMQ | 4/5 | 5/5 | 5/5 | 5/5 | 19/20 |
| Confluent Freight | 4/5 | 3/5 | 2/5 (data in Confluent) | 2/5 (new tool) | 11/20 |
| WarpStream | 4/5 | 4/5 | 5/5 | 3/5 | 16/20 |
| Redpanda | 5/5 | 2/5 (NVMe cost) | 5/5 | 4/5 | 16/20 |
| Apache Kafka | 5/5 | 1/5 (~$18k/mo) | 5/5 | 4/5 | 15/20 |
Code.
# vendor_scoring.py - repeatable multi-axis vendor scorer
from dataclasses import dataclass, field
from typing import Callable
@dataclass
class Workload:
throughput_mbps: float
retention_days: int
latency_p95_budget_ms: int
data_sovereignty_required: bool
team_familiarity_kafka: bool
team_familiarity_confluent: bool
monthly_budget_usd: int
@dataclass
class Vendor:
name: str
latency_floor_ms: int
cost_per_gb_stored: float
is_byoc: bool
is_managed: bool
is_oss: bool
def score(v: Vendor, w: Workload) -> tuple[int, dict]:
axes = {}
# Latency fit
axes["latency"] = 5 if v.latency_floor_ms <= w.latency_p95_budget_ms // 2 else \
3 if v.latency_floor_ms <= w.latency_p95_budget_ms else 1
# Cost fit (rough)
storage_gb = w.throughput_mbps * 86400 * w.retention_days / (1024 * 1024 / 1024)
est_month_cost = storage_gb * v.cost_per_gb_stored
axes["cost"] = 5 if est_month_cost <= w.monthly_budget_usd * 0.5 else \
3 if est_month_cost <= w.monthly_budget_usd else 1
# Data sovereignty
if w.data_sovereignty_required:
axes["sovereignty"] = 5 if v.is_byoc or v.is_oss else 2
else:
axes["sovereignty"] = 5
# Team-skills
if v.is_managed:
axes["team"] = 5 if w.team_familiarity_confluent else 3
else:
axes["team"] = 5 if w.team_familiarity_kafka else 3
return sum(axes.values()), axes
workload = Workload(
throughput_mbps=200, retention_days=30,
latency_p95_budget_ms=5000, data_sovereignty_required=True,
team_familiarity_kafka=True, team_familiarity_confluent=False,
monthly_budget_usd=5000,
)
vendors = [
Vendor("AutoMQ", latency_floor_ms=300, cost_per_gb_stored=0.02, is_byoc=False, is_managed=False, is_oss=True),
Vendor("Freight", latency_floor_ms=100, cost_per_gb_stored=0.06, is_byoc=False, is_managed=True, is_oss=False),
Vendor("WarpStream", latency_floor_ms=400, cost_per_gb_stored=0.03, is_byoc=True, is_managed=False, is_oss=False),
Vendor("Redpanda", latency_floor_ms=10, cost_per_gb_stored=0.05, is_byoc=False, is_managed=False, is_oss=False),
Vendor("Kafka", latency_floor_ms=5, cost_per_gb_stored=0.12, is_byoc=False, is_managed=False, is_oss=True),
]
for v in vendors:
total, axes = score(v, workload)
print(f"{v.name:<12} total={total}/20 breakdown={axes}")
Step-by-step explanation.
- Latency scoring is a straightforward comparison of the vendor's latency floor to the workload's budget. AutoMQ / Freight / WarpStream all comfortably meet a 5000 ms budget; Redpanda and Kafka over-deliver but at higher cost.
- Cost scoring estimates monthly bill from throughput × retention × per-GB cost. On this workload, AutoMQ lands well under the $5k budget (~$2k), Kafka lands well over (~$18k), the others in between. The scoring rewards being under 50% of budget (5/5) and penalises being at or over (1/5).
- Data-sovereignty scoring gives 5/5 to BYOC or self-hosted OSS (AutoMQ, WarpStream, self-hosted Kafka, Redpanda self-hosted), 2/5 to fully-managed offerings where data flows through the vendor's control plane (Freight). This axis rules out Freight for this workload.
- Team-skills scoring rewards familiarity with the deployment model. This team is Kubernetes-heavy and knows Kafka; AutoMQ (K8s + Kafka wire) scores highest; Freight (new Confluent Cloud tooling) scores lowest.
- The winner (AutoMQ, 19/20) is the natural fit: OSS, cheap, data stays in the team's AWS account, deploys on Kubernetes the team already knows. WarpStream and Redpanda tie for second at 16/20 — both viable if AutoMQ's rough edges bite. Freight (11/20) is ruled out by the data-sovereignty requirement.
Output.
| Rank | Vendor | Score | Rationale |
|---|---|---|---|
| 1 | AutoMQ | 19/20 | OSS + cheap + BYO + K8s-native |
| 2 (tie) | WarpStream | 16/20 | BYOC + proprietary tax |
| 2 (tie) | Redpanda | 16/20 | Over-engineered for latency; NVMe cost |
| 4 | Apache Kafka | 15/20 | 3× over budget |
| 5 | Confluent Freight | 11/20 | Data-sovereignty fail |
Rule of thumb. For every real Kafka broker choice, build the five-vendor scored matrix with your workload's actual numbers. Marketing tables lie; scored matrices reveal which vendor's strengths match your constraints. If your top vendor scores less than 15/20, question whether the workload is really an S3-Kafka fit.
Worked example — the interview signals rubric
Detailed explanation. The senior Kafka-choice interview has a scoring rubric that separates candidates who understand the category from those who've only used one vendor. Walk through the rubric so you know how you're being graded — and how to grade candidates yourself when the tables are turned.
- Opening question. "Walk me through how you'd pick a Kafka broker for a new workload."
- What weak candidates say. "We'd use Apache Kafka because it's the standard."
- What mid-level candidates say. "We'd look at cost and pick the cheapest option that meets latency."
- What senior candidates say. "There's a five-way choice — AutoMQ / Freight / WarpStream / Redpanda / Kafka — and the answer depends on latency-vs-cost-vs-lock-in-vs-team-skills. Let me build the matrix for the workload."
Question. Score the following four candidate answers using the interview signals rubric.
Input.
| Candidate | Opening statement | Follow-up on latency | Follow-up on OSS-vs-managed |
|---|---|---|---|
| A | "Apache Kafka" | "sub-10ms" | (no answer) |
| B | "Pick cheapest that meets latency" | "S3-Kafka is 100-500ms" | "OSS is cheaper" |
| C | "Depends on workload; probably AutoMQ or Freight" | "S3-Kafka floor is ~500ms" | "AutoMQ is Apache 2.0; Freight is proprietary" |
| D | "Five-way matrix: AutoMQ / Freight / WarpStream / Redpanda / Kafka on latency/cost/lock-in/team" | "S3-Kafka has 100-500ms physics floor; sub-10ms stays on Redpanda hot path" | "AutoMQ Apache 2.0; WarpStream proprietary BYOC; Freight proprietary managed; picks vary by exit cost" |
Code.
# interview_rubric.py - score a candidate's Kafka-choice answer
def score_candidate(names_all_five: bool,
names_latency_floor: bool,
names_license_differences: bool,
frames_as_matrix: bool,
knows_when_to_reject: bool) -> tuple[str, int]:
score = sum([
names_all_five * 3, # senior signal
names_latency_floor * 2, # required answer
names_license_differences * 2,# senior signal
frames_as_matrix * 2, # senior signal
knows_when_to_reject * 1, # differentiator
])
if score >= 8:
return "Senior", score
if score >= 5:
return "Mid-level", score
if score >= 2:
return "Junior", score
return "Weak", score
candidates = [
("A", False, False, False, False, False),
("B", False, True, False, False, False),
("C", False, True, True, False, False),
("D", True, True, True, True, True),
]
for name, *signals in candidates:
level, score = score_candidate(*signals)
print(f"Candidate {name}: {level} ({score}/10)")
Step-by-step explanation.
- Candidate A ("just Apache Kafka") scores 0/10 — no matrix, no latency floor, no license awareness. This is a weak answer that suggests they've only used one broker and haven't kept up with the category shift.
- Candidate B ("cost + latency") scores 2/10 — knows the latency floor but doesn't name vendors or think in terms of a matrix. Junior signal; they've read the AutoMQ blog but haven't shipped.
- Candidate C ("AutoMQ or Freight; know licensing") scores 4/10 — names two vendors, knows the physics, knows the license difference. Mid-level; solid but not comprehensive.
- Candidate D ("five-way matrix + physics + BYOC + when to reject") scores 10/10 — comprehensive matrix framing, physics floor, license depth, exit-cost awareness, knows when S3-Kafka doesn't fit. Senior; would likely also be able to build the workload-scored matrix on the whiteboard live.
- The pattern to learn from this rubric: mention all five vendors unprompted, name the physics floor unprompted, distinguish OSS from BYOC from managed unprompted, and know when to reject the entire category (sub-10 ms + heavy state). Any two of those four gets you to mid-level; all four is senior.
Output.
| Candidate | Score | Level | What was missing |
|---|---|---|---|
| A | 0/10 | Weak | Everything |
| B | 2/10 | Junior | Vendor names, license, matrix |
| C | 4/10 | Mid-level | All five vendors; matrix framing |
| D | 10/10 | Senior | Nothing |
Rule of thumb. Before your next Kafka-broker interview, rehearse the five-vendor matrix walkthrough out loud until it's fluent under 3 minutes. Practice naming all five vendors, the physics floor, the license breakdown, and one workload where you'd reject S3-Kafka entirely. This is a memorisation exercise; the payoff is disproportionate.
Worked example — the multi-cluster steady state
Detailed explanation. In real deployments, senior architects rarely commit an entire streaming stack to one vendor. The realistic 2026 steady state is a multi-cluster architecture: one small Dedicated Apache Kafka or Confluent Dedicated cluster for sub-10 ms workloads, one large S3-Kafka cluster (AutoMQ or Freight) for the long-tail majority, and possibly a WarpStream cluster for the regulated subset. Walk through the topology for a real e-commerce platform.
-
Cluster 1.
kafka-hot— 3-node Apache Kafka on i3.xlarge with NVMe. Serves checkout, payments, RTB. Sub-10 ms p99. ~$3k/month. -
Cluster 2.
automq-warm— 6-broker AutoMQ on EKS. Serves logs, metrics, CDC, notifications. ~100–500 ms p95. ~$8k/month. -
Cluster 3.
warpstream-regulated— 2-agent WarpStream in a dedicated VPC. Serves the regulated PII stream that must stay in the customer's AWS account. ~$4k/month. - Client library. One thin wrapper picks the cluster based on a topic tag; producers/consumers connect to the right cluster automatically.
Question. Design the multi-cluster topology and the client-library wrapper that routes topics to clusters.
Input.
| Cluster | Vendor | Purpose | Latency SLO | Monthly cost |
|---|---|---|---|---|
| kafka-hot | Apache Kafka | Checkout, payments, RTB | <10 ms | ~$3,000 |
| automq-warm | AutoMQ | Logs, metrics, CDC | <500 ms | ~$8,000 |
| warpstream-regulated | WarpStream | PII stream (regulated) | <2 s | ~$4,000 |
Code.
# kafka_router.py - client-library wrapper that routes by topic tag
from kafka import KafkaProducer, KafkaConsumer
from dataclasses import dataclass
@dataclass
class ClusterConfig:
bootstrap_servers: str
security_config: dict
CLUSTERS = {
"hot": ClusterConfig(
bootstrap_servers="kafka-hot.internal:9092",
security_config={"security_protocol": "SASL_SSL",
"sasl_mechanism": "SCRAM-SHA-256"}),
"warm": ClusterConfig(
bootstrap_servers="automq-warm.internal:9092",
security_config={"security_protocol": "SASL_SSL",
"sasl_mechanism": "SCRAM-SHA-256"}),
"regulated": ClusterConfig(
bootstrap_servers="warpstream-regulated.internal:9092",
security_config={"security_protocol": "SASL_SSL",
"sasl_mechanism": "SCRAM-SHA-256"}),
}
# Topic to cluster tag (loaded from central config, e.g. Consul)
TOPIC_TAGS = {
"checkout.events": "hot",
"payments.txn": "hot",
"rtb.bids": "hot",
"logs.app.raw": "warm",
"metrics.prom.raw": "warm",
"cdc.postgres.orders": "warm",
"notifications.email": "warm",
"regulated.pii.stream": "regulated",
}
def build_producer(topic: str, **overrides) -> KafkaProducer:
tag = TOPIC_TAGS[topic]
cluster = CLUSTERS[tag]
return KafkaProducer(
bootstrap_servers=[cluster.bootstrap_servers],
**cluster.security_config,
**overrides,
)
def build_consumer(topic: str, group_id: str, **overrides) -> KafkaConsumer:
tag = TOPIC_TAGS[topic]
cluster = CLUSTERS[tag]
return KafkaConsumer(
topic,
bootstrap_servers=[cluster.bootstrap_servers],
group_id=group_id,
**cluster.security_config,
**overrides,
)
# Usage - callers never think about which cluster
producer = build_producer("checkout.events", acks="all")
producer.send("checkout.events", value=...)
consumer = build_consumer("logs.app.raw", group_id="es-sink")
for msg in consumer:
...
Step-by-step explanation.
- The central
TOPIC_TAGSmapping is the single source of truth for "which cluster does this topic live on." In production, load this from a central config service (Consul, etcd, ConfigMap) so it can be updated without redeploying every client. - The
build_producer/build_consumerfunctions abstract the cluster choice. Application code names a topic; the router picks the right bootstrap servers and security config. This is what makes multi-cluster realistic — no application code has "which cluster?" knowledge baked in. - When a topic migrates between clusters (e.g. a checkout topic being demoted to
warmafter a latency budget review), updateTOPIC_TAGS, run the MirrorMaker cutover, and callers automatically pick up the new cluster on their next producer/consumer create. Long-lived producers should be periodically rebuilt (every hour) to catch config changes. - Security config is per cluster because each vendor may use different auth mechanisms (SCRAM for Apache Kafka, OAuth for Confluent Cloud, mTLS for WarpStream). The wrapper hides this; application code is auth-agnostic.
- The steady-state cost math: $3k + $8k + $4k = $15k/month for three specialised clusters versus ~$40k/month for a single one-size-fits-all Apache Kafka cluster serving the same aggregate throughput. The multi-cluster tax is operational complexity (three clusters to monitor, three sets of upgrade cycles); the multi-cluster benefit is roughly 2.5× cheaper.
Output.
| Cluster | Topics served | Aggregate throughput | Monthly cost |
|---|---|---|---|
| kafka-hot | 3 (checkout, payments, RTB) | ~65 MB/s | ~$3,000 |
| automq-warm | 4 (logs, metrics, cdc, notifications) | ~350 MB/s | ~$8,000 |
| warpstream-regulated | 1 (PII stream) | ~50 MB/s | ~$4,000 |
| Total | 8 topics | ~465 MB/s | ~$15,000 |
Rule of thumb. For any production streaming platform serving mixed latency and regulatory requirements, plan for a multi-cluster steady state. One cluster per requirement class (sub-10 ms, sub-500 ms, BYOC / regulated) with a thin client-library router is dramatically cheaper than one giant cluster serving everything.
Senior interview question on multi-cluster architecture
A senior interviewer might ask: "Your streaming platform serves an e-commerce site with checkout, log ingest, metrics, CDC to warehouse, and a regulated PII stream. Design the broker topology — cluster count, vendor per cluster, client-library routing, migration strategy from an existing single-cluster Apache Kafka deployment, and the observability that catches when a topic is on the wrong cluster."
Solution Using a three-cluster topology (Apache Kafka / AutoMQ / WarpStream) + client-library router + per-topic SLO monitoring
# 1. Client-library router (as above) + central config service
# (Consul or etcd-backed TOPIC_TAGS)
TOPIC_SLA = {
"checkout.events": {"cluster": "hot", "p95_ms": 50},
"payments.txn": {"cluster": "hot", "p95_ms": 100},
"rtb.bids": {"cluster": "hot", "p95_ms": 10},
"logs.app.raw": {"cluster": "warm", "p95_ms": 5000},
"metrics.prom.raw": {"cluster": "warm", "p95_ms": 10000},
"cdc.postgres.orders": {"cluster": "warm", "p95_ms": 30000},
"notifications.email": {"cluster": "warm", "p95_ms": 2000},
"regulated.pii.stream": {"cluster": "regulated", "p95_ms": 5000},
}
# 2. Per-cluster observability - one Prometheus stack, per-cluster labels
- alert: TopicOnWrongCluster
expr: kafka_topic_e2e_p95_ms{topic!=""} > on(topic) topic_sla_p95_ms
for: 15m
annotations:
runbook: consider migrating topic to a lower-latency cluster;
check the TOPIC_TAGS config
- alert: ClusterCostAnomalous
expr: aws_billing_daily_usd{cluster=~"kafka-hot|automq-warm|warpstream-regulated"}
> previous_day * 1.3
for: 1h
annotations:
runbook: check topic count on cluster; check autoscale bounds
- alert: SlowCrossClusterMigration
expr: mirror_lag_ms > 60000
for: 30m
annotations:
runbook: check source ingestion rate;
check destination cluster health;
consider pausing migration
# 3. Migration from single Apache Kafka to three-cluster topology
# Phase 1: stand up automq-warm alongside existing Apache Kafka
# Phase 2: MirrorMaker 2 logs/metrics/cdc/notifications topics
# Phase 3: flip TOPIC_TAGS for one topic at a time; validate SLOs
# Phase 4: stand up warpstream-regulated; MirrorMaker PII stream
# Phase 5: shrink Apache Kafka cluster to hot-only sizing
# Phase 6: decommission unused capacity
# Total migration timeline: 4-6 weeks per 10 topics
Step-by-step trace.
| Layer | Component | Purpose |
|---|---|---|
| Cluster hot | Apache Kafka on i3.xlarge | sub-10ms; 65 MB/s aggregate |
| Cluster warm | AutoMQ on EKS + S3 | 100-500ms; 350 MB/s aggregate |
| Cluster regulated | WarpStream BYOC in dedicated VPC | PII data-sovereignty |
| Client library | build_producer / build_consumer wrapper | topic-to-cluster routing |
| Config | Consul-backed TOPIC_TAGS + TOPIC_SLA | single source of truth |
| Observability | Prometheus with per-cluster labels | latency + cost + migration alerts |
| Migration | phased MirrorMaker + TOPIC_TAGS flips | one topic at a time |
After the rollout, the three-cluster steady state serves the same 8 topics at ~$15k/month versus ~$40k/month on a monolithic cluster; each topic hits its per-topic p95 SLO; the client-library router hides cluster complexity from application code; on-call rotations split per cluster with clear runbooks. Migration from the old single cluster took ~6 weeks phased over 3 quarters, one topic at a time.
Output:
| Metric | Before (single) | After (three-cluster) |
|---|---|---|
| Monthly cost | ~$40,000 | ~$15,000 |
| Cluster count | 1 | 3 |
| Vendor lock-in surface | high (Confluent Dedicated) | low (mixed OSS + managed) |
| Latency SLO adherence | mixed | 100% |
| Ops burden | one team, one cluster | three sub-teams, three clusters |
| Client code complexity | direct client | thin router wrapper |
Why this works — concept by concept:
- Per-workload cluster sizing — sub-10 ms workloads get expensive local-disk brokers only for the topics that need them; the rest run on cheap S3-Kafka. This right-sizing is impossible on a single cluster.
- Client-library router — topic-to-cluster mapping lives in a config service; application code just names topics. This is what makes multi-cluster realistic — the alternative is baking cluster knowledge into every service, which quickly becomes unmaintainable.
- Data sovereignty per cluster — the regulated PII stream lives in its own WarpStream cluster in a dedicated VPC with strict IAM. Compliance auditors can trace the data path without wading through the rest of the streaming stack.
- Independent scaling and upgrades — each cluster upgrades on its own cadence, scales on its own workload, and fails independently. A WarpStream control-plane outage doesn't affect the hot cluster; an AutoMQ pod restart doesn't affect the regulated cluster.
- Cost — three specialised clusters totalling ~$15k/month replace a single ~$40k/month cluster serving the same workload — a ~62% saving. The overhead is operational complexity (three sets of alerts, three runbooks, three upgrade cadences); the benefit is right-sized economics per workload class. O(cost per class) rather than O(cost of the most-expensive class × everything).
Streaming
Topic — streaming
Streaming multi-cluster Kafka problems
Design
Topic — design
Design problems on vendor-agnostic streaming stacks
Cheat sheet — S3-Kafka recipes
- Which broker when. AutoMQ is the 2026 default for greenfield self-hosted deployments — Apache 2.0 license, Kubernetes-native, S3 stream WAL, ~$0.02/GB cost model. Confluent Freight is the default inside Confluent Cloud when your latency budget is above 100 ms — ~90% cheaper than Dedicated on the workloads that fit. WarpStream is the answer when data sovereignty (BYOC) is a hard requirement — agent-in-VPC, control plane hosted by Confluent post-acquisition. Redpanda is the answer when the same cluster must serve both sub-10 ms hot and cold-archive workloads via tiered storage. Apache Kafka stays the answer for sub-10 ms workloads, heavy Kafka Streams state stores, and ecosystem-locked deployments (certifications, deep Kafka Connect investment).
-
AutoMQ deployment on EKS with S3 bucket. Provision an S3 bucket in the same region as the EKS cluster, an IAM role with
s3:PutObject,s3:GetObject,s3:ListBucket,s3:DeleteObject,s3:AbortMultipartUploadscoped to the bucket, IRSA-bind the role to theautomq-brokerservice account. Installautomq/automq-for-kafkaHelm chart withkraft.replicas=3,broker.replicas=6,s3.flushIntervalMs=500,s3.keyPrefixStrategy=hashed,s3.keyPrefixCardinality=256,autoscaling.enabled=truewithtargetCPUUtilizationPercentage=70. Enable Prometheus scrape, ship theautomq_broker_s3_put_error_rate > 0.001alert on day one. Total time: ~1 engineer-day for a smoke-testable deployment. -
Confluent Freight cluster provisioning.
confluent kafka cluster create --cloud aws --region us-east-1 --type freight --availability multi-zone— one command creates the cluster. Confluent provisions the underlying stateless brokers, metadata service, and S3 buckets on their side; you get bootstrap servers back. Migration from Dedicated uses Cluster Linking (confluent kafka link create+kafka mirror create); the client-side change is a bootstrap-server DNS flip. Model the cost before signing: for high-throughput topics Freight is ~10× cheaper than Dedicated on the CKU-driven line items. -
WarpStream BYOC agent + control-plane split. WarpStream Agents run in your VPC (data plane); WarpStream Control Plane runs in Confluent's SaaS (metadata + coordination only). Your S3 buckets, your data — Confluent sees the metadata (schemas, offsets, cluster topology) but never the message bytes. Deployment is
docker run warpstreamlabs/warpstream:latest agent -config /etc/warpstream/config.yamlper agent; scale agents horizontally on producer load. Post the September 2024 Confluent acquisition, WarpStream continues to ship as a discrete product with its "your bytes never leave your account" pitch preserved. - Compatibility matrix — what works on S3-Kafka. KRaft controller quorum: yes on AutoMQ (Apache 2.0 KRaft directly), yes on Freight (Confluent's implementation), yes on WarpStream (proprietary metadata service exposing Kafka wire). ACLs: yes on all three (Kafka wire protocol). Transactions / exactly-once: yes on all three (metadata service acts as transaction coordinator). Kafka Streams: works but heavy state-store workloads perform poorly (state lives on the Streams instance, not the broker). Kafka Connect: works unchanged; source and sink connectors don't know the broker is S3-backed. Schema Registry: works unchanged (Confluent-managed or self-hosted).
- Latency budget by workload class. Sub-10 ms: trading, RTB, real-time notifications — stay on Redpanda hot path or Apache Kafka on i3/i4i NVMe. Sub-100 ms: checkout, payments, session tracking — stay on Apache Kafka or Confluent Dedicated. 100 ms – 5 s: log ingest, CDC replication, IoT telemetry, metrics — S3-Kafka territory (AutoMQ / Freight / WarpStream all fit). 5 s – 1 min: nightly-batched CDC, audit archive — S3-Kafka with generous flush intervals (2000 ms) for maximum cost efficiency. Above 1 min: reconsider Kafka entirely; a plain S3 object store with batch processing might be simpler.
- Cost model per GB/day. AutoMQ / self-hosted on AWS: dominant term is S3 storage ($0.023/GB-month) + S3 PUT ($5 per 1M) + stateless compute (~$0.10/GB/day for typical workloads); total ~$0.02/GB stored per month. Confluent Freight: opaque managed pricing dominated by GB ingested rate (~$0.015/GB) plus a small storage line (~$0.02/GB-month); total ~$0.05/GB/month. WarpStream: your S3 cost + WarpStream control-plane subscription; total ~$0.04/GB/month. Apache Kafka on NVMe with 3× cross-AZ replication: dominant term is EBS gp3 ($0.08/GB-month × 3× replication) + cross-AZ egress ($0.01/GB × 3×) + always-on compute; total ~$0.15/GB/month. The delta between best-case (AutoMQ) and worst-case (Apache Kafka on NVMe) is roughly 7×.
-
Producer/consumer tuning for high-latency broker. Producer:
acks=all(mandatory for durability guarantees),enable.idempotence=true(implicit for exactly-once semantics),linger.ms=100(batches on client side; complements broker-side 500 ms flush),batch.size=1048576(1 MB batches — larger than default),compression.type=zstdorlz4(reduce S3 PUT / storage cost by 3–5×). Consumer:fetch.min.bytes=1048576(1 MB — coalesce fetch calls),fetch.max.wait.ms=500(align with broker flush interval),max.poll.records=5000(batch processing for downstream throughput). Any Kafka-Streams workload should keep default state-store settings; broker choice doesn't affect state stores. -
Failure semantics reminder. S3-Kafka failure surface: (a) S3 outage — write path blocked until S3 returns; read path serves from broker cache until cache empties. (b) Metadata service outage — offset commits blocked; producers with
acks=allhang; consumers can read committed data. (c) Broker pod restart — <5 s of unavailability per pod; no state loss (S3 is durable). (d) AZ loss — brokers reschedule; S3 handles cross-AZ transparently. (e) Cross-region failover — WAL bucket must be cross-region-replicated (S3 CRR); metadata service must have a cross-region standby. Compared to Apache Kafka's ISR quorum + cross-AZ replication, S3-Kafka has fewer failure modes but each mode is more critical (S3 outage is the new "cross-AZ failure"). -
Schema evolution and Confluent Schema Registry compatibility. All three vendors integrate with Confluent Schema Registry (or its OSS equivalent, Apicurio) unchanged — the Registry is a Kafka topic (
_schemas) that lives on any Kafka-wire-compatible broker. Producers register schemas at Registry startup; consumers fetch and cache; Registry handles compatibility (backward, forward, full) via configuration. Schema evolution stories are identical across AutoMQ / Freight / WarpStream / Redpanda / Kafka because the Registry pattern is broker-agnostic. Add Registry to your architecture on day one; retrofitting schema evolution to a schema-less stream is expensive. -
Observability — end-to-end lag, S3 PUT rate, virtual-cluster metrics. JMX metrics common to all S3-Kafka implementations:
kafka.producer:type=producer-metrics,client-id=*/record-send-rate(client-side ingest rate),kafka.consumer:type=consumer-fetch-manager-metrics,client-id=*/records-lag-max(consumer lag), plus vendor-specific: AutoMQautomq_broker_s3_put_latency_ms,automq_topic_end_to_end_lag_seconds,automq_virtual_cluster_quota_utilization. Freight exposes similar via Confluent Cloud Metrics API. Alert on p99 S3 PUT latency > 1500 ms for 5 min, on end-to-end lag > 10 s for 10 min, on VC quota > 95% for 5 min. Never route production traffic without these three alerts wired. - When to reject S3-Kafka. Sub-10 ms latency budget (trading, RTB, user-facing notifications). Heavy Kafka Streams state-store workloads with large RocksDB footprints on the Streams instances. Ecosystem lock-in to Kafka Connect infrastructure that assumes low-latency brokers for source-connector polling. Deep certification requirements (FedRAMP High, specific SOC 2 controls) where a well-known vendor's Apache Kafka + Platform package is the only certified answer. In all these cases, stay on Apache Kafka or Redpanda's hot path.
- Migration cost between broker types. Apache Kafka → AutoMQ: ~2–4 engineer-weeks. MirrorMaker 2 setup, 48-hour dual-write bake-in, DNS flip on bootstrap servers, per-topic latency validation, decommission source after retention. Apache Kafka → Freight (via Confluent Cloud): ~2 weeks if already Confluent-Cloud-hosted, ~6 weeks if self-hosted-to-managed. Apache Kafka → WarpStream: ~3 engineer-weeks (agent deployment + control plane config + MirrorMaker). AutoMQ → WarpStream (or reverse): ~1 engineer-week (both are wire-compatible S3-Kafka; the migration is essentially MirrorMaker between two S3-Kafka clusters). Never migrate more than one topic per day during the flip window; observability catches regressions faster than you can plan for them.
Frequently asked questions
What is AutoMQ and how does it differ from Apache Kafka?
automq is an Apache 2.0-licensed fork of Apache Kafka that replaces the local-disk segment store with an S3 stream WAL, keeping the Kafka wire protocol byte-for-byte compatible so existing producers, consumers, Kafka Connect sinks, ksqlDB queries, and Kafka Streams applications connect without any code changes. The core architectural difference is where durable data lives: in Apache Kafka, every partition's segment files live on the broker's local NVMe with 3× cross-AZ replication for durability; in AutoMQ, brokers hold no durable state — they buffer producer writes in memory for ~500 ms, flush the batch as a segment object to S3, and let S3's 11-nines durability model handle the "did the write survive?" question. The KRaft controller quorum (Apache Kafka's ZooKeeper replacement introduced in 3.x and made mandatory in 4.0) coordinates metadata in AutoMQ too, but it's small and cheap because the data plane holds no state. This changes essentially every operational assumption: broker restarts are instant (no local state to rebuild), autoscaling is CPU-driven (add or remove pods without a partition rebalance), disk sizing disappears entirely, and cross-AZ replication is delegated to S3 rather than billed 3× on your AWS invoice. The trade-off is a fixed ~100–500 ms end-to-end latency floor (the physics of buffered S3 flush), which rules out sub-10 ms workloads but is invisible for the log ingest / CDC / metrics / IoT majority of production streaming workloads. AutoMQ ships as a Kubernetes-native Helm chart and is deployed by teams that want the object-storage-native pattern without giving up open-source licensing or self-hosting control.
AutoMQ vs WarpStream — when do I pick each?
Both automq and WarpStream implement the same shared architectural pattern — stateless brokers, S3 stream WAL, small metadata service, background compaction — but they differ on three axes that decide the choice. Licensing: AutoMQ is Apache 2.0 — you can fork, self-host, embed, redistribute, and there is no vendor to lock you in. WarpStream is proprietary — the source code is not available, and the operational assumption is you run WarpStream Agents in your VPC but Confluent operates the WarpStream Control Plane as a SaaS. Deployment model: AutoMQ is fully self-hosted (Kubernetes chart, you run every component). WarpStream is BYOC — the data plane (agents) runs in your VPC and reads/writes to your S3 buckets, but the control plane (metadata coordination, scheduling) is hosted by Confluent. Data sovereignty story: both keep message bytes in your S3 buckets, but WarpStream sells the "your bytes never leave your account" marketing pitch more explicitly because Confluent operates the control plane, so if that's the framing you need for a compliance conversation, WarpStream's positioning is stronger. The decision usually comes down to: teams with a strong OSS preference and a "no vendor lock-in" policy pick AutoMQ; teams that want the BYOC pattern with Confluent operational support and post-Confluent-acquisition control-plane investment pick WarpStream. Post-Confluent's September 2024 acquisition of WarpStream the products increasingly overlap on features; AutoMQ maintains its distinct OSS-open-source-forever-guarantee positioning.
What is Confluent Freight and when is it the right cluster type?
confluent freight is a cluster tier Confluent introduced in Confluent Cloud in 2024 that sits alongside Basic, Standard, Enterprise, and Dedicated. It targets high-throughput, latency-tolerant workloads with a ~100 ms p95 latency floor and pricing dominated by GB ingested rather than always-on Cluster Kafka Units (CKUs). Confluent's own marketing puts Freight at "up to 10× cheaper than Dedicated" for workloads that don't need sub-10 ms latency — meaning log firehoses, CDC replication feeding a warehouse, IoT telemetry, metrics ingestion, notification streams, and anything else where the downstream consumer batches every few seconds or minutes anyway. Freight is the right cluster type when three conditions align: (a) you're already committed to Confluent Cloud (or migrating to it) and want the managed operational model — no infrastructure to run on your side, (b) your p95 latency budget is above 500 ms — comfortably above the ~100 ms Freight floor, and (c) throughput is high enough that the always-on CKU cost of Dedicated dominates your bill. Freight is the wrong cluster type when you need sub-100 ms latency (stay on Dedicated), when your workload is dominated by Kafka Streams state stores (broker choice matters less than instance choice), or when data sovereignty rules require your bytes to stay in your own AWS account (WarpStream's BYOC model or AutoMQ's self-host is a better fit). The migration path from Dedicated to Freight is Cluster Linking — one-command cross-cluster mirror; consumers and producers flip via bootstrap-server DNS changes; the wire protocol is preserved end-to-end. Teams that audit their Dedicated cluster topic-by-topic and move the latency-tolerant majority to Freight typically see 60–80% cost reductions on their Confluent Cloud bill.
How does S3-backed Kafka handle latency-sensitive workloads?
The short answer: it doesn't — or rather, it handles them the same way it handles any workload, which is with a ~100–500 ms p95 end-to-end latency floor imposed by the physics of buffered S3 flush. There's no magic knob to make s3 kafka sub-10 ms; the durability boundary is the S3 PUT completion, and that PUT latency (plus the batching window ahead of it) sets the floor. What senior architects do instead is segment their workloads: sub-10 ms topics (trading, RTB, real-time notifications) stay on a local-disk Kafka or Redpanda hot-path cluster, while the 100 ms – 5 s topics (log ingest, CDC, metrics, IoT) move to an S3-Kafka cluster. A well-designed streaming platform in 2026 typically runs at least two clusters: one small local-disk cluster for the latency-critical minority (usually 5–10% of topics but 100% of latency-critical revenue paths) and one large S3-Kafka cluster for the long-tail majority (90%+ of topics, 60–80% cost savings). The client library abstracts the choice — a TOPIC_TAGS map plus a build_producer(topic) wrapper routes each topic to the right cluster automatically. If a topic later needs to move (e.g. its latency SLO was tightened by a business decision), Cluster Linking (Confluent) or MirrorMaker 2 (Apache) handles the byte-for-byte mirror with no client code changes because both source and destination speak the same Kafka wire protocol. The rule of thumb: never try to force S3-Kafka to serve a sub-10 ms workload; either raise the latency budget or use a different broker for that specific topic.
Can I run Kafka clients unchanged against AutoMQ / Freight / WarpStream?
Yes — this is the single most important compatibility property of the entire object storage kafka category, and it's what makes the migration story realistic. All four object-storage-native offerings (AutoMQ, Confluent Freight, WarpStream, plus Redpanda for the tiered-storage subset) preserve the Apache Kafka wire protocol byte-for-byte, which means existing KafkaProducer, KafkaConsumer, Kafka AdminClient, Kafka Connect (source and sink), ksqlDB, and Kafka Streams applications connect and operate unchanged. The migration is a bootstrap-server DNS flip, not a client-code rewrite. This is a deliberate architectural moat — every vendor in this category understands that any story requiring client-code changes would kill the category (you're not going to convince a Fortune 500 to rewrite 500 producer applications to try a cheaper broker), so wire compatibility is non-negotiable. There are a small number of edge cases: (a) some vendor-specific advanced features (WarpStream's virtual clusters, AutoMQ's per-topic virtual-cluster tuning) require using vendor-specific admin CLIs to configure but don't change how producers/consumers talk to the cluster; (b) some very-low-level tooling that reaches into on-broker segment files (custom Kafka backup tools, some Kafka-mirror-maker forks) may need updates because there are no on-broker segment files; (c) heavy Kafka Streams applications with large state stores may see performance changes due to different broker-side response-time distributions but the code itself works unchanged. In practical terms, the migration risk is on the operational side (Cluster Linking or MirrorMaker cutover timing, DNS TTL, producer-idempotency validation) rather than the client-code side. This is the single strongest reason to consider S3-Kafka: you can try it on a subset of topics without a code freeze on your producer/consumer fleet.
Is object-storage-native Kafka production-ready in 2026?
Yes — for the workloads it was designed for (100 ms – 5 s p95 latency), and with the same operational-maturity caveats that apply to any tier-1 metadata-plus-data-plane service. AutoMQ reached general availability with its 1.x releases in 2024, is running in production at Alibaba, Airtable, and several other large deployments, and is graduating through the Apache Incubator process in 2025–2026. The remaining operational rough edges are around S3 request-rate throttling (fixed by the hashed-key-prefix strategy), monitoring integration (JMX metrics need Prometheus + alert rules on day one), and the KRaft controller quorum's disk sizing (small but non-zero — needs backup + monitoring). Confluent Freight has been generally available in Confluent Cloud since 2024, is priced and SLA'd like any other Confluent Cloud offering, and is actively displacing Dedicated CKUs on customer bills — Confluent's own case studies name Notion, Instacart, and similar as production Freight users. WarpStream was production-ready before the Confluent acquisition in September 2024 (with deployments at Airtable, Zendesk, and others) and continues to ship as a discrete product with continued investment post-acquisition. The interviewer's question here is almost always "is it mature enough to bet on for a production-critical stream" — the answer in 2026 is unambiguously yes, provided you (a) validate your latency budget against the ~100–500 ms physics floor, (b) ship the day-one observability (S3 PUT latency, end-to-end lag, virtual-cluster quota) before routing production traffic, and (c) plan a phased migration one topic at a time rather than a big-bang cutover. The category is no longer "experimental cheaper Kafka" — it's a mature architectural choice with clear win conditions and clear reject conditions.
Practice on PipeCode
- Drill the SQL practice library → for the SQL, aggregation, and window-function problems that senior interviewers use to test the downstream analytics patterns S3-Kafka feeds.
- Rehearse the streaming practice library → for the Kafka,
automq, Debezium, and CDC-into-warehouse problems that mirror the object-storage-native broker interview. - Sharpen the architecture axis with the design practice library → for the multi-cluster Kafka, vendor-selection matrix, and BYOC-vs-managed scenarios that show up in senior platform interviews.
- Warm up on the aggregation practice library → for the tumbling / session / rolling-window problems that dominate the stream-processing layer sitting on top of
s3 kafka. - Stack the prerequisites against PipeCode's broader 450+ data-engineering catalogue to anchor the five-vendor decision matrix, the shared architectural pattern, and the latency-vs-cost trade-off against real graded inputs.
Lock in automq muscle memory
Docs explain what AutoMQ is. PipeCode drills explain when it wins — when the S3 stream WAL beats local-disk Kafka on cost, when Confluent Freight's ~100 ms floor is fine and when it isn't, when WarpStream's BYOC data-sovereignty pitch earns its price premium, when a multi-cluster steady state beats one giant cluster serving everything. Pipecode.ai is Leetcode for Data Engineering — pattern-first practice tuned for the production trade-offs senior data engineers actually face when picking an object-storage-native Kafka broker in 2026.





Top comments (0)