×marble
all posts
Jun 15, 2026·13 min read

Kafka vs Pulsar for Personalization: Choosing Your Event Streaming Backbone in 2026

Most personalization teams default to Kafka without examining what their pipeline actually needs from a streaming layer. This post maps the specific tradeoffs — signal taxonomy, ordering guarantees, multi-tenancy, tiered storage, delayed delivery — to the architecture decisions that compound at scale.

Alex Shrestha·Founder, ×marble

Kafka vs Pulsar for Personalization: Choosing Your Event Streaming Backbone in 2026

TL;DR.

  • Personalization quality is bounded by how fast and faithfully user signals reach your model — the streaming layer is not infrastructure, it is the product.
  • Kafka wins on raw throughput, ecosystem depth (Kafka Streams, ksqlDB, Flink), and log compaction for profile state; Pulsar wins on native multi-tenancy, tiered storage, and delayed message delivery.
  • Most teams default to Kafka without evaluating whether their use case is actually multi-tenant, long-retention, or schedule-driven — the three cases where Pulsar's architecture pays off materially.
  • In 2026, Pulsar's key_shared subscription and Kafka's log compaction are both production-mature; the stream-processing ecosystem gap still favors Kafka for feature computation pipelines.
  • If you only remember one thing: the decision isn't about throughput benchmarks — it's about whether your personalization layer serves one product or many, and how long you need to retain raw user events.

The recommender system is only as fresh as its last signal update. Batch pipelines running hourly retraining cycles were the industry norm for over a decade — and they remain the norm at companies that haven't felt the pain yet. When a user watches three videos in a row, pivots to a different genre, and rewinds twice, the product should adapt in the next session, not tomorrow. That adaptation starts with your event streaming layer. Choosing between Kafka and Pulsar for personalization is not a generic infrastructure debate; it hinges on your signal taxonomy, your multi-tenancy model, your retention requirements, and whether you need to schedule events alongside ingesting them. Getting this choice wrong at year one means a painful migration at year two.

Why Personalization Pipelines Need Streaming, Not Batch

The knowledge graph behind a modern personalization engine is a living structure that degrades in usefulness with every second it goes unupdated. Every click, play, skip, search query, and dwell-time event is an edge update — user:U → watched → video:X, user:U → skipped → genre:Y — and the freshness of those edges directly determines recommendation quality. If your graph is refreshed hourly, your recommendations are an hour stale at minimum, and on average 30 minutes behind reality.

Streaming turns raw user behavior into a continuously updated signal graph. Both Kafka and Pulsar are log-based distributed messaging systems built for durable, ordered, high-throughput delivery at scale. They diverge in architecture in ways that are mostly invisible at low volume and become consequential when your DAU is in the tens of millions, when you are serving multiple products off a single platform, or when your cold-start resolution depends on sub-second signal propagation.

The framing matters: this is not a comparison of two message queues. It is a comparison of two different answers to the question, "how should a personalization platform manage the flow of behavioral state?"

The Signal Taxonomy: What Actually Flows Through Your Pipeline

Before choosing a streaming backbone, enumerate what you are actually streaming. Not all personalization events have the same latency requirements, ordering constraints, or retention profiles. Most teams discover they have four distinct signal classes, each with different infrastructure implications.

Interaction signals (high-volume, latency-critical)

Clicks, plays, pauses, skips, likes, shares, search queries, scroll depth, dwell time. Volume scales with DAU — at 1M active users with 50 interaction events per session, you are producing tens of millions of events per hour during peak. Latency requirements are strict: an event that arrives 30 seconds late is useless for in-session adaptation. The graph update triggered by this event needs to complete before the user's next request hits your serving layer. This is Kafka's home turf — predictable sub-10ms producer-to-consumer latency on a well-tuned cluster, with partition-ordered delivery per user_id.

Profile and feature store writes (moderate volume, correctness-critical)

When an interaction event triggers a graph update, the downstream feature store write must be idempotent and ordered per user. You cannot process a "remove preference" event before the "add preference" event that created the edge. Both Kafka (partition by user_id key) and Pulsar (key_shared subscription) enforce per-key ordering with horizontal consumer scale-out — the mechanisms differ architecturally in ways covered below.

Segment membership events (lower volume, fan-out heavy)

When a user crosses a behavioral threshold — watched more than three action films this week, completed onboarding, reached engagement tier 2 — they enter a segment. That event needs to fan out to multiple downstream consumers: the A/B experiment engine, the push notification scheduler, the real-time serving cache invalidator. Kafka consumer groups handle this with clean separation; Pulsar shared subscriptions work equivalently.

Scheduled and delayed events (Pulsar's structural advantage)

Personalization is reactive and proactive. Re-engagement triggers ("user hasn't opened in 72 hours"), scheduled model refreshes, and time-windowed cohort analyses require producing an event now that should be consumed at a future time. Kafka has no native delayed message delivery. Pulsar does — natively, via deliverAt and deliverAfter parameters on the producer (Apache Pulsar messaging concepts). Teams building on Kafka route delayed events through a separate scheduler service — Redis sorted sets with a polling consumer, or a dedicated job queue — adding an operational boundary and a failure domain. It works, but it is an additional system that can fall behind during traffic spikes exactly when re-engagement is most time-sensitive.

Kafka for Personalization: Where It Wins

Kafka is the right default for most personalization teams in 2026. The argument is not about performance benchmarks — it is about ecosystem depth and operational maturity.

Log compaction. Kafka's compacted topics retain the last message per key and discard earlier values. For user profile topics keyed by user_id, this means the topic self-manages to current state: you can rebuild the feature store from scratch by replaying a compacted topic without replaying the full raw event history. This is critical for disaster recovery and cold bootstrap. Configure log.cleanup.policy=compact on your profile-state topic, and you get a free changelog store — no separate key-value store required for current-state reads during bootstrap (Kafka documentation, log compaction).

Exactly-once semantics. Since Apache Kafka 0.11, idempotent producers and transactional APIs provide end-to-end exactly-once delivery. For personalization pipelines where double-counting a purchase event or double-applying a preference update would corrupt downstream scores, exactly-once is a correctness property, not a nice-to-have. There is a latency cost — transactional commits add overhead — so the pattern is to use exactly-once selectively on the high-value signal path (purchases, ratings, explicit feedback) and accept at-least-once with idempotent consumers on the high-volume interaction path (plays, scrolls).

Kafka Streams and ksqlDB. If your personalization pipeline requires stream-processing — windowed aggregations for "plays in the last 30 minutes," joins between the click stream and the content metadata stream, real-time feature computation before the graph update — Kafka Streams and ksqlDB run natively against Kafka topics with minimal operational overhead. The Flink-Kafka connector is battle-tested at scale and is the standard integration for teams running stateful stream processing for ML feature pipelines (Apache Flink). Pulsar has Pulsar Functions and Pulsar SQL; both work, but the ecosystem depth, community support, and hiring pool do not match.

Partition-ordered sessions. Partitioning by user_id hash guarantees that all events for a given user land on the same partition in order. A single consumer thread on that partition sees the user's causal event sequence. For knowledge graph updates where the order of edge mutations determines correctness, this is not optional. The ceiling: you cannot have more parallelism than you have partitions. Adding partitions after topic creation is operationally disruptive (it breaks the key-to-partition mapping for new messages). Set your initial partition count conservatively high — 5-10x your expected peak consumer thread count — to avoid repartitioning later.

Where Kafka falls short. Geo-replication via MirrorMaker 2 is functional but operationally heavier than native replication in Pulsar. Tiered storage arrived late (KIP-405, available in Apache Kafka 3.x and Confluent Platform), and while the implementation is now stable, Pulsar's tiered storage has a longer production track record for long-retention workloads. And there is no native delayed delivery — which forces a sidecar scheduler into your architecture if your personalization loop includes any proactive triggering.

Pulsar for Personalization: When the Architecture Pays Off

Pulsar separates compute (brokers) from storage (Apache BookKeeper) and introduces a three-level namespace hierarchy: persistent://tenant/namespace/topic. For single-product personalization, this adds operational complexity for little gain. For multi-product or multi-tenant personalization platforms, the architecture is directly load-bearing.

Native multi-tenancy. If you are building a personalization layer that serves multiple products — Vivo, Video, Music, and third-party integrations — or a B2B personalization API serving multiple customers, Pulsar's tenant/namespace model maps directly to your logical isolation requirements. Each product or customer gets its own tenant with independent quotas, ACLs, retention policies, and backpressure limits. With Kafka, equivalent isolation requires separate clusters (expensive, high operational overhead) or careful naming conventions and ACL management (which teams consistently get wrong at scale). Pulsar makes multi-tenancy a first-class primitive, not an afterthought bolted onto topic naming.

Tiered storage. User event history is long-tailed — interaction data from 18 months ago is still useful for periodic re-training, cold-start bootstrapping for returning users, and long-horizon preference inference. Pulsar's native tiered storage (Apache Pulsar tiered storage overview) offloads older log segments to object storage (S3, GCS, Azure Blob) transparently. Consumers still read through offloaded segments via the standard API — the tiering is invisible to the consumer. This makes indefinite raw event retention economically viable without a separate data lake ingestion pipeline. You are paying object storage prices for cold segments instead of broker disk prices, with no ETL boundary between hot and cold storage.

key_shared subscription for ordered parallel processing. Pulsar's key_shared subscription type delivers all messages with the same key to the same consumer instance while allowing parallel processing across different keys. This is the natural model for personalization event processing: every consumer instance handles a consistent slice of user IDs in order, and you can add consumer instances dynamically without repartitioning. Kafka achieves similar behavior through partition assignment, but partition count caps your consumer parallelism ceiling. Adding consumers beyond the partition count is wasteful — surplus consumers sit idle. Pulsar removes this ceiling, which matters during traffic spikes when you want to scale your graph update consumer fleet horizontally in seconds.

Native geo-replication. Global personalization products — serving users across NA, EU, and APAC from regional clusters — benefit from Pulsar's built-in async and sync replication between clusters. The topology is configured at the namespace level, not by deploying and managing MirrorMaker. For teams with geographic latency requirements or data residency requirements (GDPR, India DPDP), native geo-replication is a genuine operational simplification.

Where Pulsar falls short. Operational complexity is real. A production Pulsar cluster requires ZooKeeper (or the newer Oxia metadata service), a BookKeeper ensemble, brokers, and optionally a proxy tier. The minimum fault-tolerant cluster topology is heavier than a comparable Kafka setup. The hiring pool is smaller. And the stream-processing ecosystem — the equivalent of Kafka Streams, ksqlDB, and the mature Flink-Kafka connector — is functional but thinner. If your personalization feature pipeline depends heavily on stateful stream processing rather than pure event routing, Kafka's ecosystem advantage is real.

Kafka vs Pulsar: The Decision Matrix

The honest answer is that most personalization teams will be fine with Kafka, and the operationally simpler default is usually correct. The cases where Pulsar's architecture is the right choice are specific and worth examining before you build.

Choose Kafka if:

  • You are building a single-product personalization pipeline with a bounded DAU ceiling.
  • Your team has existing Kafka or Flink expertise and does not want to retool.
  • Your primary need is rich stream-processing — windowed features, real-time joins, stateful aggregations — where Kafka Streams or Flink-Kafka is the right tool.
  • You have no requirement for delayed or scheduled event delivery, or are willing to run a separate scheduler service.
  • Your raw event retention requirement is under 90 days, or you are on a Kafka distribution with mature tiered storage support.

Choose Pulsar if:

  • You are building a multi-tenant personalization platform serving multiple products, business units, or B2B customers off a shared infrastructure layer.
  • You need indefinite raw event retention at low cost and want to avoid a separate data lake ingestion pipeline.
  • Your personalization loop includes scheduled events — re-engagement triggers, time-windowed cohort analyses, delayed model refreshes — and you want them in the same system as your interaction signals.
  • You are deploying across multiple geographic regions and want native geo-replication without the operational overhead of MirrorMaker 2.
  • Your consumer parallelism requirements will exceed your sustainable partition count, or your DAU growth is rapid enough that partition ceiling management would become a recurring tax.

The hybrid trap. Some teams run Kafka for the hot interaction signal path and Pulsar for scheduled events. This works and avoids the latency cost of routing interaction signals through Pulsar's heavier storage layer, but it doubles your streaming infrastructure operational surface. Only go hybrid if you have exhausted single-system options and have a platform team to own two streaming clusters in production.

Latency Budgets and the p50 < 30 ms Target

For p50 < 30 ms end-to-end on the signal-to-recommendation path, the streaming layer needs to consume a small fraction of that budget — ideally under 5 ms producer-to-consumer for the hot path.

Both Kafka and Pulsar deliver sub-5 ms p50 producer-to-consumer latency on well-tuned clusters for small messages (< 1 KB user event payloads). The performance differentiation that matters for personalization is not raw throughput or median latency — both systems can saturate multi-gigabit links. It is tail latency under consumer lag, and it is what happens when your consumer fleet falls behind during a traffic spike.

Consumer lag is the silent killer of real-time personalization. If your knowledge graph update consumer is 100,000 events behind, your recommendations reflect behavioral state from 100,000 interactions ago. The failure mode is worse than having no personalization — you are serving stale preferences with high model confidence, actively misleading users into a dead-end recommendation loop.

The mechanisms to manage lag differ between the systems. Kafka consumer lag is surfaced via JMX or the consumer group API and is well-understood in the ecosystem — standard alerting tooling covers it. Scaling out consumer parallelism requires either adding partitions (disruptive) or accepting that consumers beyond the partition count are idle. Pulsar's key_shared subscription allows adding consumer instances dynamically without repartitioning, which provides operational flexibility during organic traffic growth and traffic spikes.

Both systems reward separating signals by priority. Routing purchase events and explicit ratings (high value, low volume, process immediately with exactly-once) to a separate high-priority topic with a dedicated consumer from scroll events and hover signals (high volume, tolerate 1-2 second lag, process with at-least-once) is standard practice regardless of which system you choose — and it is the pattern used by teams building real-time ranking at search and content discovery scale (Airbnb, "Real-time Personalization using Embeddings for Search Ranking," KDD 2018).

Schema evolution is a long-term maintenance concern worth solving at day one. User interaction events evolve as you add content types, interaction modalities, and metadata fields. Confluent Schema Registry (Avro or Protobuf) is the standard Kafka pairing; Pulsar has a built-in schema registry. Both support backward-compatible schema evolution. Use a binary serialization format — Avro or Protobuf, not JSON — from the start. JSON schema overhead compounds at the event volumes that personalization generates, and the lack of explicit schema enforcement in JSON topics becomes a source of silent data quality issues as the codebase grows.

Where to Start

If you are choosing a streaming backbone for a new personalization pipeline today:

Start with Kafka if your team has existing Flink or Kafka expertise and you are not building a multi-tenant platform. Use log-compacted topics for current user profile state keyed by user_id, raw append-only topics for interaction signals, and Kafka Streams or Flink for real-time feature computation. Add a lightweight scheduler (Redis sorted sets with a consumer poller, or a purpose-built job queue) for delayed events. This is the lower-risk path — the hiring pool is larger, the tooling is deeper, and the operational playbooks are widely documented.

Evaluate Pulsar seriously if your roadmap includes multiple products or B2B customers sharing the same personalization infrastructure, if you need multi-year raw event retention without a separate cold storage pipeline, or if geographic deployment constraints make native geo-replication valuable. The operational overhead is real but the architectural fit for multi-tenant personalization platforms is genuine, not marketing.

Define your signal taxonomy before you finalize the choice. List every event type, its expected volume at your target DAU, its latency requirement, its ordering constraint, and whether it is delayed or scheduled. If you have more than a handful of delayed event types, that single column may be decisive.

Instrument consumer lag before you go to production. Set up alerting on consumer group lag — or Pulsar subscription backlog — before the first production traffic. A personalization pipeline degraded because the graph update consumer is 10 minutes behind is worse than no personalization. It delivers stale recommendations with the confidence of a live system, and the failure is invisible without explicit lag monitoring.

The streaming layer is not a commodity decision for personalization. It is the connective tissue between raw user behavior and the live graph that your recommendation serving reads from. Partition models, retention policies, delayed delivery support, and multi-tenancy isolation are constraints that compound as you scale from 100K DAU to 10M. Choose the system that matches the shape of your personalization architecture — not the one with the best benchmark headline, and not the one your last job used by default.

the product behind these notes

×marble is the personalization graph.

One API. A living knowledge graph per user. Day-zero ready, explainable by construction. We built it so you don't have to.

See ×marble