maxTermJump
Parameters
How far above this node's own term a frame may claim to be and still be admitted (#1897). A frame whose term exceeds currentTerm + maxTermJump is dropped at the dispatch boundary, before any adoption.
The bound is relative, and that is the whole point. Bounding the term's value against a constant A cannot avoid a cliff: closing it would need T ≤ A ⟹ T + 1 ≤ A, true only for A = ∞, so the one value such a filter admits is the value whose successor every peer — including its own author — refuses. Bounding the jump admits currentTerm + 1 at every term, so no term has an unrepresentable successor. It is also the stronger test: an implausible term is implausible relative to what this node has seen, which is a local witness, where a constant is a guess about the deployment.
The default is 10 000, sized between the two failure modes it sits between:
Too small breaks rejoin. A node that was away while the cluster held many elections must still be able to adopt the current term. Terms advance once per election, so 10 000 covers an absence spanning ten thousand of them — hours of pathological churn, and orders of magnitude beyond an ordinary partition, which costs a handful.
Too large weakens the bound. The value is the attacker's step size: reaching the arithmetic danger zone near Long.MAX_VALUE costs roughly
2^63 / maxTermJumpaccepted frames. At 10 000 that is ~10^15 — infeasible — where the absolute ceiling it replaces cost exactly one frame.
A deployment whose members routinely miss more than 10 000 elections has a liveness problem this knob would only hide; the documented route back for a node that has genuinely fallen outside the bound is a new identity plus an ordinary membership change, never a wiped disk under the same one. See docs/raft-wedge-diagnosis-and-recovery.md.
Validated at construction to 1..2^20 (1 048 576), inclusive (#1972). A knob whose whole job is to bound a safety property must not be settable to a value that defeats it. The two ends are not the same kind of limit, and reading them as symmetric is the error to avoid:
The floor is a cliff, and it is at 0. A jump of exactly one is refused there (
1 > 0), so no candidate'scurrentTerm + 1is ever admitted by anyone and no leader can be elected again. Below 0 the subtraction exceeds the bound for every frame at or above our own term, and the node goes silently deaf — nothing logs abovedebug. This is a property of the arithmetic, not a judgement call: 1 is the smallest value that preserves liveness, so it must be admitted rather than merely non-negative.The ceiling is a chosen line on a continuum. There is no cliff at
2^20. The attack price2^60 / maxTermJumpdegrades continuously as the knob rises — at2^20 + 1the climb still costs about2^40frames, indistinguishable from the value chosen here — and the guard becomes literally vacuous, one frame reinstating #1833's cluster-wide wedge, only asmaxTermJumpapproaches2^60itself. So the ceiling is not where the bound stops bounding; it is the largest value at which the price is still guaranteed to be at least the2^40frames derived below, placed where that guarantee costs no honest deployment anything.
That guarantee is what the ceiling is chosen to hold, from both directions:
Attack cost. This value is the attacker's step size, so climbing from term 0 to the storage-path ceiling
RaftEngine.MAX_PLAUSIBLE_TERM(2^60) costs2^60 / maxTermJumpaccepted frames. At2^20that is2^40≈ 1.1×10^12 — over twelve days of uninterrupted attack even at a sustained million admitted frames per second, a rate far above what any real Raft peer processes. Before #1897 the same climb cost exactly one frame.This
2^60and the2^63in the default's derivation above are different thresholds and both are live — do not reconcile one to the other.2^60is where a running node can still be driven (adoption is relative now, so nothing refuses a term above it) but can no longer restart, becausecheckedRestoredTermrejects a durable term past it; the climb towardLong.MAX_VALUEcontinues from there, and is where the arithmetic itself breaks.Legitimate need. Terms advance once per election, so
2^20covers roughly 1.05 million missed elections — 100× the default above, and still some 29 hours of absence at a pathological ten elections per second. A deployment that genuinely exceeds it has the liveness problem described above, which this knob would hide rather than fix.
Throwing here is right, and is not in tension with RaftEngine.onMessage's refusal to throw on a malformed frame (#1818): this is local, deterministic, consumer-supplied configuration evaluated once at construction, not a value a peer controls on the actor loop.