Kafka mattered because it turned service-to-service communication from a blocking chain into a replayable, partitioned log that could survive real production pressure.
Problem
LinkedIn did not need a prettier queue in 2011. It needed a way to stop user-facing requests from waiting on every downstream system that cared about the same event. A profile update might need to refresh a feed, update search, notify connections, record analytics, and feed recommendation models. Treating all of that as synchronous work made the request path fragile. One slow dependency could stretch latency for everyone upstream.

The core failure mode is familiar in distributed systems. A user action enters Service A, then Service A calls Service B, Service C, and Service D before it can return. Each dependency adds latency, failure probability, retry behavior, and operational coupling. When traffic spikes, the slowest consumer becomes the effective throughput limit for the whole path.
A message queue changes the contract. The request handler records that something happened, publishes a message, and returns once the user-visible transaction is complete. Downstream systems process the event independently. This does not remove complexity. It moves complexity from synchronous call chains into delivery guarantees, ordering, replay, idempotency, and backpressure.
Solution Approach
A message queue lets producers and consumers communicate asynchronously. The producer emits a message, the broker stores it, and consumers process it later. That simple shape gives systems three useful properties: decoupling, load leveling, and recovery after partial failure.
In a point-to-point queue, each message is processed by one consumer. This works well for jobs such as thumbnail generation, email sending, or order fulfillment tasks where duplicate work is wasteful. RabbitMQ is often used this way, especially when routing rules, acknowledgements, priority queues, and per-message behavior matter.
Kafka took a different path. Apache Kafka is closer to a distributed commit log than a traditional queue. Producers append records to topics. Topics are split into partitions. Each partition is an ordered log with monotonically increasing offsets. Consumers track their own offsets, which means consumption does not delete the data.
That design is the key difference. In a traditional queue, the broker often treats successful consumption as the end of the message lifecycle. In Kafka, the log remains available for a retention window. A new analytics service can start reading from the beginning. A broken consumer can rewind after a bug fix. A team can rebuild a materialized view from historical events instead of asking every upstream service to resend state.
Partitioning is how Kafka scales. A topic such as order-events may have 32 partitions spread across multiple brokers. A producer chooses a partition, often by hashing a key such as customer_id or order_id. Events with the same key go to the same partition, preserving order for that key while allowing unrelated keys to process in parallel.
That ordering model is practical, but narrow. Kafka does not give global ordering across a large topic without forcing everything through one partition, which becomes a throughput bottleneck. Good Kafka design starts by asking which entity actually needs ordered history. For many systems, per-user, per-order, or per-account ordering is enough.
Consumer groups complete the model. A consumer group represents one logical application reading a topic. Within a group, each partition is assigned to one consumer at a time. If the group has more consumers, work spreads out. If a consumer dies, Kafka reassigns its partitions. Separate consumer groups each get their own view of the same topic, so billing, search indexing, fraud detection, and analytics can all consume order-events independently.
The API pattern that emerges is event publication, not remote procedure calling. Instead of asking five services to do work now, the system records facts such as ProfileUpdated, OrderPlaced, or PaymentCaptured. Consumers react to those facts. This fits event-driven architecture, event sourcing, CQRS, and outbox-based integration patterns. The Kafka documentation covers the broker and client mechanics, while the Transactional Outbox pattern explains one common way to publish events without losing consistency between a database write and a broker write.

Identity and security systems are a good example. A signup event may trigger welcome email, audit logging, fraud checks, profile initialization, and analytics. None of those should usually block the account creation response once the core user record is safely written. Providers such as Auth0 expose event and log streams because authentication is not only a request-response concern. It is also an audit and integration stream.
Consistency Models
Queues do not make distributed consistency disappear. They force teams to choose where inconsistency is acceptable and how it will be repaired.
The most common production choice is at-least-once delivery. The broker will retry messages until they are acknowledged, which means a consumer may process the same message more than once. This is usually the right default because losing business events is worse than seeing duplicates, but it pushes work into consumer design.
Idempotency becomes mandatory. A payment consumer should not charge a card twice because it saw the same payment_id twice. An email consumer may store a sent marker. An inventory consumer may apply a deduplication key before decrementing stock. The broker can help, but correctness usually lives in the business data model.
At-most-once delivery is simpler and faster because the system can drop messages during failures. That may be acceptable for metrics, clickstream samples, or noisy telemetry. It is a poor fit for orders, account changes, and money movement.
Exactly-once processing is the phrase that causes the most design damage when used loosely. Kafka supports idempotent producers and transactions, documented in its producer configuration and transaction APIs, but exactness is scoped. Kafka can coordinate writes to Kafka topics and offset commits. It cannot magically make an arbitrary external payment provider, SQL database, cache, and email service participate in one perfect distributed transaction.
A better way to reason about it is this: Kafka can provide strong guarantees inside its log. Your application must still define how side effects become idempotent, transactional, or compensatable outside that log.
API Patterns That Survive Failure
The producer API should publish facts, not commands disguised as facts. UserSignedUp is a fact. SendWelcomeEmail is a command. Both can be useful, but they create different coupling. Facts allow new consumers to appear later without changing producers. Commands encode the producer's assumptions about who should act.
Event schemas also need versioning discipline. A JSON blob with whatever fields the current service happens to emit will age badly. Teams often use Apache Avro, Protocol Buffers, or JSON Schema with a schema registry such as Confluent Schema Registry. The point is not ceremony. The point is allowing producers and consumers to deploy independently without breaking each other.
Retries need boundaries. A poison message that always fails should not block a partition forever. Dead letter queues or dead letter topics are the usual escape hatch. After a fixed number of attempts, the message is moved aside for inspection, alerting, and later replay. AWS documents this pattern for SQS dead-letter queues, but the same operational idea applies broadly.
Backpressure is the other half of the contract. Kafka's pull-based consumption means consumers read at their own pace. If they slow down, lag grows in the topic. That lag is not free, but it is observable and often survivable. Push-based systems such as RabbitMQ use flow control and prefetch settings, documented in RabbitMQ consumer prefetch, to avoid overwhelming workers.
Trade-Offs
Kafka is excellent when the system needs high-throughput event streams, replay, independent consumers, and partitioned ordering. It is a strong fit for activity feeds, event-driven integration, audit pipelines, analytics ingestion, stream processing, and rebuilding derived state.
Kafka is less pleasant when the workload is really a small task queue with complex routing, per-message priority, delayed jobs, or strict one-job-one-worker semantics. RabbitMQ, Amazon SQS, or a database-backed job system may be easier to operate and reason about for those cases.
The main Kafka trade-off is that it gives teams powerful primitives rather than a finished consistency model. You get topics, partitions, offsets, retention, transactions, and consumer groups. You still have to choose keys carefully, design idempotent consumers, monitor lag, manage schema evolution, and decide how replay interacts with side effects.
The lesson from LinkedIn's original pressure point is not that every system needs Kafka. The lesson is that synchronous dependency chains are a scalability liability when the user-visible action and the downstream reactions do not need to complete together. Message queues let engineers split those timelines. Kafka made that split durable, replayable, and scalable enough for large data systems.
Used well, the queue is not just a buffer. It becomes the system's memory of what happened. That is useful, but it also raises the bar for API design, consistency thinking, and operational discipline.

Comments
Please log in or register to join the discussion