From: Serge Petrenko via Tarantool-patches <tarantool-patches@dev.tarantool.org> To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>, gorcunov@gmail.com Cc: tarantool-patches@dev.tarantool.org Subject: Re: [Tarantool-patches] [PATCH v4 07/12] raft: filter rows based on known peer terms Date: Sun, 18 Apr 2021 11:49:55 +0300 [thread overview] Message-ID: <76899832-2a8a-c47b-9e40-32310d9e9e38@tarantool.org> (raw) In-Reply-To: <d50a2bf7-eac0-7bcd-d583-3c4f2aa54fe9@tarantool.org> 17.04.2021 01:21, Vladislav Shpilevoy пишет: > I appreciate the work you did here! > >> diff --git a/src/lib/raft/raft.h b/src/lib/raft/raft.h >> index e447f6634..a5f7e08d9 100644 >> --- a/src/lib/raft/raft.h >> +++ b/src/lib/raft/raft.h >> @@ -207,6 +207,19 @@ struct raft { >> * subsystems, such as Raft. >> */ >> const struct vclock *vclock; >> + /** >> + * The biggest term seen by this instance and persisted in WAL as part >> + * of a PROMOTE request. May be smaller than @a term, while there are >> + * ongoing elections, or the leader is already known, but this instance >> + * hasn't read its PROMOTE request yet. >> + * During other times must be equal to @a term. >> + */ >> + uint64_t greatest_term; >> + /** >> + * Latest terms received with PROMOTE entries from remote instances. >> + * Raft uses them to determine data from which sources may be applied. >> + */ >> + struct vclock term_map; > I am sorry for not noticing this first time, but I realized the > names are still not perfect - they give an impression the terms are > collected on any term bump. But they are only for promotions. So > they should probably be greatest_promote_term, and promote_term_map. > > Another issue I see after that rename - they depend on something not > related to raft. Raft does write PROMOTEs. You can see that these > 2 members are not used in raft code at all. Only in the limbo and > box. On the other hand, they don't remove terms dependency from the > limbo, because they are part of PROMOTE, which is part of the limbo. > > That means, we introduced an explicit dependency on raft in the > limbo just to store some numbers in struct raft. > > Maybe move these 2 members to the limbo? They have nothing to do with > the leader election as we can see, and our lib/raft is only about that. > > They are for filtering once the leader is elected already, which is > synchronous replication's job, and which in turn is the limbo. > > This also makes us closer to the idea I mentioned about lsn map > and promote term map merged into something new inside of the limbo. > > I tried to deal with that idea myself, and it resulted into a commit > I pushed on top of your branch, and pasted below. > > I made so the limbo does not depend on raft anymore (on its API). It > only uses term numbers. Box is the link between raft and limbo - it > passes the raft terms to the new promote entries in box.ctl.promote(). > > If you agree, please, squash. Otherwise lets discuss. I didn't delete > the unit test about this new map yet, only commented it out. You would > need to drop it if squash. I see what you mean. It was hard to see that these methods belong to txn_limbo, at first. But I agree with you now. Your version looks good. Thanks for the help! Squashed. > ==================== > diff --git a/src/box/applier.cc b/src/box/applier.cc > index 61d53fdec..b0e8fbba7 100644 > --- a/src/box/applier.cc > +++ b/src/box/applier.cc > @@ -967,6 +967,59 @@ apply_final_join_tx(struct stailq *rows) > return rc; > } > > +/* > + * When elections are enabled we must filter out synchronous rows coming > + * from an instance that fell behind the current leader. This includes > + * both synchronous tx rows and rows for txs following unconfirmed > + * synchronous transactions. > + * The rows are replaced with NOPs to preserve the vclock consistency. > + */ > +static void > +applier_synchro_filter_tx(struct applier *applier, struct stailq *rows) > +{ > + /* > + * XXX: in case raft is disabled, synchronous replication still works > + * but without any filtering. That might lead to issues with > + * unpredictable confirms after rollbacks which are supposed to be > + * fixed by the filtering. > + */ > + if (!raft_is_enabled(box_raft())) > + return; > + if (!txn_limbo_is_replica_outdated(&txn_limbo, applier->instance_id)) > + return; > + > + struct xrow_header *row; > + row = &stailq_last_entry(rows, struct applier_tx_row, next)->row; > + if (row->wait_sync) > + goto nopify; > + > + row = &stailq_first_entry(rows, struct applier_tx_row, next)->row; > + /* > + * Not waiting for sync and not a synchro request - this make it already > + * NOP or an asynchronous transaction not depending on any synchronous > + * ones - let it go as is. > + */ > + if (!iproto_type_is_synchro_request(row->type)) > + return; > + /* > + * Do not NOPify promotion, otherwise won't even know who is the limbo > + * owner now. > + */ > + if (iproto_type_is_promote_request(row->type)) > + return; > +nopify:; > + struct applier_tx_row *item; > + stailq_foreach_entry(item, rows, next) { > + row = &item->row; > + row->type = IPROTO_NOP; > + /* > + * Row body is saved to fiber's region and will be freed > + * on next fiber_gc() call. > + */ > + row->bodycnt = 0; > + } > +} > + > /** > * Apply all rows in the rows queue as a single transaction. > * > @@ -1026,29 +1079,7 @@ applier_apply_tx(struct applier *applier, struct stailq *rows) > } > } > } > - > - /* > - * When elections are enabled we must filter out synchronous rows coming > - * from an instance that fell behind the current leader. This includes > - * both synchronous tx rows and rows for txs following unconfirmed > - * synchronous transactions. > - * The rows are replaced with NOPs to preserve the vclock consistency. > - */ > - struct applier_tx_row *item; > - if (raft_is_node_outdated(box_raft(), applier->instance_id) && > - (last_row->wait_sync || > - (iproto_type_is_synchro_request(first_row->type) && > - !iproto_type_is_promote_request(first_row->type)))) { > - stailq_foreach_entry(item, rows, next) { > - struct xrow_header *row = &item->row; > - row->type = IPROTO_NOP; > - /* > - * Row body is saved to fiber's region and will be freed > - * on next fiber_gc() call. > - */ > - row->bodycnt = 0; > - } > - } > + applier_synchro_filter_tx(applier, rows); > if (unlikely(iproto_type_is_synchro_request(first_row->type))) { > /* > * Synchro messages are not transactions, in terms > diff --git a/src/box/box.cc b/src/box/box.cc > index 70cb2bd53..cc68f0168 100644 > --- a/src/box/box.cc > +++ b/src/box/box.cc > @@ -1516,10 +1516,12 @@ box_promote(void) > > /* > * Do nothing when box isn't configured and when PROMOTE was already > - * written for this term. > + * written for this term (synchronous replication and leader election > + * are in sync, and both chose this node as a leader). > */ > - if (!is_box_configured || > - raft_node_term(box_raft(), instance_id) == box_raft()->term) > + if (!is_box_configured) > + return 0; > + if (txn_limbo_replica_term(&txn_limbo, instance_id) == box_raft()->term) > return 0; > > bool run_elections = false; > diff --git a/src/box/txn_limbo.c b/src/box/txn_limbo.c > index 0726b5a04..bafb47aaa 100644 > --- a/src/box/txn_limbo.c > +++ b/src/box/txn_limbo.c > @@ -34,7 +34,6 @@ > #include "iproto_constants.h" > #include "journal.h" > #include "box.h" > -#include "raft.h" > > struct txn_limbo txn_limbo; > > @@ -46,6 +45,8 @@ txn_limbo_create(struct txn_limbo *limbo) > limbo->owner_id = REPLICA_ID_NIL; > fiber_cond_create(&limbo->wait_cond); > vclock_create(&limbo->vclock); > + vclock_create(&limbo->promote_term_map); > + limbo->promote_greatest_term = 0; > limbo->confirmed_lsn = 0; > limbo->rollback_count = 0; > limbo->is_in_rollback = false; > @@ -644,8 +645,13 @@ complete: > void > txn_limbo_process(struct txn_limbo *limbo, const struct synchro_request *req) > { > - /* It's ok to process an empty term. It'll just get ignored. */ > - raft_process_term(box_raft(), req->origin_id, req->term); > + uint64_t term = req->term; > + uint32_t origin = req->origin_id; > + if (txn_limbo_replica_term(limbo, origin) < term) { > + vclock_follow(&limbo->promote_term_map, origin, term); > + if (term > limbo->promote_greatest_term) > + limbo->promote_greatest_term = term; > + } > if (req->replica_id != limbo->owner_id) { > /* > * Ignore CONFIRM/ROLLBACK messages for a foreign master. > diff --git a/src/box/txn_limbo.h b/src/box/txn_limbo.h > index f35771dc9..e409ac657 100644 > --- a/src/box/txn_limbo.h > +++ b/src/box/txn_limbo.h > @@ -129,6 +129,24 @@ struct txn_limbo { > * transactions, created on the limbo's owner node. > */ > struct vclock vclock; > + /** > + * Latest terms received with PROMOTE entries from remote instances. > + * Limbo uses them to filter out the transactions coming not from the > + * limbo owner, but so outdated that they are rolled back everywhere > + * except outdated nodes. > + */ > + struct vclock promote_term_map; > + /** > + * The biggest PROMOTE term seen by the instance and persisted in WAL. > + * It is related to raft term, but not the same. Synchronous replication > + * represented by the limbo is interested only in the won elections > + * ended with PROMOTE request. > + * It means the limbo's term might be smaller than the raft term, while > + * there are ongoing elections, or the leader is already known and this > + * instance hasn't read its PROMOTE request yet. During other times the > + * limbo and raft are in sync and the terms are the same. > + */ > + uint64_t promote_greatest_term; > /** > * Maximal LSN gathered quorum and either already confirmed in WAL, or > * whose confirmation is in progress right now. Any attempt to confirm > @@ -193,6 +211,28 @@ txn_limbo_last_entry(struct txn_limbo *limbo) > in_queue); > } > > +/** > + * Return the latest term as seen in PROMOTE requests from instance with id > + * @a replica_id. > + */ > +static inline uint64_t > +txn_limbo_replica_term(const struct txn_limbo *limbo, uint32_t replica_id) > +{ > + return vclock_get(&limbo->promote_term_map, replica_id); > +} > + > +/** > + * Check whether replica with id @a source_id is too old to apply synchronous > + * data from it. The check is only valid when elections are enabled. > + */ > +static inline bool > +txn_limbo_is_replica_outdated(const struct txn_limbo *limbo, > + uint32_t replica_id) > +{ > + return txn_limbo_replica_term(limbo, replica_id) < > + limbo->promote_greatest_term; > +} > + > /** > * Return the last synchronous transaction in the limbo or NULL when it is > * empty. > diff --git a/src/lib/raft/raft.c b/src/lib/raft/raft.c > index b21693642..874e9157e 100644 > --- a/src/lib/raft/raft.c > +++ b/src/lib/raft/raft.c > @@ -1012,7 +1012,6 @@ raft_create(struct raft *raft, const struct raft_vtab *vtab) > .death_timeout = 5, > .vtab = vtab, > }; > - vclock_create(&raft->term_map); > raft_ev_timer_init(&raft->timer, raft_sm_schedule_new_election_cb, > 0, 0); > raft->timer.data = raft; > diff --git a/src/lib/raft/raft.h b/src/lib/raft/raft.h > index 69dec63c6..f7bc205d2 100644 > --- a/src/lib/raft/raft.h > +++ b/src/lib/raft/raft.h > @@ -207,19 +207,6 @@ struct raft { > * subsystems, such as Raft. > */ > const struct vclock *vclock; > - /** > - * The biggest term seen by this instance and persisted in WAL as part > - * of a PROMOTE request. May be smaller than @a term, while there are > - * ongoing elections, or the leader is already known, but this instance > - * hasn't read its PROMOTE request yet. > - * During other times must be equal to @a term. > - */ > - uint64_t greatest_term; > - /** > - * Latest terms received with PROMOTE entries from remote instances. > - * Raft uses them to determine data from which sources may be applied. > - */ > - struct vclock term_map; > /** State machine timed event trigger. */ > struct ev_timer timer; > /** Configured election timeout in seconds. */ > @@ -256,39 +243,6 @@ raft_is_source_allowed(const struct raft *raft, uint32_t source_id) > return !raft->is_enabled || raft->leader == source_id; > } > > -/** > - * Return the latest term as seen in PROMOTE requests from instance with id > - * @a source_id. > - */ > -static inline uint64_t > -raft_node_term(const struct raft *raft, uint32_t source_id) > -{ > - assert(source_id < VCLOCK_MAX); > - return vclock_get(&raft->term_map, source_id); > -} > - > -/** > - * Check whether replica with id @a source_id is too old to apply synchronous > - * data from it. The check is only valid when elections are enabled. > - */ > -static inline bool > -raft_is_node_outdated(const struct raft *raft, uint32_t source_id) > -{ > - uint64_t source_term = raft_node_term(raft, source_id); > - return raft->is_enabled && source_term < raft->greatest_term; > -} > - > -/** Remember the last term seen for replica with id @a source_id. */ > -static inline void > -raft_process_term(struct raft *raft, uint32_t source_id, uint64_t term) > -{ > - if (raft_node_term(raft, source_id) >= term) > - return; > - vclock_follow(&raft->term_map, source_id, term); > - if (term > raft->greatest_term) > - raft->greatest_term = term; > -} > - > /** Check if Raft is enabled. */ > static inline bool > raft_is_enabled(const struct raft *raft) > diff --git a/test/unit/raft.c b/test/unit/raft.c > index 575886932..4214dbc4c 100644 > --- a/test/unit/raft.c > +++ b/test/unit/raft.c > @@ -1267,38 +1267,38 @@ raft_test_too_long_wal_write(void) > raft_finish_test(); > } > > -static void > -raft_test_term_filter(void) > -{ > - raft_start_test(9); > - struct raft_node node; > - raft_node_create(&node); > - > - is(raft_node_term(&node.raft, 1), 0, "empty node term"); > - ok(!raft_is_node_outdated(&node.raft, 1), "not outdated initially"); > - > - raft_process_term(&node.raft, 1, 1); > - is(raft_node_term(&node.raft, 1), 1, "node term updated"); > - ok(raft_is_node_outdated(&node.raft, 2), "other nodes are outdated"); > - > - raft_process_term(&node.raft, 2, 100); > - ok(raft_is_node_outdated(&node.raft, 1), "node outdated when others " > - "have greater term"); > - ok(!raft_is_node_outdated(&node.raft, 2), "node with greatest term " > - "isn't outdated"); > - > - raft_process_term(&node.raft, 3, 100); > - ok(!raft_is_node_outdated(&node.raft, 2), "node not outdated when " > - "others have the same term"); > - > - raft_process_term(&node.raft, 3, 99); > - is(raft_node_term(&node.raft, 3), 100, "node term isn't decreased"); > - ok(!raft_is_node_outdated(&node.raft, 3), "node doesn't become " > - "outdated"); > - > - raft_node_destroy(&node); > - raft_finish_test(); > -} > +// static void > +// raft_test_term_filter(void) > +// { > +// raft_start_test(9); > +// struct raft_node node; > +// raft_node_create(&node); > + > +// is(raft_node_term(&node.raft, 1), 0, "empty node term"); > +// ok(!raft_is_node_outdated(&node.raft, 1), "not outdated initially"); > + > +// raft_process_term(&node.raft, 1, 1); > +// is(raft_node_term(&node.raft, 1), 1, "node term updated"); > +// ok(raft_is_node_outdated(&node.raft, 2), "other nodes are outdated"); > + > +// raft_process_term(&node.raft, 2, 100); > +// ok(raft_is_node_outdated(&node.raft, 1), "node outdated when others " > +// "have greater term"); > +// ok(!raft_is_node_outdated(&node.raft, 2), "node with greatest term " > +// "isn't outdated"); > + > +// raft_process_term(&node.raft, 3, 100); > +// ok(!raft_is_node_outdated(&node.raft, 2), "node not outdated when " > +// "others have the same term"); > + > +// raft_process_term(&node.raft, 3, 99); > +// is(raft_node_term(&node.raft, 3), 100, "node term isn't decreased"); > +// ok(!raft_is_node_outdated(&node.raft, 3), "node doesn't become " > +// "outdated"); > + > +// raft_node_destroy(&node); > +// raft_finish_test(); > +// } > > static void > raft_test_start_stop_candidate(void) > @@ -1332,7 +1332,7 @@ raft_test_start_stop_candidate(void) > static int > main_f(va_list ap) > { > - raft_start_test(15); > + raft_start_test(14); > > (void) ap; > fakeev_init(); > @@ -1350,7 +1350,7 @@ main_f(va_list ap) > raft_test_death_timeout(); > raft_test_enable_disable(); > raft_test_too_long_wal_write(); > - raft_test_term_filter(); > + //raft_test_term_filter(); > raft_test_start_stop_candidate(); > > fakeev_free(); > diff --git a/test/unit/raft.result b/test/unit/raft.result > index bb799936b..f9a8f249b 100644 > --- a/test/unit/raft.result > +++ b/test/unit/raft.result > @@ -1,5 +1,5 @@ > *** main_f *** > -1..15 > +1..14 > *** raft_test_leader_election *** > 1..24 > ok 1 - 1 pending message at start > @@ -220,25 +220,12 @@ ok 12 - subtests > ok 8 - became candidate > ok 13 - subtests > *** raft_test_too_long_wal_write: done *** > - *** raft_test_term_filter *** > - 1..9 > - ok 1 - empty node term > - ok 2 - not outdated initially > - ok 3 - node term updated > - ok 4 - other nodes are outdated > - ok 5 - node outdated when others have greater term > - ok 6 - node with greatest term isn't outdated > - ok 7 - node not outdated when others have the same term > - ok 8 - node term isn't decreased > - ok 9 - node doesn't become outdated > -ok 14 - subtests > - *** raft_test_term_filter: done *** > *** raft_test_start_stop_candidate *** > 1..4 > ok 1 - became leader after start_candidate > ok 2 - remain leader after stop_candidate > ok 3 - vote request from 2 > ok 4 - demote once new election starts > -ok 15 - subtests > +ok 14 - subtests > *** raft_test_start_stop_candidate: done *** > *** main_f: done *** > -- Serge Petrenko
next prev parent reply other threads:[~2021-04-18 8:49 UTC|newest] Thread overview: 57+ messages / expand[flat|nested] mbox.gz Atom feed top 2021-04-16 16:25 [Tarantool-patches] [PATCH v4 00/12] raft: introduce manual elections and fix a bug with re-applying rolled back transactions Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 01/12] wal: make wal_assign_lsn accept journal entry Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 02/12] xrow: enrich row's meta information with sync replication flags Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 03/12] xrow: introduce a PROMOTE entry Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 04/12] box: actualise iproto_key_type array Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 05/12] box: make clear_synchro_queue() write a PROMOTE entry instead of CONFIRM + ROLLBACK Serge Petrenko via Tarantool-patches 2021-04-16 22:12 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-18 8:24 ` Serge Petrenko via Tarantool-patches 2021-04-20 22:30 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-21 5:58 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 06/12] box: write PROMOTE even for empty limbo Serge Petrenko via Tarantool-patches 2021-04-19 13:39 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 07/12] raft: filter rows based on known peer terms Serge Petrenko via Tarantool-patches 2021-04-16 22:21 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-18 8:49 ` Serge Petrenko via Tarantool-patches [this message] 2021-04-18 15:44 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-19 9:31 ` Serge Petrenko via Tarantool-patches 2021-04-18 16:27 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-19 9:30 ` Serge Petrenko via Tarantool-patches 2021-04-20 20:29 ` Serge Petrenko via Tarantool-patches 2021-04-20 20:31 ` Serge Petrenko via Tarantool-patches 2021-04-20 20:55 ` Serge Petrenko via Tarantool-patches 2021-04-20 22:30 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-21 5:58 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 08/12] election: introduce a new election mode: "manual" Serge Petrenko via Tarantool-patches 2021-04-19 22:34 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-20 9:25 ` Serge Petrenko via Tarantool-patches 2021-04-20 17:37 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 09/12] raft: introduce raft_start/stop_candidate Serge Petrenko via Tarantool-patches 2021-04-16 22:23 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-18 8:59 ` Serge Petrenko via Tarantool-patches 2021-04-19 22:35 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-20 9:28 ` Serge Petrenko via Tarantool-patches 2021-04-19 12:52 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 10/12] election: support manual elections in clear_synchro_queue() Serge Petrenko via Tarantool-patches 2021-04-16 22:24 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-18 9:26 ` Serge Petrenko via Tarantool-patches 2021-04-18 16:07 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-19 9:32 ` Serge Petrenko via Tarantool-patches 2021-04-19 12:47 ` Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 11/12] box: remove parameter from clear_synchro_queue Serge Petrenko via Tarantool-patches 2021-04-16 16:25 ` [Tarantool-patches] [PATCH v4 12/12] box.ctl: rename clear_synchro_queue to promote Serge Petrenko via Tarantool-patches 2021-04-19 22:35 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-20 10:22 ` Serge Petrenko via Tarantool-patches 2021-04-18 12:00 ` [Tarantool-patches] [PATCH v4 13/12] replication: send accumulated Raft messages after relay start Serge Petrenko via Tarantool-patches 2021-04-18 16:03 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-19 12:11 ` Serge Petrenko via Tarantool-patches 2021-04-19 22:36 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-20 10:38 ` Serge Petrenko via Tarantool-patches 2021-04-20 22:31 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-21 5:59 ` Serge Petrenko via Tarantool-patches 2021-04-19 22:37 ` [Tarantool-patches] [PATCH v4 00/12] raft: introduce manual elections and fix a bug with re-applying rolled back transactions Vladislav Shpilevoy via Tarantool-patches 2021-04-20 17:38 ` [Tarantool-patches] [PATCH v4 14/12] txn: make NOPs fully asynchronous Serge Petrenko via Tarantool-patches 2021-04-20 22:31 ` Vladislav Shpilevoy via Tarantool-patches 2021-04-21 5:59 ` Serge Petrenko via Tarantool-patches 2021-04-20 22:30 ` [Tarantool-patches] [PATCH v4 00/12] raft: introduce manual elections and fix a bug with re-applying rolled back transactions Vladislav Shpilevoy via Tarantool-patches 2021-04-21 6:01 ` Serge Petrenko via Tarantool-patches
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=76899832-2a8a-c47b-9e40-32310d9e9e38@tarantool.org \ --to=tarantool-patches@dev.tarantool.org \ --cc=gorcunov@gmail.com \ --cc=sergepetrenko@tarantool.org \ --cc=v.shpilevoy@tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH v4 07/12] raft: filter rows based on known peer terms' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox