Repository navigation
Conversation
…nd new messages The broker deduplicates a batch by its sequence_id and highest_sequence_id. The Go client never set highest_sequence_id and let a resent message share a batch with new ones, so a batch like [dup 0, new 2] was dropped as a whole and a resend of a message from the middle of an earlier batch was persisted twice. - Set highest_sequence_id on batched CommandSend and MessageMetadata. - Flush the current batch before a message whose sequence ID is not above the last pushed one, and keep such messages in their own batch, as the Java client does (apache/pulsar#6326). - LastSequenceID() now reports the highest acknowledged sequence ID.
|
Thanks for the fix — I verified it locally on the PR branch and the analysis and implementation check out:
Two non-blocking questions:
|
Make the per-batch invariant explicit instead of relying on the first Add of the next batch to overwrite it, and assert it in the batch builder test. Also document why only the first connection seeds the sequence state from ProducerSuccess.
|
Thanks for verifying it so thoroughly.
|
Fixes #1395
Motivation
With batching enabled, the broker deduplicates a batch using only its
sequence_id(lowest) andhighest_sequence_id. The Go client had two gaps there:highest_sequence_id, so the broker recorded only the first sequence ID of each batch.Against a standalone 4.2.5 broker with deduplication enabled, that gives:
main[0, 1], then[0 (resend), 2][0, 1, 2], then[1 (resend)], then[3]The first row is the case from the issue (data loss), the second is a duplicate getting through.
Modifications
batchContainertracks the highest sequence ID it holds and setshighest_sequence_idon both the batchCommandSendandMessageMetadata(also per key for the key-based builder).FlushBatchandpendingItemcarry it.partitionProducertracks the last pushed sequence ID (seeded fromProducerSuccess.last_sequence_id). A batched message whose ID is not above it flushes the current batch and goes into its own batch, and the next message flushes again, so potential duplicates are never mixed with new messages. This is the same rule as the Java client ([Fix-6273] Flush the potential duplicated message when add messages to a batch. pulsar#6326), including its warn/info logs.LastSequenceID()now reports the highest acknowledged sequence ID instead of the first ID of the last acknowledged batch, matching the Java client.Producers that use generated sequence IDs are not affected: IDs only increase, so batches are built exactly as before (covered by a test).
Verifying this change
pulsar/internal/batch_builder_test.go: decodes flushed frames and checkssequence_id/highest_sequence_idfor the default and key-based builders, and that the value resets between batches.pulsar/producer_partition_test.go: drivesinternalSendwith a mock connection and checks how messages are split into batches (issue scenario, consecutive resends, resend from the middle of an in-flight batch, generated IDs) and thatLastSequenceID()follows acks. Runs without a broker, also with-race.pulsar/producer_test.go:TestProducerDeduplicationWithBatchedResendsenables deduplication on a fresh namespace and checks what the consumer actually receives for both rows in the table above. It fails onmainand passes with this change (run locally against a 4.2.5 standalone).go vetandgolangci-lint run ./pulsar/...(v2.12.2) are clean;go test ./pulsar/internal/...passes.This change was prepared with AI assistance (per the ASF generative tooling guidance); all tests listed above were run locally.
Does this pull request potentially affect one of the following parts:
LastSequenceID()semantics now match the Java client for batches)highest_sequence_idfield on batched sends