Make the retention clocks survive a metaserver restart - #235
Conversation
Everything the metaserver ages -- retention purge, freeze aging -- was stamped with now_ms() inside the apply path. Replay runs that same path, so every clock was re-stamped with the clock of whenever the metaserver last came up. Measured across one restart with a 40ms pause: the frozen table's clock moved 44ms, the dropped table's 43ms, the dropped server's 42ms. Every one moved by exactly the pause. On a real cluster the pause is the restart, so retention purge never collects anything and freeze aging never fires on a cluster that restarts more often than those windows. Both fail silently -- the mechanism runs, finds nothing old enough, and reports success. The log line now carries the time the metaserver accepted the change and replay stamps with that. record_mutation returns the time it wrote, and the five apply paths that stamp a clock take it as an argument instead of reading the clock themselves. This also fixes frozen_since_ms and freeze_cooldown_until_ms on a server, which came from the same value. Both compatibility directions were measured, not assumed. A line written before the field exists has no time, defaults to zero, and replay falls back to the current clock -- exactly what those lines have always been given. And a build that predates the field can still read a line that carries one: serde ignores it as unknown, which is worth checking rather than assuming because adjacently tagged enums are a special case. Tests pin both. Only the paths whose stamp is consumed at replay are changed. Heartbeat and boot times still take the current clock, which is arguably right for liveness values a real heartbeat corrects within seconds, and is a separate question.
7057399 to
b9ef8f5
Compare
|
This PR also collides with #285, and that resolution is written and tested — branch No branch of yours has been touched. That one is #231, then #285 composed onto The collision is over the same two lines. This PR makes the drop clock come from Git's automatic merge produces something that does not compile, which is the The trap is what comes next. The quickest way to make it compile is to delete The resolution that keeps both: the lock-holding helper takes |
Every retention clock restarted when the metaserver restarted
Everything the metaserver ages — retention purge, freeze aging — is stamped with
now_ms()inside the apply path. Replay runs that same path, so every clockwas re-stamped with the clock of whenever the metaserver last came up.
Measured across one restart, with a 40ms pause in between:
Every clock moved by exactly the pause. On a real cluster the pause is the
restart, so:
often than its retention window — tombstones accumulate forever
after any restart
Both failures are silent. The mechanism runs, finds nothing old enough, and
reports success.
The change
The log line now carries the time the metaserver accepted the change, and replay
stamps with that instead of the current clock.
record_mutationreturns the timeit wrote, and the five apply paths that stamp a clock take it as an argument
rather than reading the clock themselves.
It also fixes
frozen_since_msandfreeze_cooldown_until_mson a server, whichcame from the same
nowand had the same problem.Compatibility, measured rather than assumed
The log is JSONL. The time is an added field, so I checked both directions
before writing any of this:
New build, old line —
at_msis absent, defaults to 0, and replay fallsback to the current clock. That is exactly what those lines have always been
given, so nothing changes for a log already on disk.
Old build, new line — I measured this rather than trusting that serde
ignores unknown fields, because adjacently tagged enums are a special case:
So rolling back to a build that predates this does not strand the log. A test
pins that behaviour.
Tests
The last two pass either way, deliberately. They guard the format change, which
is the part that would hurt most if it broke, and the part a later edit is most
likely to break.
Scope
Only the paths that stamp a clock consumed at replay.
last_heartbeat_msandboot time still take the current clock on replay, which is arguably right — they
describe liveness, and a real heartbeat corrects them within seconds — but it is
a separate question from this one and I have not touched it.
Verification
cargo check --all-targets— 0 errorscargo test --bin metaserver -- --test-threads=1cargo test --lib -- --test-threads=1The
data_node::…jitter_backoff…andengine::…recovery_validates_…failuresreproduce on an unmodified tree at this base and are untouched here.
Correction: which backend this affects
I originally wrote the consequence as though it applied to any cluster. It does
not, and the difference matters for judging this change.
The single-node backend drives retention GC and freeze aging, so everything
above applies to it directly: the clocks reset on restart, and neither mechanism
can reach its threshold on a cluster that restarts often.
The raft backend does not run either loop at all. Both are refused at
startup:
So on raft the clocks were wrong and nothing consumed them. This change makes
them correct; it does not make retention run there. That is a separate, larger
piece of work — the tombstones a raft metaserver accumulates are still
accumulating after this merges, and its mutation log is never compacted either.
The change is still worth making on its own terms — the clocks are wrong on both
backends, the single-node one acts on them today, and any future raft-side
retention needs them right before it can work — but I would rather state the
scope than let the measurement above imply more than it shows.
Second correction: the retention framing undersold this
My first correction narrowed the consequence to the backends that run retention.
That was accurate as far as it went and left the wrong impression, because the
same stamp feeds two things that are not behind any switch.
A freeze cooldown restarted on every metaserver restart. The cooldown is
computed as
freeze_cooldown_until_ms = now + freeze_cooldown_msfrom the samevalue, and it is enforced in
apply_register_serverandapply_register_proxy,gated by nothing:
So a resource frozen with a ten-minute cooldown was locked out for a fresh ten
minutes after every restart, on any configuration, including the default one.
frozen_since_msis operator-visible. It is a public field on bothServerMetaInfoandProxyMetaInfo, so it is serialised to/serversand/proxies. Before this change it reported "frozen since the last restart"rather than when the freeze happened.
Neither depends on retention GC or freeze aging being enabled. Nine of the
metaserver's twelve switches default to off — including retention GC — so the
retention consequence reaches fewer deployments than these two do.
Correction to the verification note above
I described two failures as reproducing on an unmodified tree. Re-checked on
a quiet machine with disk headroom, run in isolation against unmodified
main:data_node::…jitter_backoff…engine::…recovery_validates_all_timestamped_kv_page_familiesOnly the first is pre-existing. My original evidence for the second came from a
run taken immediately after the disk hit 100%, so it was environmental — that
test is sensitive to disk pressure and load, not broken on
main.The conclusion this change is not responsible for either failure is unchanged.
The evidence offered for half of it was wrong, and the record should say so.