Replies: 8 comments 9 replies
This comment has been hidden.
This comment has been hidden.
|
Thanks for the design! There's one extra issue with synchronous join you haven't mentioned: when an entry with replica's ID is committed on master, master re-calculates its replication_synchro_quorum.
That's a serious problem. On large installations with high write load it's quite easy to achive situations where the final join stage will run for minutes. And for that whole time the cluster will be unable to serve writes given an unlucky enough replica topology. For example: The alternative proposal (with bootstrap protocol rework) handles this issue in the following way:
Speaking of your proposal, I like that it turned out so concise and easy to implement, but I don't see how this issue can be handled there. Maybe you have any ideas? |
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
|
Good RFC, we still have several major comments to discuss, but in general it's already in very good shape.
In the scope of the RFC review I won't review the code descriptions you have there, I suppose it should be done in the PR when it's ready. Basically, in that specific case I'm ok with anon bounded subscribe, but I'm not sure introducing the flag So, if I were you I'd avoid using such specific wording of solutions in code, we're not discussing the implementation, only ideas. But up to you.
I find this part of description a little bit confusing and just wanna make sure, we understand that: Replica is always "able to send ACKSs", there's only two questions:
Speaking of the last one: can we consider sending ACKs as early, as possible? Now we start the ACK machinery on replica only in SUBSCRIBE, but we can do that even earlier: in the register stage. But only because of the changes, proposed in the current RFC. There is an important durability condition: ACKs must represent persisted data. The proposed flow creates the initial checkpoint and enables WAL before registration, which supports this. Legacy JOIN applies its final stream through a recovery journal that performs no actual writes, so we cannot give that path the same quorum (see the
If only it was just that flow) The problem here is that recovery happens a lot earlier than we subscribe: the connections are created, we check ballots, then appliers are on pause and we recover snaps/xlogs (which can take hours) and only after that appliers are resumed. So, we'll just exit with error after user waited for 5 hours for tarantool to start. I agree, this is not an option for sure.
And this solution looks like the breaking change to me: it changes more than And in order not to introduce the breaking change we should use the solution, Vladislav proposed: register after bounded catch-up wait. But using the
It seeems I'm ok with automatic register if no @Gerold103, please, resolve the comments, if they're not useful anymore, way too many text in the comments |
This comment has been hidden.
This comment has been hidden.
|
I will tick the box. But the document still seems not complete. The comments about part 1 and related points from part 2 doesn't look all addressed. Below is my summary, and in the end of the message is a more detailed analysis from Fable.
v6 is the split from my previous comment, part 1 alone as the #11039 fix, good. Below is part 1 reviewed as a standalone change. The text is right where it matters: the snapshot comes from an in-memory checkpoint, which waits for the last pending synchronous transaction to be resolved before the read view is frozen, so the replica's limbo is empty when it writes its own checkpoint. And the The register step is not in the change list. Items 1 to 4 fetch the snapshot and remove The restart in the window did not leave with part 2. The window is between the initial checkpoint and the arrival of the own The late registration failure from my previous comment applies to part 1 unchanged, the price being the snapshot transfer instead of hours. The two asks stay: a failed registration keeps the transfer, which is the restart-in-window path above, and configuration errors from One con to add. Until Smaller things. Changelog: "fixex". The pros say "only replica's side changes"; true, and worth keeping true through the points above, they all are. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
Tickets
Changelog
Problem overview
Master & Replica deadlock during bootstrap
Bug description:
Q.stop_vclockwhile the CONFIRM is still unpersisted.FINAL_JOINstage, the master sends the sync txn's data (≤ stop_vclock) but not its CONFIRM (> stop_vclock).SUBSCRIBE, which starts only after the checkpoint - circular wait.replication_synchro_timeout, then bootstrap fails.The places in the code where the bug is reproduced:
Replica's side:
Master's side:
Solutions
1. Changing the bootstrap protocol (getting rid of FINAL_JOIN stage)
In Tarantool there are 3 main ways how the bootstrap process can proceed:
Anonymous bootstrap
AnonToNamed bootstrap
Named bootstrap
The solution reorders the bootstrap so that registration (which increases the master's sync quorum) happens only after the replica is already an active, catching-up subscriber that can send ACKs. Concretely, a named replica no longer bootstraps via
JOINfrom a new master; instead it fetches the master's snapshot, builds its initial checkpoint, subscribes and catches up anonymously, and only then sendsREGISTER.Fix of Master & Replica deadlock during bootstrap Replica bootstrap / rebootstrap fails if role writes to sync space #11039
We replace the
JOIN-based bootstrap of a named replica with the sequence that anonymous replicas already use, plus a deferred registration:FETCH_SNAPSHOT→initial checkpoint→REGISTER→SUBSCRIBE. The key idea is a small reordering of bootstrap process — the replica's initial checkpoint is created before any WAL stream that can carry unconfirmed synchronous transactions:FETCH_SNAPSHOTinstead ofJOIN. The replica receives a consistent snapshot (with limbo/raft state in JOIN_META) viabox_process_fetch_snapshot, which never sends unconfirmed rows. The replica recovers from it and creates its own initial checkpoint while its limbo is guaranteed clean, so nothing can be placed into the limbo duringgc_checkpoint.REGISTERafter the checkpoint. Once bootstrap has its checkpoint, the applier sendsREGISTERand receives the "final-join" WAL stream viabox_process_register. If this stream contains an unconfirmed sync transaction, it is now harmless: it just sits in the replica's limbo as a pending entry — the same supported steady state as any running replica receiving an in-flight sync transaction.SUBSCRIBEis unchanged from the old flow. The only difference is what it may find on arrival: if theREGISTERstage delivered synchronous transactions whoseCONFIRMentries were not yet written on the master (they lay afterstop_vclock), those transactions are now pending in the replica's limbo. TheSUBSCRIBEresolves them: the replica ACKs the master through it, the master's pending sync transactions gather quorum and get confirmed, and theCONFIRMentries flow back to the replica through the relay, draining its limbo — just like for any running replica that received an in-flight sync transaction during normal replication.This part requires only replica's side changes, because our current master can serve any types of replication IPROTO requests in any way. On the replica's side we need to:
applier_falways callapplier_fetch_snapshotregardless of anon/named status of replica.box_bootstrap_from_master(fromFINAL_JOIN/FETCHED_SNAPSHOTtoAPPLIER_FETCHED_SNAPSHOT).applier_joinentirely — named replicas no longer bootstrap viaJOIN.APPLIER_FINAL_JOINandAPPLIER_JOINED.Pros & Cons:
Rejected alternatives
2. Using build_in_memory_checkpoint on the master side
The core idea of this solution:
stop_vclockmust not be a raw copy ofinstance_vclock, but a journal position V for which the master can prove that everything up to V is committed. That is exactly whatbox_checkpoint_build_in_memoryproduces: a pair of "journal vclock V" + "limbo state (confirmed_lsn, queue owner, term) captured at the exact moment the last pending sync txn of the build time got committed". The master uses the checkpoint's vclock asstop_vclock, streams(start_vclock, stop_vclock]as today, and then sends the checkpoint's limbo state as a "fake" PROMOTE meta row - the same mechanismsend_join_metaalready uses in the initial join. The replica applies it viatxn_limbo_processand instantly commits all sync txns <=stop_vclock, so its initial checkpoint never waits for aCONFIRMthat can only arrive viaSUBSCRIBE.The key insight (why this beats both the drain and the on-commit-trigger capture): we do not try to find a point in the xlog where every
CONFIRMcovering the data is also below it - Serpentian showed this is impossible without draining (T1 → T2 → CONFIRM(T1) → T3 → CONFIRM(T2) → … chases forever). Instead, we ship the limbo state next to the data, exactly like a snapshot does: snapshot recovery never replays CONFIRMs of the rows stored in it, it restoresconfirmed_lsnfrom the limbo state saved in the snapshot.Pros & Cons:
3. Drain limbo during master freeze
The core idea of this solution: during bootstrap process the master should capture stop_vclock only when the limbo is "completed" (every sync txn with data in WAL also has its terminal record in WAL - CONFIRM or ROLLBACK).
High-level algorithm:
(start_vclock; stop_vclock]to newly added replica.Implementation details:
Introduce
ER_SYNC_LIMBO_DRAIN_TIMEOUT- an error which sends from master to replica if some pending sync txns don't have time to be resolved during bootstrap process in replicaset.Introduce two new fields in
struct limbo-bool is_frozen(when true, new sync-txn submissions must wait) andstruct fiber_cond frozen_cond(signaled on unfreeze).Define a new limbo function which captures vclock only when all sync txns entries are confirmed or rollbacked. It is important to mention that if some sync txn don't have time to be resolved we raise a new error to new replica.
txn_limbo.cc
In
txn_limbo_submitadd a waiting loop in order to not allow sync txns appear on master:txn_limbo.cc
In
box_process_registerandbox_process_joininsert atxn_limbo_capture_vclock_after_all_confirmswaiting functionbox.cc
Pros & Cons:
All reactions