Skip to content

Problem with replaying UPDATE or DELETE using key of BINARY(N) ending in 00's #654

@jrgm

Description

@jrgm

First, thanks for the development and open sourcing of this tool.

However, I was running a migration and left it postponed for a while, and before cutover I noticed that I had some differences between the master table and the gh-ost table involving columns with BINARY(N) type when values end in 00's. Perhaps I was using this incorrectly, but I distilled the problem down to these steps to reproduce. Apologies for the length.

Setup is on AWS RDS, mysql 5.6.40, gh-ost 1.0.46. RDS master is set to binlog_format of MIXED. RDS read replica is set to binlog_format of ROW.

RDS master parameter group settings difference from default:

Parameter           test-rdsparamgroup   default.mysql5.6
---------------------------------------------------------
general_log           1                  <engine-default>
innodb_file_format    Barracuda          <engine-default>
innodb_flush_method   O_DIRECT           <system>
log_output            FILE               TABLE
long_query_time       0.9                <engine-default>
slow_query_log        1                  <engine-default>

Steps to reproduce:

  1. Create a new test database with a single test table.
DROP DATABASE IF EXISTS `test_ghost`;
CREATE DATABASE `test_ghost` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
USE `test_ghost`;

DROP TABLE IF EXISTS `_test_table_gho`;
DROP TABLE IF EXISTS `_test_table_ghc`;
DROP TABLE IF EXISTS `test_table`;
CREATE TABLE `test_table` (
  `id` binary(16) NOT NULL,
  `info` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `data` binary(8) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `info` (`info`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `test_table` (id, info, data) VALUES
 (X'11ffffffffffffffffffffffffffff00', 'item 1', X'11ffffffffffffff'), -- id ends in 00
 (X'22ffffffffffffffffffffffffffff00', 'item 2', X'22ffffffffffffff'), -- id ends in 00
 (X'33ffffffffffffffffffffffffffffff', 'item 3', X'33ffffffffffffff'),
 (X'44ffffffffffffffffffffffffffffff', 'item 4', X'44ffffffffffffff'),
 (X'55ffffffffffffffffffffffffffffff', 'item 5', X'55ffffffffffffff'),
 (X'99ffffffffffffffffffffffffffffff', 'item 9', X'99ffffffffffff00'); -- data ends in 00

SELECT HEX(id) AS id, info, HEX(data) AS data FROM test_table;

mysql -NBe 'source ./bin/create-test-table.sql'

  1. Run a migration to add a new column to the test table and leave it postponed.
#!/usr/bin/env sh

./gh-ost \
  --execute \
  --max-load=Threads_running=25                                                                  \
  --critical-load=Threads_running=1000                                                           \
  --assume-rbr                                                                                   \
  --chunk-size=1000                                                                              \
  --max-lag-millis=15000                                                                         \
  --user='the_user'                                                                              \
  --password='the-password'                                                                      \
  --host='the-slave-hostname'                                                                    \
  --assume-master-host='the-master-hostname'                                                     \
  --database='test_ghost'                                                                        \
  --table='test_table'                                                                           \
  --verbose                                                                                      \
  --alter='ADD COLUMN testColumn BIGINT UNSIGNED DEFAULT NULL, ALGORITHM = INPLACE, LOCK = NONE' \
  --cut-over=default                                                                             \
  --exact-rowcount                                                                               \
  --concurrent-rowcount                                                                          \
  --default-retries=120                                                                          \
  --panic-flag-file=/tmp/ghost.panic.flag                                                        \
  --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag

I'll add the gh-ost output in a further comment below.

  1. Compare the test table to the ghost table.
#!/usr/bin/env sh

mysql test_ghost -NBe 'SELECT HEX(id) AS id, info, HEX(data) AS data FROM test_table'      > /tmp/test_table.dump
mysql test_ghost -NBe 'SELECT HEX(id) AS id, info, HEX(data) AS data FROM _test_table_gho' > /tmp/_test_table_gho.dump

diff -U8 /tmp/test_table.dump /tmp/_test_table_gho.dump
md5sum /tmp/test_table.dump /tmp/_test_table_gho.dump
$ sh ./bin/compare-test-table.sh
689e658d47fd8b0e987f8356446aa05a  /tmp/test_table.dump
689e658d47fd8b0e987f8356446aa05a  /tmp/_test_table_gho.dump

After the initial migration, everything matches between the two tables.

  1. Now do an INSERT, UPDATE, and DELETE with a BINARY(16) primary key ending in 00.
-- FAIL 1. is an existing row with `id` ending in `00` mirrored correctly if deleted from master table
-- This row was not deleted from the ghost table.
DELETE FROM `test_table` WHERE id = X'11ffffffffffffffffffffffffffff00';

-- FAIL 2. is an existing row with `id` ending in `00` mirrored correctly if updated on master table
-- The ghost table was not updated
UPDATE `test_table` SET info = 'item 2++' WHERE id = X'22ffffffffffffffffffffffffffff00';

-- PASS 3. is an existing row with `id` NOT ending in `00` but `data` ending in `00` mirrored correctly if updated on master table
UPDATE `test_table` SET info = 'item 3++', data = X'33ffffffffffff00' WHERE id = X'33ffffffffffffffffffffffffffffff';

-- PASS 4. is an existing row with `id` NOT ending in `00` mirrored correctly if deleted from master table
DELETE FROM `test_table` WHERE id = X'44ffffffffffffffffffffffffffffff';

-- PASS 5. is an existing row with `id` NOT ending in `00` mirrored correctly if updated on master table
UPDATE `test_table` SET info = 'item 5++', data = X'55ffffffffffffee'  WHERE id = X'55ffffffffffffffffffffffffffffff';

-- PASS 6. is a row with `id` ending in `00` mirrored correctly if inserted into master table
-- but maybe lucky with auto-padding of replace into?
INSERT INTO `test_table` (id, info, data) VALUES (X'66ffffffffffffffffffffffffffff00', 'item 6', X'66ffffffffffffff');

-- PASS 7. is a row with `id` NOT ending in `00` but `data` ending in `00` mirrored correctly if inserted into master table
INSERT INTO `test_table` (id, info, data) VALUES (X'77ffffffffffffffffffffffffffffff', 'item 7', X'77ffffffffffff00');

-- PASS 8. is a row with `id` NOT ending in `00` mirrored correctly if inserted into master table
INSERT INTO `test_table` (id, info, data) VALUES (X'88ffffffffffffffffffffffffffffff', 'item 8', X'88ffffffffffffff');

mysql test_ghost -e 'source ./bin/change-test-table.sql'

  1. Re-compare the test table to the ghost table. There are two differences now:
  1. The deletion using a BINARY(16) key has not been done on the gh-ost table.
  2. The update using a BINARY(16) key has not been done on the gh-ost table.
$ sh ./bin/compare-test-table.sh
--- /tmp/test_table.dump        2018-10-29 01:07:39.349169830 +0000
+++ /tmp/_test_table_gho.dump   2018-10-29 01:07:39.357169511 +0000
@@ -1,7 +1,8 @@
-22FFFFFFFFFFFFFFFFFFFFFFFFFFFF00       item 2++        22FFFFFFFFFFFFFF
+11FFFFFFFFFFFFFFFFFFFFFFFFFFFF00       item 1  11FFFFFFFFFFFFFF
+22FFFFFFFFFFFFFFFFFFFFFFFFFFFF00       item 2  22FFFFFFFFFFFFFF
 33FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF       item 3++        33FFFFFFFFFFFF00
 55FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF       item 5++        55FFFFFFFFFFFFEE
 66FFFFFFFFFFFFFFFFFFFFFFFFFFFF00       item 6  66FFFFFFFFFFFFFF
 77FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF       item 7  77FFFFFFFFFFFF00
 88FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF       item 8  88FFFFFFFFFFFFFF
 99FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF       item 9  99FFFFFFFFFFFF00
8a363b2e25dceb1928df81b1ef48bba4  /tmp/test_table.dump
d65973974ef97bb8b7b95caca4bce937  /tmp/_test_table_gho.dump
  1. Remove the /tmp/ghost.postpone.flag file to complete the migration. A compare of the test_table to the test_table_del shows the same difference as above.

I dumped the slave and master binary log with:

./mysql-5.6.42-linux-glibc2.12-x86_64/bin/mysqlbinlog \
  --no-defaults \
  --set-charset=utf8mb4 \
  --base64-output=decode-rows \
  --verbose \
  /path/to/mysql-bin-changelog.NNNNNN

On the slave's ROW binlog, for the key rows changing test_ghost.test_table, I see:

# at 135553
#181029  1:07:25 server id 1879241316  end_log_pos 135620 CRC32 0xf4be343c      Table_map: `test_ghost`.`test_table` mapped to number 1410
# at 135620
#181029  1:07:25 server id 1879241316  end_log_pos 135689 CRC32 0xd5a24b62      Delete_rows: table id 1410 flags: STMT_END_F
### DELETE FROM `test_ghost`.`test_table`
### WHERE
###   @1='\x11\377\377\377\377\377\377\377\377\377\377\377\377\377\377'
###   @2='item 1'
###   @3='\x11\377\377\377\377\377\377\377'
# at 135689
#181029  1:07:25 server id 1879241316  end_log_pos 135720 CRC32 0x045e1201      Xid = 3491148
COMMIT/*!*/;
# at 135720
#181029  1:07:25 server id 1879241316  end_log_pos 135798 CRC32 0xaffbabb7      Query   thread_id=4947  exec_time=0     error_code=0
SET TIMESTAMP=1540775245/*!*/;
BEGIN
/*!*/;
# at 135798
#181029  1:07:25 server id 1879241316  end_log_pos 135865 CRC32 0x7728f223      Table_map: `test_ghost`.`test_table` mapped to number 1410
# at 135865
#181029  1:07:25 server id 1879241316  end_log_pos 135971 CRC32 0xdf24d1dc      Update_rows: table id 1410 flags: STMT_END_F
### UPDATE `test_ghost`.`test_table`
### WHERE
###   @1='"\377\377\377\377\377\377\377\377\377\377\377\377\377\377'
###   @2='item 2'
###   @3='"\377\377\377\377\377\377\377'
### SET
###   @1='"\377\377\377\377\377\377\377\377\377\377\377\377\377\377'
###   @2='item 2++'
###   @3='"\377\377\377\377\377\377\377'
# at 135971
#181029  1:07:25 server id 1879241316  end_log_pos 136002 CRC32 0xc507994b      Xid = 3491150
COMMIT/*!*/;

On the master's MIXED binlog, for the key rows changing test_ghost.test_table_gho, I see:

# at 150176
#181029  1:07:26 server id 1879241316  end_log_pos 150401 CRC32 0x1de6ff71      Query   thread_id=4943  exec_time=0     error_code=0
SET TIMESTAMP=1540775246/*!*/;
delete /* gh-ost `test_ghost`.`_test_table_gho` */
                                from
                                        `test_ghost`.`_test_table_gho`
                                where
                                        ((`id` = '\021\377\377\377\377\377\377\377\377\377\377\377\377\377\377'))
/*!*/;
# at 150401
#181029  1:07:26 server id 1879241316  end_log_pos 150696 CRC32 0x21b0348e      Query   thread_id=4943  exec_time=0     error_code=0
SET TIMESTAMP=1540775246/*!*/;
update /* gh-ost `test_ghost`.`_test_table_gho` */
                                        `test_ghost`.`_test_table_gho`
                                set
                                        `id`='\"\377\377\377\377\377\377\377\377\377\377\377\377\377\377', `info`='item 2++', `data`='\"\377\377\377\377\377\377\377'
                                where
                                        ((`id` = '\"\377\377\377\377\377\377\377\377\377\377\377\377\377\377'))
/*!*/;

(Note: I changed the actual binary values above to the four character sequence '\377' for readability. Otherwise putting this into github was converting them to '???????????????'.)

Note in the above the id keys used are, in hex, are X'11FFFFFFFFFFFFFFFFFFFFFFFFFFFF' for the DELETE, and X'22FFFFFFFFFFFFFFFFFFFFFFFFFFFF' which are both 15 bytes long, not 16 bytes. The WHERE condition fails to match in both statements, and the DELETE and UPDATE change nothing in the ghost table.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions