[Commits] 55acd87: More aggressive transaction retry in --slave-parallel-mode=aggressive

Kristian Nielsen knielsen at knielsen-hq.org
Thu Jul 2 15:33:09 EEST 2015


revision-id: 55acd870e7e86f12686d06888ae88200f0787243
parent(s): c000ffafc1eb27c992f59ae19b1c4797e648d1f9
committer: Kristian Nielsen
branch nick: mariadb
timestamp: 2015-07-02 14:25:12 +0200
message:

More aggressive transaction retry in --slave-parallel-mode=aggressive

When transaction T1 in parallel replication needs to deadlock kill another
transaction T3, record the wait_for_commit and the sub_id of T1 in the
rpl_group_info of T3.

Then in aggressive mode, when we do the retry of T3, wait for T1 to complete
before starting the retry. In other modes, we still wait for all prior
transactions to complete before the retry (eg. T2 if we have T1 T2 T3).

This is a tentative patch to study ways to improve in-order parallel
replication of loads with many lock conflicts between transactions. This
patch might reduce the impact of transaction retries due to such conflicts,
as they allow the conflicting transactions to be retried earlier. The
downside is that such transactions may conflict again with other prior
transactions. At worst, up to (N-1) conflicts could occur, if
--slave-paralle-mode=N.

---
 sql/rpl_parallel.cc | 51 ++++++++++++++++++++++++++++++++++++++++++++++-----
 sql/rpl_rli.h       | 10 ++++++++++
 sql/sql_class.cc    |  4 +++-
 3 files changed, 59 insertions(+), 6 deletions(-)

diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc
index 8612df5..3ea2b6b 100644
--- a/sql/rpl_parallel.cc
+++ b/sql/rpl_parallel.cc
@@ -418,18 +418,57 @@ retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt,
 
   for (;;)
   {
-    mysql_mutex_lock(&entry->LOCK_parallel_entry);
-    register_wait_for_prior_event_group_commit(rgi, entry);
-    mysql_mutex_unlock(&entry->LOCK_parallel_entry);
+    uint64_t deadlock_sub_id;
+    wait_for_commit *deadlock_wfc;
 
     /*
-      Let us wait for all prior transactions to complete before trying again.
+      Let us wait for prior transactions to complete before trying again.
       This way, we avoid repeatedly conflicting with and getting deadlock
       killed by the same earlier transaction.
+
+      In aggressive mode, we only wait for the transaction that we actually
+      conflicted with, if we have a reference to it. In other modes, we
+      wait for all prior transactions to commit, to avoid potentially getting
+      multiple deadlock kills against multiple prior transactions (since we
+      already conflicted with one transaction, there might be increased risk
+      of more conflicts).
     */
+    mysql_mutex_lock(&thd->LOCK_thd_data);
+    deadlock_sub_id= rgi->deadlock_kill_wait_commit_sub_id;
+    deadlock_wfc= rgi->deadlock_kill_wfc;
+    mysql_mutex_unlock(&thd->LOCK_thd_data);
+    mysql_mutex_lock(&entry->LOCK_parallel_entry);
+    if (deadlock_wfc && rli->mi->parallel_mode >= SLAVE_PARALLEL_AGGRESSIVE)
+    {
+      if (deadlock_sub_id > entry->last_committed_sub_id)
+        rgi->commit_orderer.register_wait_for_prior_commit(rgi->deadlock_kill_wfc);
+    }
+    else
+      register_wait_for_prior_event_group_commit(rgi, entry);
+    mysql_mutex_unlock(&entry->LOCK_parallel_entry);
+    rgi->deadlock_kill_wfc= NULL;
+
     if (!(err= thd->wait_for_prior_commit()))
     {
-      rgi->speculation = rpl_group_info::SPECULATE_WAIT;
+      if (deadlock_wfc && rli->mi->parallel_mode >= SLAVE_PARALLEL_AGGRESSIVE)
+      {
+        /*
+          If we waited for an earlier transaction before retrying, now we have
+          to re-register to wait for the immediately prior transaction, to
+          preserve commit order.
+        */
+        mysql_mutex_lock(&entry->LOCK_parallel_entry);
+        register_wait_for_prior_event_group_commit(rgi, entry);
+        mysql_mutex_unlock(&entry->LOCK_parallel_entry);
+      }
+      else
+      {
+        /*
+          We already waited for the immediately prior transaction to have
+          completed its commit. No need to wait again.
+        */
+        rgi->speculation = rpl_group_info::SPECULATE_WAIT;
+      }
       break;
     }
 
@@ -459,6 +498,7 @@ retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt,
     possibility of an old deadlock kill lingering on beyond this point.
   */
   thd->reset_killed();
+  thd->tx_isolation= (enum_tx_isolation)thd->variables.tx_isolation;
 
   strmake_buf(log_name, ir->name);
   if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0)
@@ -1416,6 +1456,7 @@ rpl_parallel_thread::get_rgi(Relay_log_info *rli, Gtid_log_event *gtid_ev,
   rgi->retry_start_offset= rli->future_event_relay_log_pos-event_size;
   rgi->retry_event_count= 0;
   rgi->killed_for_retry= false;
+  rgi->deadlock_kill_wfc= NULL;
 
   return rgi;
 }
diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h
index 2b45ed9..2ba654b 100644
--- a/sql/rpl_rli.h
+++ b/sql/rpl_rli.h
@@ -676,6 +676,16 @@ struct rpl_group_info
   uint64 retry_start_offset;
   uint64 retry_event_count;
   /*
+    If deadlock killed (killed_for_retry==true), this is the sub_id of the
+    killing GTID, and the wait_for_commit pointer to wait on if that sub_id is
+    still active. This is used in aggressive parallel replication mode to wait
+    only for the transaction we actually conflicted with before retrying, not
+    for all prior transactions.
+  */
+  uint64_t deadlock_kill_wait_commit_sub_id;
+  wait_for_commit *deadlock_kill_wfc;
+
+  /*
     If `speculation' is != SPECULATE_NO, then we are optimistically running
     this transaction in parallel, even though it might not be safe (there may
     be a conflict with a prior event group).
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 49bc65d..83f131d 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -4515,8 +4515,10 @@ thd_report_wait_for(MYSQL_THD thd, MYSQL_THD other_thd)
     cause replication to rollback (and later re-try) the other transaction,
     releasing the lock for this transaction so replication can proceed.
   */
-  other_rgi->killed_for_retry= true;
   mysql_mutex_lock(&other_thd->LOCK_thd_data);
+  other_rgi->deadlock_kill_wait_commit_sub_id= rgi->gtid_sub_id;
+  other_rgi->deadlock_kill_wfc= &rgi->commit_orderer;
+  other_rgi->killed_for_retry= true;
   other_thd->awake(KILL_CONNECTION);
   mysql_mutex_unlock(&other_thd->LOCK_thd_data);
 }


More information about the commits mailing list