Architecture
How the Kafka drop-in connector works — the 9092/9093 wire-protocol listeners, Produce/Fetch/group-coordinator dispatch, and the Events Store log mapping.
Overview
The KubeMQ Kafka connector is an embedded, wire-protocol bridge inside kubemq-server that
speaks the genuine Apache Kafka binary protocol — the same length-prefixed frames, the same
request/response pairs, and the same flexible-version encoding real kafka-clients,
librdkafka, and franz-go clients already speak. The connector is opt-in (disabled by
default) — enable it with CONNECTORS_KAFKA_ENABLE=true (Docker) or spec.kafka.enabled: true
(Kubernetes) — and it opens two wire-protocol listeners once turned on. Any unmodified Kafka
client connects by repointing bootstrap.servers: no library swap, no code change, no KubeMQ
SDK.
Two ideas anchor the whole model:
- Every Kafka topic partition maps onto a native KubeMQ Events Store log — a persistent, ordered, replayable primitive that already exists independent of Kafka.
- A Kafka offset is not a separate number the connector invents and tracks — it tracks that
log's own
Sequencevalue directly (the two are one apart —Sequencestarts at 1, Kafka offsets at 0).
Because the connector reuses an existing KubeMQ primitive instead of building a parallel storage layer, a produced record is durable, replayable by any other KubeMQ transport, and behaves identically on every node of a cluster the moment it lands.
The wire-protocol listeners
A Kafka client bootstraps against one of two TCP listeners:
- Port 9092 — plaintext. A plain TCP socket that accepts raw, length-prefixed Kafka frames.
- Port 9093 — TLS. The same frame format, wrapped in a TLS listener that reuses the server's global certificate configuration — there is no Kafka-specific certificate option.
Both ports stay closed until the connector is enabled. A disabled connector never binds a socket, so a client dialing 9092 or 9093 against a stock, unconfigured KubeMQ server gets connection-refused, not a half-working listener. See Configuration for the opt-in flag and the security posture the listeners enforce.
Once a connection is open, every request runs the same short pipeline before it ever reaches a handler: the frame is decoded into a length-prefixed Kafka request, its API key and version are checked against the connector's advertised version table (an unsupported version is rejected before any work happens), the request size is checked against a bounded cap, and — for everything except the handful of pre-authentication calls — the caller's authorization is enforced. Only then does the request reach the single dispatch function that routes it by API key.
That dispatch function is deliberately narrow: one function, one switch over the Kafka API key,
with more than forty explicit cases. Grouping those cases by what they do gives you the
connector's real functional surface:
- Data plane —
Produce(key 0) andFetch(key 1) carry the actual records;ListOffsets(key 2) andOffsetForLeaderEpoch(key 23) answer positional questions about a partition without moving data. - Group coordination —
FindCoordinator(10),JoinGroup(11),SyncGroup(14),Heartbeat(12), andLeaveGroup(13) run the classic consumer-group protocol;OffsetCommit(8) andOffsetFetch(9) persist and read back a group's position. See Consumer Groups for how these five calls fit together. - Admin —
CreateTopics(19),DeleteTopics(20),CreatePartitions(37),DescribeConfigs(32), andDescribeCluster(60) manage topic and cluster metadata. - Share groups (preview) —
ShareGroupHeartbeat(76),ShareFetch(78), andShareAcknowledge(79) implement the KIP-932 queue-style consumption model, alongside admin/observability counterparts. See Share Groups for the preview scope.
A cluster adds one more responsibility ahead of dispatch: a request that must run on the partition leader — a produce, or a group-coordinator call for a group whose coordinator lives on another node — is transparently forwarded there rather than rejected, so a client that happens to connect to a follower still gets a correct answer.
Topics, partitions, and channels
Once a request clears dispatch, it lands on a channel — the same channel a native KubeMQ Events Store client would use. The mapping is deliberately simple:
- Partition 0 of a topic maps to the Events Store log
kafka.{topic}. - Every partition after 0 gets its own log, named
kafka.{topic}~{partition}— the topic name, a literal~, and the partition number.
A topic starts with one partition (unless CreateTopics asks for more) and can only grow —
partitions are added, never removed, up to a hard ceiling of 256. Growing a topic to N
partitions simply means N independent, equally-ordered Events Store logs back it, each
accepting its own stream of produces.
A Kafka request clears the wire-protocol listener and dispatch, then lands on the Events Store
log for its partition — kafka.{topic} for partition 0, kafka.{topic}~{partition} for every
partition after it — all backed by the message broker.
The offset a client sees for a record isn't a separate number the connector invents and tracks
alongside the log — it tracks that log's own Sequence value directly (KubeMQ numbers
Sequence starting at 1; Kafka numbers offsets starting at 0, one apart). That has three
practical consequences: an offset is durable — it survives a broker restart, because the log
itself is durable; it is restart-stable — the same record always reports the same offset,
because nothing recomputes it; and on a cluster it is identical on every node — because every
node applies the same replicated log in the same order. This is also why compaction and time/size
retention — covered in
Durability & Retention — never
renumber a surviving record's offset: the offset was never a separate, movable number to begin
with.
This page only needs the shape of the mapping. The exact offset arithmetic, and the internal channels a consumer group's offsets and membership live on, are catalogued in Topic Mapping.
The next storage engine
The Kafka connector runs only on KubeMQ's next storage engine. That is not a configuration
step you perform yourself: enabling Kafka on a fresh store auto-selects next, with no
manual store.engine setting required. See
Storage Engines → Zero-config engine selection
for the complete decision tree — what happens on a fresh store, an existing store, and an
explicit pin. This page only needs the coupling, not the rules.
The coupling exists because next is what makes the two guarantees above possible. Every Events
Store log on next is backed by an owned segment log replicated with Dragonboat raft — the same
mechanism that makes a Kafka offset identical across every node — and next is also the engine
that implements compaction, which several Kafka behaviors
(Compacted Topics, the Kafka Connect and Kafka
Streams ecosystems) depend on.
Related
Topic Mapping
The exhaustive kafka.{topic} / kafka.{topic}~{partition} grammar, the off-by-one offset-to-Sequence arithmetic, and the group and offset channels.
Durability & Retention
The acks contract, quorum-fsynced durability on next, retention by time and size, and log compaction.
Storage Engines
The legacy vs next engine model and the zero-config selection rules Kafka relies on.
Consumer Groups
The classic JoinGroup/SyncGroup/Heartbeat protocol, durable offsets, and static membership.
Was this page helpful?
Kafka
Point unmodified Kafka clients at KubeMQ over the native wire protocol — produce, consume, consumer groups, and compaction, with no client-library swap.
Configuration
How the Kafka connector is enabled, ported, and secured — the opt-in CONNECTORS_KAFKA_ENABLE flag, the 9092/9093 listeners, and the settings reference.