[Commits] Rev 4493: MDEV-6676: Speculative parallel replication in http://bazaar.launchpad.net/~maria-captains/maria/10.0

knielsen at knielsen-hq.org knielsen at knielsen-hq.org
Thu Nov 13 16:03:56 EET 2014


At http://bazaar.launchpad.net/~maria-captains/maria/10.0

------------------------------------------------------------
revno: 4493
revision-id: knielsen at knielsen-hq.org-20140910074629-7jptk0w4itt4je37
parent: knielsen at knielsen-hq.org-20141113100131-292bj775z1f7ebc2
committer: Kristian Nielsen <knielsen at knielsen-hq.org>
branch nick: work-10.0-mdev6676
timestamp: Wed 2014-09-10 09:46:29 +0200
message:
  MDEV-6676: Speculative parallel replication
  
  Intermediate commit. Patch is far from complete, but this small patch was
  nevertheless sufficient to be able to sysbench-0.4 OLTP with full
  parallelisation.
  
  With the fix for MDEV-5941 and friends, we get the ability to detect and
  resolve conflicts between transactions that are replicated in parallel. This
  means that for InnoDB DML, it becomes always safe to attempt to replicate
  transactions T1 and T2 in parallel, even if it is not known a-priori if this
  is possible without risk of conflicts.
  
  In case of a conflict between T1 and T2, it might happen that T2 manages to
  run first, while on the master T1 ran first. In this case T1 will end up
  waiting for T2 on an InnoDB row lock. This will cause a deadlock, and T2 will
  be killed and rolled back to let T1 continue. T2 will then later be re-tried.
  
  In this intermediate patch, we run _all_ transactions in parallel,
  optimistically. Later, we will add flags in the GTID event to detect various
  cases where we do not want to speculatively attempt parallelisation, such as
  DDL, non-transactional updates, user annotations, etc.
=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc	2014-10-09 08:30:11 +0000
+++ b/sql/log_event.cc	2014-09-10 07:46:29 +0000
@@ -187,6 +187,8 @@ is_parallel_retry_error(rpl_group_info *
 {
   if (!rgi->is_parallel_exec)
     return false;
+  if (rgi->speculative)
+    return true;
   if (rgi->killed_for_retry &&
       (err == ER_QUERY_INTERRUPTED || err == ER_CONNECTION_KILLED))
     return true;

=== modified file 'sql/rpl_parallel.cc'
--- a/sql/rpl_parallel.cc	2014-11-13 09:46:09 +0000
+++ b/sql/rpl_parallel.cc	2014-09-10 07:46:29 +0000
@@ -250,7 +250,13 @@ dbug_simulate_tmp_error(rpl_group_info *
   asynchroneously to allow the former to complete its commit.
 
   In this case, we convert the 'killed' error into a deadlock error, and retry
-  the later transaction.  */
+  the later transaction.
+
+  If we are doing optimistic parallel apply of transactions not known to be
+  safe, we convert any error to a deadlock error, but then at retry we will
+  wait for prior transactions to commit first, so that the retries can be
+  done non-speculative.
+*/
 static void
 convert_kill_to_deadlock_error(rpl_group_info *rgi)
 {
@@ -260,8 +266,9 @@ convert_kill_to_deadlock_error(rpl_group
   if (!thd->get_stmt_da()->is_error())
     return;
   err_code= thd->get_stmt_da()->sql_errno();
-  if ((err_code == ER_QUERY_INTERRUPTED || err_code == ER_CONNECTION_KILLED) &&
-      rgi->killed_for_retry)
+  if (rgi->speculative ||
+      ((err_code == ER_QUERY_INTERRUPTED || err_code == ER_CONNECTION_KILLED) &&
+       rgi->killed_for_retry))
   {
     thd->clear_error();
     my_error(ER_LOCK_DEADLOCK, MYF(0));
@@ -358,6 +365,21 @@ retry_event_group(rpl_group_info *rgi, r
   register_wait_for_prior_event_group_commit(rgi, entry);
   mysql_mutex_unlock(&entry->LOCK_parallel_entry);
 
+  if (rgi->speculative)
+  {
+    /*
+      We speculatively tried to run this in parallel with prior event groups,
+      but it did not work for some reason.
+
+      So let us wait for all prior transactions to complete before trying
+      again. This way, we avoid repeatedly retrying and failing a small
+      transaction that conflicts with a prior long-running one.
+    */
+    rgi->speculative= false;
+    if ((err= thd->wait_for_prior_commit()))
+      goto err;
+  }
+
   strmake_buf(log_name, ir->name);
   if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0)
   {
@@ -1960,6 +1982,7 @@ rpl_parallel::do_event(rpl_group_info *s
   if (typ == GTID_EVENT)
   {
     Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev);
+    bool do_parallel;
 
     if (!(rgi= cur_thread->get_rgi(rli, gtid_ev, e, event_size)))
     {
@@ -1986,14 +2009,46 @@ rpl_parallel::do_event(rpl_group_info *s
     rgi->wait_commit_sub_id= e->current_sub_id;
     rgi->wait_commit_group_info= e->current_group_info;
 
-    if (!((gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID) &&
-          e->last_commit_id == gtid_ev->commit_id))
+    if ((gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID) &&
+        e->last_commit_id == gtid_ev->commit_id)
+    {
+      /* A new batch of transactions that group-committed together on the master. */
+      do_parallel= true;
+    }
+    else
     {
       /*
-        A new batch of transactions that group-committed together on the master.
+        ToDo: Test for flags, avoid speculative parallelisation of DDL or
+        MyISAM, or stuff that waited on the master, or user-marked risky
+        transactions, or anything if speculation is disabled.
+
+        ToDo: Also, we need to start a new batch not just at DDL/MyISAM, but
+        also at the end of the commit_id group of that DDL/MyISAM, so we do
+        not try to eg. run a bunch of inserts in parallel with a long-running
+        ALTER TABLE that they depend on.
+      */
 
-        Remember the count that marks the end of the previous group committed
-        batch, and allocate a new gco.
+      /*
+        We do not know for sure that this is safe to run in parallel with what
+        came before. But we can try anyway; there should be a good chance that
+        it will work. If it does _not_ work, we will catch the problem as a
+        deadlock or other error, and we can rollback and retry the
+        transaction.
+      */
+      rgi->speculative= true;
+      do_parallel= true;
+    }
+    if (do_parallel && e->current_gco)
+      rgi->gco= e->current_gco;
+    else
+    {
+      /*
+        Do not run this event group in parallel with what came before; instead
+        wait for everything prior to at least have started its commit phase, to
+        avoid any risk of performing any conflicting action too early.
+
+        Remember the count that marks the end of the previous batch of event
+        groups that run in parallel, and allocate a new gco.
       */
       uint64 count= e->count_queued_event_groups;
       group_commit_orderer *gco;
@@ -2009,8 +2064,6 @@ rpl_parallel::do_event(rpl_group_info *s
       }
       e->current_gco= rgi->gco= gco;
     }
-    else
-      rgi->gco= e->current_gco;
     if (gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID)
       e->last_commit_id= gtid_ev->commit_id;
     else

=== modified file 'sql/rpl_rli.cc'
--- a/sql/rpl_rli.cc	2014-11-13 09:20:48 +0000
+++ b/sql/rpl_rli.cc	2014-09-10 07:46:29 +0000
@@ -1601,6 +1601,7 @@ rpl_group_info::reinit(Relay_log_info *r
   long_find_row_note_printed= false;
   did_mark_start_commit= false;
   gtid_ignore_duplicate_state= GTID_DUPLICATE_NULL;
+  speculative= false;
   commit_orderer.reinit();
 }
 

=== modified file 'sql/rpl_rli.h'
--- a/sql/rpl_rli.h	2014-11-13 09:20:48 +0000
+++ b/sql/rpl_rli.h	2014-09-10 07:46:29 +0000
@@ -653,6 +653,16 @@ struct rpl_group_info
   uint64 retry_start_offset;
   uint64 retry_event_count;
   bool killed_for_retry;
+  /*
+    If `speculative' is true, 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).
+
+    In this case, a conflict can cause other errors than deadlocks (like
+    duplicate key for example). So in case of _any_ error, we need to roll
+    back and retry the event group.
+  */
+  bool speculative;
 
   rpl_group_info(Relay_log_info *rli_);
   ~rpl_group_info();



More information about the commits mailing list