mysqlnd-ms-php-quickstart-php-gtid-3

  • Quickstart and
    Examples
  • Global transaction IDs

  • Global transaction IDs
  • Global transaction IDs

    Global transaction IDs

    Note: Version
    requirement

    A client-side global transaction ID injection has
    been introduced in mysqlnd_ms version 1.2.0-alpha. The feature is
    not required for synchronous clusters, such as MySQL Cluster. Use
    it with asynchronous clusters such as classical MySQL
    replication.

    As of MySQL 5.6.5-m8 release candidate the MySQL
    server features built-in global transaction identifiers. The MySQL
    built-in global transaction ID feature is supported by
    PECL/mysqlnd_ms 1.3.0-alpha or later. However, the final
    feature set found in MySQL 5.6 production releases to date is not
    sufficient to support the ideas discussed below in all cases.
    Please, see also the concepts section.

    PECL/mysqlnd_ms can either use its own
    global transaction ID emulation or the global transaction ID
    feature built-in to MySQL 5.6.5-m8 or later. From a developer
    perspective the client-side and server-side approach offer the same
    features with regards to service levels provided by
    PECL/mysqlnd_ms. Their differences are discussed in the concepts section.

    The quickstart first demonstrates the use of the
    client-side global transaction ID emulation built-in to
    PECL/mysqlnd_ms before its show how to use the server-side
    counterpart. The order ensures that the underlying idea is
    discussed first.

    Idea and client-side
    emulation

    In its most basic form a global transaction ID
    (GTID) is a counter in a table on the master. The counter is
    incremented whenever a transaction is committed on the master.
    Slaves replicate the table. The counter serves two purposes. In
    case of a master failure, it helps the database administrator to
    identify the most recent slave for promoting it to the new master.
    The most recent slave is the one with the highest counter value.
    Applications can use the global transaction ID to search for slaves
    which have replicated a certain write (identified by a global
    transaction ID) already.

    PECL/mysqlnd_ms can inject SQL for every
    committed transaction to increment a GTID counter. The so created
    GTID is accessible by the application to identify an applications
    write operation. This enables the plugin to deliver session
    consistency (read your writes) service level by not only querying
    masters but also slaves which have replicated the change already.
    Read load is taken away from the master.

    Client-side global transaction ID emulation has
    some limitations. Please, read the concepts section carefully to fully understand the
    principles and ideas behind it, before using in production
    environments. The background knowledge is not required to continue
    with the quickstart.

    First, create a counter table on your master server
    and insert a record into it. The plugin does not assist creating
    the table. Database administrators must make sure it exists.
    Depending on the error reporting mode, the plugin will silently
    ignore the lack of the table or bail out.

    Example #1 Create counter table on master

    CREATE TABLE `trx` (
      `trx_id` int(11) DEFAULT NULL,
      `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1
    INSERT INTO `trx`(`trx_id`) VALUES (1);
    

    In the plugins configuration file set the SQL to
    update the global transaction ID table using on_commit
    from the global_transaction_id_injection section. Make
    sure the table name used for the UPDATE statement is fully
    qualified. In the example, test.trx is used to refer to
    table trx in the schema (database) test. Use the
    table that was created in the previous step. It is important to set
    the fully qualified table name because the connection on which the
    injection is done may use a different default database. Make sure
    the user that opens the connection is allowed to execute the
    UPDATE.

    Enable reporting of errors that may occur when
    mysqlnd_ms does global transaction ID injection.

    Example #2 Plugin config: SQL for client-side GTID
    injection

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            },
            "global_transaction_id_injection":{
                "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
                "report_error":true
            }
        }
    }
    

    Example #3 Transparent global transaction ID
    injection

    <?php
    $mysqli 
    = new mysqli("myapp""username""password""database");
    if (!
    $mysqli) {
        
    /* Of course, your error handling is nicer... */
        
    die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
    }

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (!$mysqli->query("CREATE TABLE test(id INT)")) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    /* auto commit mode, read on slave, no increment */
    if (!($res $mysqli->query("SELECT id FROM test"))) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    var_dump($res->fetch_assoc());
    ?>

    The above example will output:

    array(1) {
      ["id"]=>
      string(1) "1"
    }
    

    The example runs three statements in auto commit
    mode on the master, causing three transactions on the master. For
    every such statement, the plugin will inject the configured
    UPDATE transparently before executing the users SQL
    statement. When the script ends the global transaction ID counter
    on the master has been incremented by three.

    The fourth SQL statement executed in the example, a
    SELECT, does not trigger an increment. Only transactions
    (writes) executed on a master shall increment the GTID counter.

    Note: SQL for global
    transaction ID: efficient solution wanted!

    The SQL used for the client-side global transaction
    ID emulation is inefficient. It is optimized for clearity not for
    performance. Do not use it for production environments. Please,
    help finding an efficient solution for inclusion in the manual. We
    appreciate your input.

    Example #4 Plugin config: SQL for fetching
    GTID

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            },
            "global_transaction_id_injection":{
                "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
                "fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
                "report_error":true
            }
        }
    }
    

    Example #5 Obtaining GTID after injection

    <?php
    $mysqli 
    = new mysqli("myapp""username""password""database");
    if (!
    $mysqli) {
        
    /* Of course, your error handling is nicer... */
        
    die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
    }

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    printf("GTID after transaction %s\n"mysqlnd_ms_get_last_gtid($mysqli));

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (!$mysqli->query("CREATE TABLE test(id INT)")) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    printf("GTID after transaction %s\n"mysqlnd_ms_get_last_gtid($mysqli));
    ?>

    The above example will output:

    GTID after transaction 7
    GTID after transaction 8
    

    Applications can ask PECL mysqlnd_ms for a global
    transaction ID which belongs to the last write operation performed
    by the application. The function mysqlnd_ms_get_last_gtid() returns the GTID
    obtained when executing the SQL statement from the
    fetch_last_gtid entry of the
    global_transaction_id_injection section from the plugins
    configuration file. The function may be called after the GTID has
    been incremented.

    Applications are adviced not to run the SQL
    statement themselves as this bares the risk of accidentally causing
    an implicit GTID increment. Also, if the function is used, it is
    easy to migrate an application from one SQL statement for fetching
    a transaction ID to another, for example, if any MySQL server ever
    features built-in global transaction ID support.

    The quickstart shows a SQL statement which will
    return a GTID equal or greater to that created for the previous
    statement. It is exactly the GTID created for the previous
    statement if no other clients have incremented the GTID in the time
    span between the statement execution and the SELECT to
    fetch the GTID. Otherwise, it is greater.

    Example #6 Plugin config: Checking for a certain
    GTID

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            },
            "global_transaction_id_injection":{
                "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
                "fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
                "check_for_gtid" : "SELECT trx_id FROM test.trx WHERE trx_id >= #GTID",
                "report_error":true
            }
        }
    }
    

    Example #7 Session consistency service level and GTID
    combined

    <?php
    $mysqli 
    = new mysqli("myapp""username""password""database");
    if (!
    $mysqli) {
        
    /* Of course, your error handling is nicer... */
        
    die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
    }

    /* auto commit mode, transaction on master, GTID must be incremented */
    if (   !$mysqli->query("DROP TABLE IF EXISTS test")
        || !
    $mysqli->query("CREATE TABLE test(id INT)")
        || !
    $mysqli->query("INSERT INTO test(id) VALUES (1)")
    ) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    /* GTID as an identifier for the last write */
    $gtid mysqlnd_ms_get_last_gtid($mysqli);

    /* Session consistency (read your writes): try to read from slaves not only master */
    if (false == mysqlnd_ms_set_qos($mysqliMYSQLND_MS_QOS_CONSISTENCY_SESSIONMYSQLND_MS_QOS_OPTION_GTID$gtid)) {
        die(
    sprintf("[006] [%d] %s\n"$mysqli->errno$mysqli->error));
    }

    /* Either run on master or a slave which has replicated the INSERT */
    if (!($res $mysqli->query("SELECT id FROM test"))) {
        die(
    sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
    }

    var_dump($res->fetch_assoc());
    ?>

    A GTID returned from mysqlnd_ms_get_last_gtid() can be used as an
    option for the session consistency service level. Session
    consistency delivers read your writes. Session consistency can be
    requested by calling mysqlnd_ms_set_qos(). In the example, the
    plugin will execute the SELECT statement either on the
    master or on a slave which has replicated the previous
    INSERT already.

    PECL mysqlnd_ms will transparently check every
    configured slave if it has replicated the INSERT by
    checking the slaves GTID table. The check is done running the SQL
    set with the check_for_gtid option from the
    global_transaction_id_injection section of the plugins
    configuration file. Please note, that this is a slow and expensive
    procedure. Applications should try to use it sparsely and only if
    read load on the master becomes to high otherwise.

    Use of the server-side global
    transaction ID feature

    Note: Insufficient server
    support in MySQL 5.6

    The plugin has been developed against a
    pre-production version of MySQL 5.6. It turns out that all released
    production versions of MySQL 5.6 do not provide clients with enough
    information to enforce session consistency based on GTIDs. Please,
    read the concepts
    section
    for details.

    Starting with MySQL 5.6.5-m8 the MySQL Replication
    system features server-side global transaction IDs. Transaction
    identifiers are automatically generated and maintained by the
    server. Users do not need to take care of maintaining them. There
    is no need to setup any tables in advance, or for setting
    on_commit. A client-side emulation is no longer
    needed.

    Clients can continue to use global transaction
    identifier to achieve session consistency when reading from MySQL
    Replication slaves in some cases but not all! The algorithm works
    as described above. Different SQL statements must be configured for
    fetch_last_gtid and check_for_gtid. The
    statements are given below. Please note, MySQL 5.6.5-m8 is a
    development version. Details of the server implementation may
    change in the future and require adoption of the SQL statements
    shown.

    Using the following configuration any of the above
    described functionality can be used together with the server-side
    global transaction ID feature. mysqlnd_ms_get_last_gtid() and mysqlnd_ms_set_qos() continue to work as
    described above. The only difference is that the server does not
    use a simple sequence number but a string containing of a server
    identifier and a sequence number. Thus, users cannot easily derive
    an order from GTIDs returned by mysqlnd_ms_get_last_gtid().

    Example #8 Plugin config: using MySQL 5.6.5-m8 built-in
    GTID feature

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            },
            "global_transaction_id_injection":{
                "fetch_last_gtid" : "SELECT @@GLOBAL.GTID_DONE AS trx_id FROM DUAL",
                "check_for_gtid" : "SELECT GTID_SUBSET('#GTID', @@GLOBAL.GTID_DONE) AS trx_id FROM DUAL",
                "report_error":true
            }
        }
    }