~bigbes/tarantool-etcd · parade

main · last commit 1 month ago · 2npkec0r

← Back to the parade

tarantool-etcd-vnp Watch/Range reject compact_revision itself — off-by-one vs etcd, livelocks watch recovery Lined Up

status: open P0 bug etcd-changelog-sweep
bd update tarantool-etcd-vnp --claim bd close tarantool-etcd-vnp
Created byEugene Blikh
Ownerbigbes@gmail.com
Created2026-07-17T13:18:41Z
Updated2026-07-17T13:18:41Z
Description
etcd keeps the tombstone whose revision == the compact revision and rejects only rev < compactMainRev (verified at server/storage/mvcc/kvstore_txn.go:80 — 'if rev < tr.s.compactMainRev'). A watch or range started exactly AT compact_revision succeeds.

We reject at <= on both paths and delete the boundary row outright:
- app/etcd/compact.lua:63-84 — the batch loop breaks on 'tuple.mod_revision > target_rev', so it deletes every kv_history row with mod_revision <= target_rev, INCLUDING the entry at target_rev.
- app/etcd/watch.lua:281 — 'if opts.start_revision <= compacted then return errors.revision_compacted(compacted)'
- app/etcd/kv.lua:261 — 'if target_rev <= compacted then error(errors.revision_compacted())'

Why this is more than cosmetic: etcd's documented watch-recovery pattern is 'on a canceled response carrying compact_revision, restart the watch at compact_revision'. Against this server that revision is itself rejected, so a conforming clientv3 watcher that survives a compaction retries forever against the same rejection — an infinite cancel loop.

Verified by hand (not just by the sweep agent) against both our source and the upstream clone.
Design
Two coupled changes; do them together or the boundary stays inconsistent: (1) compaction must retain rows with mod_revision == target_rev — break the loop on '>=' rather than deleting through the boundary; (2) both guards move from '<=' to '<'. Note the retained boundary row must be the tombstone/latest version at that revision, matching etcd's key_index.go behavior of keeping the generation's last entry.
Acceptance criteria
Compact(N) then Watch(start_revision=N) succeeds and delivers the event at N, matching etcd; Range(revision=N) after Compact(N) succeeds; a conformance test probes the compact-revision boundary itself on both backends (current compact_test.go only probes strictly below it); clientv3 restart-at-CompactRevision terminates.

Depends on

No outgoing dependencies.

Depended on by

Nothing depends on this issue.

No comments.

  • Eugene Blikh created the issue · 2026-07-17T16:18:41Z
  • Eugene Blikh added label etcd-changelog-sweep · 2026-07-17T16:18:41Z
Stored rows — what this pane was built from, as read
issues 1 row
id tarantool-etcd-vnp
content_hash 1d9a61e1b6a987a8cb6ddae9833f7073ce29f05f3b852205dae442bfa24f3819
title Watch/Range reject compact_revision itself — off-by-one vs etcd, livelocks watch recovery
description etcd keeps the tombstone whose revision == the compact revision and rejects only rev < compactMainRev (verified at server/storage/mvcc/kvstore_txn.go:80 — 'if rev < tr.s.compactMainRev'). A watch or range started exactly AT compact_revision succeeds. We reject at <= on both paths and delete the boundary row outright: - app/etcd/compact.lua:63-84 — the batch loop breaks on 'tuple.mod_revision > target_rev', so it deletes every kv_history row with mod_revision <= target_rev, INCLUDING the entry at target_rev. - app/etcd/watch.lua:281 — 'if opts.start_revision <= compacted then return errors.revision_compacted(compacted)' - app/etcd/kv.lua:261 — 'if target_rev <= compacted then error(errors.revision_compacted())' Why this is more than cosmetic: etcd's documented watch-recovery pattern is 'on a canceled response carrying compact_revision, restart the watch at compact_revision'. Against this server that revision is itself rejected, so a conforming clientv3 watcher that survives a compaction retries forever against the same rejection — an infinite cancel loop. Verified by hand (not just by the sweep agent) against both our source and the upstream clone.
design Two coupled changes; do them together or the boundary stays inconsistent: (1) compaction must retain rows with mod_revision == target_rev — break the loop on '>=' rather than deleting through the boundary; (2) both guards move from '<=' to '<'. Note the retained boundary row must be the tombstone/latest version at that revision, matching etcd's key_index.go behavior of keeping the generation's last entry.
acceptance_criteria Compact(N) then Watch(start_revision=N) succeeds and delivers the event at N, matching etcd; Range(revision=N) after Compact(N) succeeds; a conformance test probes the compact-revision boundary itself on both backends (current compact_test.go only probes strictly below it); clientv3 restart-at-CompactRevision terminates.
notes
status open
priority 0
issue_type bug
assignee NULL
estimated_minutes NULL
created_at 2026-07-17T13:18:41Z
created_by Eugene Blikh
owner bigbes@gmail.com
updated_at 2026-07-17T13:18:41Z
closed_at NULL
closed_by_session
external_ref NULL
spec_id
compaction_level 0
compacted_at NULL
compacted_at_commit NULL
original_size NULL
sender
ephemeral 0
wisp_type
pinned 0
is_template 0
mol_type
work_type
source_system
metadata <binary>
source_repo
close_reason
event_kind
actor
target
payload
await_type
await_id
timeout_ns 0
waiters
hook_bead
role_bead
agent_state
last_activity NULL
role_type
rig
due_at NULL
defer_until NULL
no_history 0
started_at NULL
is_blocked 0
labels 1 row
issue_id tarantool-etcd-vnp
label etcd-changelog-sweep
events 2 rows
id 019f703a-ac96-7ce8-bc76-b40bfb3065d8
issue_id tarantool-etcd-vnp
event_type created
actor Eugene Blikh
old_value
new_value
comment NULL
created_at 2026-07-17T16:18:41Z
id 019f703a-ac97-7add-9250-528204146424
issue_id tarantool-etcd-vnp
event_type label_added
actor Eugene Blikh
old_value NULL
new_value NULL
comment Added label: etcd-changelog-sweep
created_at 2026-07-17T16:18:41Z