Skip to content
  • Nuno Carvalho's avatar
    e02ac083
    BUG#23075016: GR ROLLS-BACK SOME TRANSACTIONS WHEN IT HAS MANY CLIENTS · e02ac083
    Nuno Carvalho authored
    Group Replication uses transactions write-set and database version
    to check if parallel transactions from different members do
    conflict, on which case the ordered first is the one allowed to
    commit and the other(s) are rollback.
    The write-set is the list of the primary key equivalents that can
    unequivocally identify a row on a table.
    The database version is the GTID_EXECUTED on top of which the
    transaction was executed.
    These information combined with the Database State Machine (DBSM)
    approach allow us to check for conflicts between transactions.
    
    When two transactions write on the same rows on the same server,
    there was a race between InnoDB release locks and GTID_EXECUTED
    read:
    
      T1: W(A)
                                          T2: W(A) -- blocks
      T1: PREPARE (innodb)
      T1: before_commit hook
      T1: Read GTID_EXECUTED
      T1: FLUSH (binlog)
      T1: SYNC (binlog)
      T1: COMMIT (innodb)
      T1: release innodb locks (unblocks T2)
                                         T2: PREPARE (innodb)
                                         T2: before_commit hook
                                         T2: read GTID_EXECUTED
      T1: update GTID_EXECUTED
    
    The "T2: read GTID_EXECUTED" operation would have missed the fact
    that T1 had already committed, thence would treat it as conflicting.
    
    There was a race condition on GTID_EXECUTED read that could make
    transactions to read not updated GTID_EXECUTED, despite the binary
    log was already updated, that is, there was a small windows between
    write to binary log and update GTID_EXECUTED on which parallel
    threads could read a GTID_EXECUTED value which did not include the
    last logged transaction to binary log.
    When operating on single member, this window could cause a parallel
    transactions to be marked as conflicting despite it was not, since
    it was serialized be the server. The problem was the outdated
    GTID_EXECUTED value that this transaction has read.
    
    To eliminate this opportunity window, now GTID_EXECUTED is read
    under the protection of MYSQL_BIN_LOG::LOCK_commit to ensure that we
    when GTID_EXECUTED is read all previous transactions are committed
    and GTID_EXECUTED updated:
    
      T1: W(A)
                                          T2: W(A) -- blocks
      T1: PREPARE (innodb)
      T1: before_commit hook
      T1: Read GTID_EXECUTED
      T1: FLUSH (binlog)
      T1: SYNC (binlog)
      T1: COMMIT (innodb)
      T1: update GTID_EXECUTED
      T1: release innodb locks (unblocks T2)
                                         T2: PREPARE (innodb)
                                         T2: before_commit hook
                                         T2: read GTID_EXECUTED
    e02ac083
    BUG#23075016: GR ROLLS-BACK SOME TRANSACTIONS WHEN IT HAS MANY CLIENTS
    Nuno Carvalho authored
    Group Replication uses transactions write-set and database version
    to check if parallel transactions from different members do
    conflict, on which case the ordered first is the one allowed to
    commit and the other(s) are rollback.
    The write-set is the list of the primary key equivalents that can
    unequivocally identify a row on a table.
    The database version is the GTID_EXECUTED on top of which the
    transaction was executed.
    These information combined with the Database State Machine (DBSM)
    approach allow us to check for conflicts between transactions.
    
    When two transactions write on the same rows on the same server,
    there was a race between InnoDB release locks and GTID_EXECUTED
    read:
    
      T1: W(A)
                                          T2: W(A) -- blocks
      T1: PREPARE (innodb)
      T1: before_commit hook
      T1: Read GTID_EXECUTED
      T1: FLUSH (binlog)
      T1: SYNC (binlog)
      T1: COMMIT (innodb)
      T1: release innodb locks (unblocks T2)
                                         T2: PREPARE (innodb)
                                         T2: before_commit hook
                                         T2: read GTID_EXECUTED
      T1: update GTID_EXECUTED
    
    The "T2: read GTID_EXECUTED" operation would have missed the fact
    that T1 had already committed, thence would treat it as conflicting.
    
    There was a race condition on GTID_EXECUTED read that could make
    transactions to read not updated GTID_EXECUTED, despite the binary
    log was already updated, that is, there was a small windows between
    write to binary log and update GTID_EXECUTED on which parallel
    threads could read a GTID_EXECUTED value which did not include the
    last logged transaction to binary log.
    When operating on single member, this window could cause a parallel
    transactions to be marked as conflicting despite it was not, since
    it was serialized be the server. The problem was the outdated
    GTID_EXECUTED value that this transaction has read.
    
    To eliminate this opportunity window, now GTID_EXECUTED is read
    under the protection of MYSQL_BIN_LOG::LOCK_commit to ensure that we
    when GTID_EXECUTED is read all previous transactions are committed
    and GTID_EXECUTED updated:
    
      T1: W(A)
                                          T2: W(A) -- blocks
      T1: PREPARE (innodb)
      T1: before_commit hook
      T1: Read GTID_EXECUTED
      T1: FLUSH (binlog)
      T1: SYNC (binlog)
      T1: COMMIT (innodb)
      T1: update GTID_EXECUTED
      T1: release innodb locks (unblocks T2)
                                         T2: PREPARE (innodb)
                                         T2: before_commit hook
                                         T2: read GTID_EXECUTED
Loading