Skip to content

ZOOKEEPER-2789: Reassign ZXID to a 24-bit epoch / 40-bit counter with rolling upgrade#2417

Open
asdf2014 wants to merge 1 commit into
apache:masterfrom
asdf2014:reassign_zxid
Open

ZOOKEEPER-2789: Reassign ZXID to a 24-bit epoch / 40-bit counter with rolling upgrade#2417
asdf2014 wants to merge 1 commit into
apache:masterfrom
asdf2014:reassign_zxid

Conversation

@asdf2014

@asdf2014 asdf2014 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Continues #262 (ZOOKEEPER-2789), reworked and rebased onto current master as a single commit.

What this does. The zxid stays a 64-bit epoch | counter. This adds an opt-in wide-counter layout — 24-bit epoch / 40-bit counter — pushing the counter-rollover-forces-reelection horizon (ZOOKEEPER-1277) from ~49.7 days to ~34.9 years at 1k writes/s, while 24 bits of epoch still lasts ~1915 years at hourly elections. Off by default; enabled per-ensemble via rolling upgrade — no downtime, no reformat.

Design:

  • Opt-in + capability-gated. zookeeper.wideCounterZxidEnabled (default off). The learner protocol version bumps 0x100000x11000 to advertise "I understand the wide layout"; once a leader has switched it refuses any learner below 0x11000, so a half-upgraded ensemble can't feed wide zxids to a node that would misparse them.
  • The switch rides an epoch bump. The leader switches at its own new epoch and announces that switch epoch in the LEADERINFO handshake; every member persists it (new zxidLayoutSwitchEpoch file). Because the first wide zxid (switchEpoch<<40) numerically exceeds every legacy zxid of prior epochs, the total order of zxids is preserved — the numeric zxid comparisons in ZAB keep working untouched.
  • Mixed on-disk data is handled, not reformatted. Nothing rewrites existing snapshots/txnlogs. ZxidLayoutState.layoutFor(zxid) picks the decode layout per zxid by comparing against the switch point, so a legacy new-epoch zxid (5,0) is still read as counter==0 and LearnerHandler.syncFollower() makes the right DIFF/TRUNC/SNAP call across the boundary.
  • One-way, with a downgrade guard. The switch never moves once wide data exists; a node already holding wide zxids refuses to follow a legacy-layout leader rather than corrupt the order.

Upgrade validation. WideCounterZxidUpgradeTest runs a real 3-node ensemble end to end: commit legacy data → rolling-restart with the flag → new leader switches → verify legacy data survives, wide+legacy zxids keep total order, a lagging follower re-syncs across the boundary, and the switch epoch reloads after a full restart.

Merge & review. Rebased onto current master (~880 commits past the old merge-base); adapted to ZOOKEEPER-4925 and the removed loggraph module. I ran an adversarial backward-compat review over the whole change and fixed a real gap where a pre-3.4.6 learner could slip past the wide-layout refusal, plus a diagnostics issue in offline zkTxnLogToolkit chop. New unit + integration tests throughout; checkstyle / RAT / spotbugs clean.

@asdf2014 asdf2014 force-pushed the reassign_zxid branch 2 times, most recently from 27a7697 to 0c5778c Compare July 7, 2026 07:02
… rolling upgrade

Merge current master into the reassign_zxid branch and rework the change into an
opt-in, rolling-upgradeable wide-counter zxid layout.

The 32-bit counter overflowing forces a leader re-election roughly every 49.7
days at 1k writes/s (ZOOKEEPER-1277). This adds a 24-bit epoch / 40-bit counter
layout that pushes that to ~34.9 years while still allowing ~1915 years of
hourly elections before the epoch overflows.

- ZxidLayout / ZxidLayoutState: the legacy 32/32 and wide 24/40 layouts plus a
  per-QuorumPeer record of the epoch from which the ensemble is wide. The static
  ZxidUtils helpers remain the legacy-fixed layout for the on-the-wire epoch
  carrier zxids of the handshake.
- Opt-in via zookeeper.wideCounterZxidEnabled (default off). A newly elected
  leader switches at its new epoch, persists it to a new zxidLayoutSwitchEpoch
  file, and announces it in the LEADERINFO handshake (learner protocol version
  bumped to 0x11000). Learners below 0x11000 are refused once switched.
- The switch coincides with an epoch bump, so the numeric order of zxids is
  preserved; layoutFor(zxid) selects the decode layout per zxid so existing
  legacy snapshots/txnlogs are read correctly across the boundary without being
  rewritten. One-way switch with an anti-downgrade guard.
- Replace hardcoded zxid bit operations with layout-aware calls across Leader,
  LearnerHandler, Learner, Follower, ObserverMaster, ZKDatabase,
  FastLeaderElection, Commands and LogChopper.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant