Skip to content

server/storage/backend: dedupe intra-batch duplicate keys in writeback before merge#21987

Open
wilmerdooley wants to merge 1 commit into
etcd-io:mainfrom
wilmerdooley:oss/issue-21986
Open

server/storage/backend: dedupe intra-batch duplicate keys in writeback before merge#21987
wilmerdooley wants to merge 1 commit into
etcd-io:mainfrom
wilmerdooley:oss/issue-21986

Conversation

@wilmerdooley

Copy link
Copy Markdown

When the read buffer already holds a bucket, txWriteBuffer.writeback folds the write buffer in via bucketBuffer.merge. The non-sequential path was sorting and assuming no duplicate keys, but put/putInternal/add append unconditionally, so a key overwritten twice in one batch (e.g. [(k,OLD),(k,NEW)]) survived the merge. A subsequent single-key read with endKey == nil forces limit=1, and bucketBuffer.Range/sort.Search returns the smallest-index match, returning the stale OLD value from the in-memory buffer.

This change makes the non-sequential branch in writeback call wb.dedupe() before rb.merge(wb), mirroring the sibling delete branch and honoring the newest-wins contract that TestDedupe pins. bucketBuffer.merge itself is intentionally left unchanged: its only caller (writeback) now guarantees the source is duplicate-free for non-seq buckets, and adding dedupe inside merge would double-dedupe non-seq buckets while needlessly sorting the hot sequential Key/revision bucket on every merge.

Resolves #21986

Changes:

  • server/storage/backend/tx_buffer.go: in txWriteBuffer.writeback, replace the non-sequential sort.Sort(wb) (gated on wb.used > 1) with wb.dedupe() for the read-buffer-already-has-bucket branch.
  • server/storage/backend/tx_buffer_test.go: add TestWritebackDedupesIntraBatchDuplicateForNonSeqBucket, a regression test that exercises the bug through the real writeback path on a non-sequential bucket. It puts the same key twice in one batch, primes the read buffer with a smaller key so the no-overlap merge fast-path fires, calls txw.writeback(txr), and asserts that the subsequent txr.Range(bucket, key, nil, 1) returns NEW. The test fails on the previous sort.Sort logic and passes after the fix.

This PR was written in part with the assistance of generative AI; all changes were authored and reviewed by me.

…k before merge

Signed-off-by: wilmerdooley <wilmerdooley1@gmail.com>
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: wilmerdooley
Once this PR has been reviewed and has the lgtm label, please assign ahrtr for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow

Copy link
Copy Markdown

Hi @wilmerdooley. Thanks for your PR.

I'm waiting for a etcd-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

backend: stale single-key read after intra-batch overwrite (merge no-overlap fast-path skips dedupe)

1 participant