DEV Community

Cover image for AutoMQ, Confluent Freight & S3-Backed Kafka: The Object-Storage-Native Broker Wave
Gowtham Potureddi
Gowtham Potureddi

Posted on

AutoMQ, Confluent Freight & S3-Backed Kafka: The Object-Storage-Native Broker Wave

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.

PipeCode blog header for AutoMQ, Confluent Freight and S3-backed Kafka — bold white headline 'S3-Backed Kafka Brokers' over a hero composition of three glyph medallions (AutoMQ spark, Freight truck, WarpStream ribbon) arranged in a triangle around a central purple 'object-storage-native 2026' seal, on a dark gradient.

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


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
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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."
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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)'
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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()
Enter fullscreen mode Exit fullscreen mode
-- 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
Enter fullscreen mode Exit fullscreen mode

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

Practice →

Design Topic — design Design problems on Kafka broker selection

Practice →


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.

Iconographic AutoMQ diagram — a producer card writing into a stateless broker pod with a purple S3 WAL tape flowing to an S3 bucket, a metadata card labelled 'controller quorum', and an Apache 2.0 badge.

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=all write 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, offset from 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 scale command; removing a broker is a kubectl delete pod that 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:DeleteObject on the bucket, IRSA configured to bind that role to the broker's service account.
  • Chart. automq/automq-for-kafka Helm 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/*"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. IRSA binds the Kubernetes service account automq-broker to 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.
  3. 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: 500 is the batching window that dominates the end-to-end latency; lower values reduce latency but increase S3 PUT costs.
  4. 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.
  5. 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 tune flushIntervalMs per 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")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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 metric automq_broker_s3_put_error_rate goes non-zero.
  • The fix. Prepend a hash prefix to spread keys across S3 partitions: wal/{hash(topic,partition) % 256:02x}/{topic}/{partition}/{segment}. Configure via s3.keyPrefixStrategy: hashed in 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
Enter fullscreen mode Exit fullscreen mode
# 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())
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. The trade-off is human-readability: wal/a7/orders/0/00000123.log is less scannable than wal/orders/0/00000123.log. AutoMQ's admin CLI abstracts this — automq-cli topic describe orders still shows segments by topic, not by hash prefix.
  4. 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 check keyPrefixStrategy and keyPrefixCardinality.
  5. In extreme cases (10+ GB/s workloads), even 256 prefixes can saturate; in that case bump keyPrefixCardinality to 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}/*"]
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode

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

Practice →

Design Topic — design Design problems on stateless broker architectures

Practice →


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.

Iconographic Confluent Freight diagram — a Confluent Cloud console showing four cluster tiers with Freight highlighted, a truck-glyph flowing into an S3 bucket, and a cost-slider showing 90% savings.

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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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}%")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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)}")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. The ambiguous case is sessions.web.click at 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.
  5. 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.raw on a Dedicated cluster lkc-oldded, 200 MB/s ingest.
  • Destination. logs.app.raw mirrored to Freight cluster lkc-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)
Enter fullscreen mode Exit fullscreen mode
# 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 ...
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. kafka mirror promote is 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)
Enter fullscreen mode Exit fullscreen mode
# 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")
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode

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

Practice →

Design Topic — design Design problems on multi-cluster Kafka topologies

Practice →


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).

Iconographic shared S3-Kafka architecture diagram — producer batching in memory, a ~500ms flush arrow into an S3 bucket, a metadata service card for offsets, a background compaction rewriter glyph, and a virtual-cluster ring.

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=all request 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=all success, 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_offsets topic — 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.bytes metrics.

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 standard InitProducerId, AddPartitionsToTxn, EndTxn protocol.
  • The commit atomicity. A transaction commit writes a control message to every affected partition (including __consumer_offsets for 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=true is 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"...") with acks=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=all success ~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")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The producer's linger_ms=100 batches 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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 virtualCluster property that binds it to one of the three configurations.
  • Isolation. Quotas are enforced per virtual cluster; a noisy tenant on logs-hot cannot starve cdc-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
Enter fullscreen mode Exit fullscreen mode
# 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}")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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-hot bursts to 400 MB/s, its VC quota throttles it back to 300, and the other VCs are unaffected.
  2. The flushIntervalMs tuning is the latency/cost lever per VC. logs-hot at 200 ms flushes ~5 times more frequently than cdc-archive at 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.
  3. The segmentSizeMb knob 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.
  4. 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.
  5. 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 to orders.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
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. producer.init_transactions() registers the transactional.id with AutoMQ's transaction coordinator (a component of the metadata service). If a previous producer with the same transactional.id is still alive, it gets fenced (its next commit fails with ProducerFencedError) — this is how zombie processes are prevented from double-writing.
  2. The consumer's isolation_level=read_committed tells 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.
  3. producer.begin_transaction() opens a transaction. All producer.send() calls inside the transaction accumulate at the metadata coordinator; consumers with read_committed don't see them yet.
  4. 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.
  5. producer.commit_transaction() triggers the coordinator to write commit markers to every affected partition (including __consumer_offsets and 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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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.
Enter fullscreen mode Exit fullscreen mode

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

Practice →

Design Topic — design Design problems on stateless distributed systems

Practice →


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.

Iconographic decision matrix diagram — a 5-column comparison card with AutoMQ / Freight / WarpStream / Redpanda / Kafka columns rated on latency, cost, licensing, ecosystem, deployment axes, and an interview signals strip.

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}")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. 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)")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:
    ...
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The central TOPIC_TAGS mapping 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.
  2. The build_producer / build_consumer functions 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.
  3. When a topic migrates between clusters (e.g. a checkout topic being demoted to warm after a latency budget review), update TOPIC_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.
  4. 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.
  5. 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},
}
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode

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

Practice →

Design
Topic — design
Design problems on vendor-agnostic streaming stacks

Practice →


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:AbortMultipartUpload scoped to the bucket, IRSA-bind the role to the automq-broker service account. Install automq/automq-for-kafka Helm chart with kraft.replicas=3, broker.replicas=6, s3.flushIntervalMs=500, s3.keyPrefixStrategy=hashed, s3.keyPrefixCardinality=256, autoscaling.enabled=true with targetCPUUtilizationPercentage=70. Enable Prometheus scrape, ship the automq_broker_s3_put_error_rate > 0.001 alert 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.yaml per 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=zstd or lz4 (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=all hang; 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: AutoMQ automq_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.

Practice streaming problems →
Practice design problems →

Top comments (0)