Replies: 7 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 |
|
v4 is a big step, thanks. Part 1 is now the minimal fix of #11039 and part 2 is the #6281 work on top of it, which is the split we asked for. Below I go through Nikita's points and the updated RFC. Part 1, the #11039 fixAgreed as written, with one correction which Nikita also spotted. Point 1.2 says I assume the part 1 is just for the sake of completeness of the doc, and we will be implementing right away the part 2. Part 2, the #6281 fixAnon acks. The text says the anonymous subscriber "is able to send ACKs, which lets the master drain its pending sync queue". The problem is that anonymous replica acks are ignored by everything, Raft and limbo both. This fact and things based on it won't survive. Nikita also spotted that. In fact, during this anon part of the subscribe the replica will be probably catching up on some old txns which are already acked and confirmed a while ago. Lets correct that in the text. ACKs in the register stage (Nikita's idea). With part 2 the register stream is a handful of rows, since otherwise we would be stalling the quorum for too long while we catch up. Thus there won't be much to ack in this window. And this probably makes the acks during register-catch-up not very useful, while also requiring a master-side change in the code. I would suggest to not do that. Part 3, the catch-up variants
What is missing is the fourth variant, as Nikita also mentioned. The one from my previous comment. A bounded wait, after which the replica registers anyway. Its worst case is today's behaviour minus the deadlock, Part 4, the restart in the windowThe lost-registration case Nikita describes. The master committed the Not a hang, but a dead end, and the same dead end exists today for The replica can see this case coming before it subscribes: the ballot it receives at connect time lists every UUID registered on the master ( The ending of point 4 still says that on a catch-up timeout Registration after
|
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