Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
World desk8 min

How to Prevent Duplicate or Missing Chat Messages in Distributed Fan-Out

Retries can prevent silent message loss, but they can also repeat work. Learn how stable event IDs, Kafka’s scoped guarantees, recipient-level idempotency, and client replay work together to keep chat fan-out correct.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Preventing duplicate or missing chat messages requires correctness at every handoff: accept a message durably, publish it, process it, persist it for each recipient, and let clients catch up after interruptions. Retries reduce silent loss but can repeat work, so give each logical message a stable ID, make retryable effects idempotent, track progress carefully, and provide a way to replay gaps. Kafka can make certain producer and Kafka-to-Kafka workflows exactly once within defined boundaries; that does not make delivery to every database, push service, or chat client exactly once.

Where duplicates and gaps enter a fan-out system

A chat send is a chain of operations, not one delivery event. A service may accept a message, publish a fan-out event, route it to recipient-specific workers, write recipient state, and notify connected clients. A timeout or crash at any boundary can leave the sender unsure whether the previous step completed.

As an Amazon Associate I earn from qualifying purchases.

  • Acceptance: The service reports success before the message is durably recorded, or records it but fails before acknowledging the sender.
  • Publication: The message is stored in the source database, but the service crashes before publishing the fan-out event—or the broker accepts the event and its acknowledgement is lost.
  • Processing: A worker performs an effect and crashes before recording progress, so a retry performs the effect again. If it records progress first and crashes before the effect, the work can be skipped.
  • Recipient persistence: Some recipient records are written while others fail, leaving a partial fan-out.
  • Client synchronization: A push or WebSocket notification is lost, a device disconnects, or a client retries a request whose response never arrived.

These are distinct failure domains. A broker acknowledgement says something about the broker boundary; it does not prove that each recipient’s durable state was updated or that a device displayed the message.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose delivery semantics for each boundary

“At-most-once” and “at-least-once” describe trade-offs around retries and progress, not a guarantee that a message appears exactly once on every device. Apache Kafka 4.1 Design documentation says Kafka guarantees at-least-once delivery by default; at-most-once can be implemented by disabling retries and committing offsets before processing. That at-most-once choice risks missing work if a crash occurs after progress is committed but before processing finishes.

#1 Best Overall
Approach Loss and duplicate behavior What it does not guarantee
At-most-once Avoids retrying completed or uncertain work, but a crash between progress commit and processing can lose work. Apache Kafka 4.1 describes disabling retries and committing offsets before processing as one way to implement it. That every event is processed.
At-least-once Retries can recover transient failures, but a crash after processing and before progress is recorded can cause the operation to run again. That processing occurs only once; effects must tolerate repetition.
Idempotent Kafka producer Kafka producer identity and per-partition sequence numbers suppress duplicate records caused by internal producer retries within the documented scope. Deduplication across arbitrary services, independent producer sessions, or downstream side effects.
Kafka transactions Can atomically publish Kafka output records and commit consumed Kafka offsets in a consume-transform-produce workflow. Atomic effects in an external database, push provider, or end-user device.

Kafka ordering is per topic partition, represented by sequential offsets; it is not a global ordering across a topic’s partitions. Design routing and ordering requirements around the partition key and the chat behavior you need rather than assuming that all fan-out recipients observe one universal sequence.

Give a logical chat event one stable identity

Create an application-level event ID once, at the authoritative message-write boundary, and carry that same ID through publication, retries, recipient writes, and client synchronization. A retry is another attempt to deliver the same logical event, not a new message. Kafka’s producer ID and sequence numbers address internal retry duplication in Kafka; they are not a shared chat event ID that independent services can use to recognize the same message.

A useful event envelope can include an immutable event_id, a conversation_id, a conversation sequence or cursor where your application defines one, the sender and payload, and the event schema version. The ID is the deduplication key; the conversation cursor supports ordering and gap detection. These are application design choices, not fields or guarantees imposed by Kafka.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

At each retryable destination, make the effect conditional on the ID. For example, a recipient store can enforce uniqueness for the pair of recipient and event ID, then treat a repeated insert as already applied rather than creating another visible message. A notification worker can record that it has handled an event before deciding whether to send another best-effort notification. Keep notification delivery separate from durable message state: notifications can be retried or missed, while the stored conversation history remains authoritative.

Close the gap between durable acceptance and publication

If the chat service writes an accepted message to an application database and publishes a Kafka event in a separate operation, there is a crash window between those writes. Publishing first can create a fan-out event for a message that the database never committed; committing the database first can leave a stored message without a published event.

A common application design option is a transactional outbox: write the message and a pending publication record in the same database transaction, then have a publisher send pending records and mark them published. The publisher can itself retry, so downstream consumers still need stable event IDs and idempotent effects. This pattern coordinates durable source state with a publication workflow, but it is not a Kafka transaction spanning an arbitrary database. The Kafka transaction guarantees described in Apache Kafka 4.1 apply to Kafka records and consumed Kafka offsets, not an unrelated database commit.

Use Kafka idempotence and transactions within their scope

Producer retries

When a producer times out after Kafka accepted a record but before the acknowledgement reaches the producer, retrying can otherwise create a second record. Kafka’s idempotent producer uses a producer identity and per-partition sequence numbers to deduplicate those internal retries and preserve order within a partition. Enable producer idempotence using the client’s supported configuration, and verify the behavior and compatible settings against the Kafka client version actually deployed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In the KIP-98 design, idempotent behavior is scoped to a producer session. For recovery across producer restarts, use a stable transactional.id; Kafka can use it to recover transactional state and fence an older producer instance. This still does not replace the application event ID: separate publishers or later application-level retries may not represent the same Kafka producer session.

Kafka-to-Kafka processing

For a consumer that reads Kafka input, transforms it, and writes Kafka output, the strongest pattern is to publish the output and advance the consumed offsets in the same Kafka transaction. Configure enable.auto.commit=false and use isolation.level=read_committed for consumers that must not read aborted transactional output. If a transaction aborts, restore or seek the consumer to the last committed input position so the input can be processed again.

This addresses the familiar choice between committing progress before processing, which can skip work, and processing before committing progress, which can repeat work after a crash. It applies to the Kafka input/output workflow; it does not atomically include recipient database writes or device delivery. Kafka’s exactly-once processing guarantee should therefore be stated with its scope, not shortened to “chat messages are delivered exactly once.”

Make recipient fan-out recoverable

Recipient routing creates an application-specific completion problem. A single event might need to update many recipient inboxes, and a worker can fail partway through. Track work so that retrying unfinished recipients is possible without duplicating completed writes. The stable event ID should follow the message into every recipient-level operation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Keep the accepted message and enough durable history to reconstruct the intended conversation state.
  • Track a recipient’s last applied cursor or another explicit progress marker when the application needs recipient-level recovery.
  • Make recipient writes idempotent, and ensure progress advances only when the corresponding effect has completed or can safely be repeated.
  • For large fan-outs, record or derive completion per recipient or per bounded work unit rather than treating one worker acknowledgement as proof that all recipients were updated.

Kafka’s cited documentation explains partition offsets and Kafka transaction behavior; it does not prescribe a per-recipient chat cursor. The cursor and completion scheme must match the application’s storage model, recipient semantics, and retention policy.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Let clients detect and repair gaps

A connected client is not a durable delivery destination. A device can miss a notification while offline, switch networks, or reconnect after the server has moved on. Treat push and WebSocket messages as prompts to synchronize, not as the sole record of the conversation.

  1. Persist messages in the authoritative conversation history and assign a stable sequence or cursor whose meaning is consistent for that conversation.
  2. Have each client retain the last cursor it has durably applied, not merely the last notification it received.
  3. On reconnect, request changes after that cursor. If the server detects a discontinuity or the requested history is no longer retained, return a repair path such as a current snapshot followed by subsequent events.
  4. Have the client deduplicate by event ID and apply messages in the order required by the product. If an event arrives twice, it should not create two visible messages.

This replay-and-reconciliation mechanism is an application design recommendation, not a Kafka guarantee. It also handles the reverse of message loss: a client may receive an event notification before it can fetch the corresponding durable message, so synchronization should retry against the authoritative store.

Set durability expectations at the broker boundary

Do not equate a successful producer acknowledgement with an unconditional guarantee that a record survives every failure. Deployment-level durability depends on the configured replication and acknowledgement policy and on which replicas remain available. State those conditions when making a durability claim, and validate them for the broker configuration and client version in use.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Likewise, Kafka transactions make Kafka output records and offsets atomic for the supported workflow, but they do not mean that every consumer reads all records in a transaction as one indivisible batch in every circumstance. Seeking within transactions, omitting participating partitions, and retention or compaction can affect what a consumer observes. Define consumer behavior and retention to fit the recovery guarantees the chat system promises.

Practical correctness checklist

  • Generate the event ID once when the message is accepted; preserve it through every retry and fan-out path.
  • Make each retryable database or notification effect idempotent, with uniqueness or deduplication based on that ID.
  • Use Kafka producer idempotence for Kafka internal retry handling, while keeping application-level deduplication separate.
  • For Kafka consume-transform-produce, transact output records and input offsets together, disable auto-commit, and use read-committed visibility where appropriate.
  • Do not claim Kafka transactions include a separate database, push service, or client; coordinate those effects or provide idempotency and repair.
  • Retain enough history and client cursor state to replay or reconcile recipient and reconnect gaps.
  • Test the uncertain outcomes that cause most errors: acknowledgement lost after acceptance, crash after effect before progress commit, crash after progress commit before effect, partial recipient fan-out, and a client reconnecting behind the conversation cursor.

Apache Kafka’s 4.1 Design documentation summarizes the external-system boundary this way: “Exactly-once delivery for other destination systems generally requires cooperation with such systems, but Kafka provides the primitives which makes implementing this feasible (see also Kafka Connect).” The practical target for chat is therefore not one global exactly-once switch; it is a chain of durable state, retry-safe effects, scoped Kafka guarantees, and explicit recovery at the recipient and client boundaries.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.