Acumatica · Pulsar

Apache Pulsar Streaming — A Field Guide

Apache Pulsar Streaming — A Field Guide is the work that turns raw data into decisions. The pipeline from "we have data" to "we have a model that runs in production" is the same.

John Kihiu12 min read

Pulsar's pitch against Kafka comes down to one architectural decision: it separates the serving layer (brokers) from the storage layer (Apache BookKeeper). Brokers are stateless and handle routing and protocol; BookKeeper bookies own the durable log storage. That split is what enables Pulsar's most distinctive operational property — brokers can be added, removed, or rebalanced without moving any data, because the data never lived on the broker in the first place.

Segmented storage versus Kafka's partition model

Kafka ties a partition's data to the broker that owns it, so rebalancing partitions across brokers means physically moving log segments. Pulsar topics are split into ledgers stored across a pool of bookies, striped and replicated independently of any single broker. The practical effect: scaling brokers up or down, or recovering from a broker failure, doesn't trigger a data-copying rebalance — the new broker just starts serving the same ledgers from BookKeeper.

This matters most for large, bursty clusters

If your throughput and partition count are stable, Kafka's simpler single-layer model is easier to operate and reason about. Pulsar's storage/serving split pays off specifically when you need to scale brokers independently of storage, or when topic count runs into the tens of thousands — a scale where Kafka's per-partition file handles and ZooKeeper metadata load become the bottleneck.

Four subscription models, not one consumer group shape

Kafka has one consumption model: consumer groups, where each partition is read by exactly one consumer in the group. Pulsar supports four subscription types on the same topic simultaneously — exclusive (one consumer), failover (one active, others standby), shared (round-robin across consumers, like a work queue), and key_shared (messages with the same key always go to the same consumer, giving ordering per key while still load-balancing across consumers). Key_shared is the one with no direct Kafka equivalent and is genuinely useful for per-entity ordering (e.g., all events for one order ID processed in order) without needing one partition per entity.

JAVA · KEY_SHARED CONSUMER
Consumer consumer = client.newConsumer()
    .topic("persistent://tenant/ns/orders")
    .subscriptionName("order-processors")
    .subscriptionType(SubscriptionType.Key_Shared)
    .subscribe();
// messages with the same orderId key always land on
// the same consumer instance, preserving per-key order
// while still spreading load across the subscription

Tiered storage for cheap long-term retention

Because BookKeeper already treats a topic as a sequence of immutable segments, Pulsar can offload older segments to S3 or another object store while keeping recent segments on local disk — transparently, with no change to how consumers read the topic. This gives Kafka-style low-latency reads on hot data and cheap, effectively unlimited retention on cold data without running a separate archival pipeline, which is a real operational simplification versus bolting tiered storage onto Kafka after the fact.

Multi-tenancy as a first-class concept

Pulsar topics are namespaced as tenant/namespace/topic, with quotas, retention, and access policies configurable per tenant and per namespace out of the box. Kafka has no native concept of a tenant — multi-tenant Kafka clusters are usually built with naming conventions and ACLs layered on top by convention, not enforced by the broker. If you're building a platform that serves genuinely separate customers or business units off one cluster, Pulsar's namespace isolation removes a layer of tooling you'd otherwise build yourself.

Operational complexity is real, not hypothetical

Pulsar requires running and tuning BookKeeper (bookies) in addition to brokers and ZooKeeper (or its newer metadata store options) — that's more components than a Kafka cluster, and BookKeeper has its own failure modes and tuning surface. Don't adopt Pulsar for the subscription model alone if your team has zero BookKeeper operational experience; budget real time for the learning curve.

NeedBetter fit
Per-key ordered work-queue consumptionPulsar (key_shared)
Simplest possible operational footprintKafka
Native multi-tenant isolationPulsar
Largest ecosystem / connector libraryKafka
Independent scaling of compute vs storagePulsar

Wrapping up

Pulsar's architecture is genuinely different from Kafka's, not just a re-skin — the broker/bookie split, native multi-tenancy, and key_shared subscriptions solve real problems Kafka users build workarounds for. That said, Kafka's ecosystem maturity and simpler single-layer operational model still win by default; reach for Pulsar when one of its specific architectural advantages maps directly onto a problem you actually have.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.