Merge lp:~percona-toolkit-dev/percona-toolkit/ptc-errors-on-slave-with-different-time-zone-1388870 into lp:~percona-toolkit-dev/percona-toolkit/release-2.2.13

Proposed by Frank Cizmich
Status: Merged
Approved by: Frank Cizmich
Approved revision: 613
Merged at revision: 621
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/ptc-errors-on-slave-with-different-time-zone-1388870
Merge into: lp:~percona-toolkit-dev/percona-toolkit/release-2.2.13
Diff against target: 155 lines (+38/-13)
7 files modified
bin/pt-table-checksum (+3/-3)
lib/RowChecksum.pm (+3/-3)
t/lib/RowChecksum.t (+4/-4)
t/pt-table-checksum/bugs.t (+25/-0)
t/pt-table-checksum/samples/chunkidx004.txt (+1/-1)
t/pt-table-checksum/samples/chunkidx005.txt (+1/-1)
t/pt-table-checksum/samples/n-chunk-index-cols.txt (+1/-1)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/ptc-errors-on-slave-with-different-time-zone-1388870
Reviewer Review Type Date Requested Status
Daniel Nichter Needs Fixing
Review via email: mp+245683@code.launchpad.net

Description of the change

Problem:
  When a slave is in a server with a different system timezone, pt-table-checksum detects differences on timestamp type columns.

Solution:
 Normalize the output of those columns using UNIX_TIMESTAMP

Notes:
 - No difference in performance was detected. Docs say it is an extremely fast function.
 - Tested manually. Creating a test case for this is near impossible since system tz is read-only on the database, and is lifted from the machine at startup.

To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

I think this is ok. The +0 was just a clever way to coerce a timestamp to a big int. UNIX_TIMESTAMP() does the same but avoids the tz problem. So the only thing this MP lacks is a test case. This should be possible because you can SET GLOBAL time_zone='+7:00' on a slave and then reproduce the problem. We should not get in habit of fixes without solid test cases.

review: Needs Fixing
Revision history for this message
Frank Cizmich (frank-cizmich) wrote :

> I think this is ok. The +0 was just a clever way to coerce a timestamp to a
> big int. UNIX_TIMESTAMP() does the same but avoids the tz problem. So the only
> thing this MP lacks is a test case. This should be possible because you can
> SET GLOBAL time_zone='+7:00' on a slave and then reproduce the problem. We
> should not get in habit of fixes without solid test cases.

Believe me I tried! But this is some sort of subtle issue.
A change in global time_zone alone does not reproduce the problem.
It's only when you change the system time_zone that the issue arises, (and it is solved by using UNXIX_TIMESTAMP)
To change the system_time_zone , the only way is to shut down the slave, change the machine time_zone, and boot slave again.
That's the only way I could reproduce the bug.

I think it's something to do with the way INSERT...SELECT handles timezones. When reading data from slaves it prefers system_time_zone over (global) time_zone.

Again, timestamps are stored correctly, and UNIX_TIMESTAMP fixes the issue.

Just that an automated test case seems near impossible.

Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

Can do TZ="UTC" <start slave> to set system_time_zone on startup, so this can be tested.

Revision history for this message
Frank Cizmich (frank-cizmich) wrote :

Just to add that, also according to my tests, indeed it's a replication thing.

When doing insert on master, slave uses system_tz to do convert to UTC locally when inserting into a timestamp column.
When doing insert..select on master, slave also uses its own system_tz to convert to UTC locally when reading.

613. By Frank Cizmich

pt-tcs added testcase for timezone bug

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/pt-table-checksum'
--- bin/pt-table-checksum 2014-11-11 13:28:27 +0000
+++ bin/pt-table-checksum 2015-01-19 22:19:29 +0000
@@ -5714,8 +5714,8 @@
5714 $query = join(', ',5714 $query = join(', ',
5715 map { 5715 map {
5716 my $col = $_;5716 my $col = $_;
5717 if ( $col =~ m/\+ 0/ ) {5717 if ( $col =~ m/UNIX_TIMESTAMP/ ) {
5718 my ($real_col) = /^(\S+)/;5718 my ($real_col) = /^UNIX_TIMESTAMP\((.+?)\)/;
5719 $col .= " AS $real_col";5719 $col .= " AS $real_col";
5720 }5720 }
5721 elsif ( $col =~ m/TRIM/ ) {5721 elsif ( $col =~ m/TRIM/ ) {
@@ -5815,7 +5815,7 @@
5815 my $type = $tbl_struct->{type_for}->{$_};5815 my $type = $tbl_struct->{type_for}->{$_};
5816 my $result = $q->quote($_);5816 my $result = $q->quote($_);
5817 if ( $type eq 'timestamp' ) {5817 if ( $type eq 'timestamp' ) {
5818 $result .= ' + 0';5818 $result = "UNIX_TIMESTAMP($result)";
5819 }5819 }
5820 elsif ( $float_precision && $type =~ m/float|double/ ) {5820 elsif ( $float_precision && $type =~ m/float|double/ ) {
5821 $result = "ROUND($result, $float_precision)";5821 $result = "ROUND($result, $float_precision)";
58225822
=== modified file 'lib/RowChecksum.pm'
--- lib/RowChecksum.pm 2014-08-05 14:21:32 +0000
+++ lib/RowChecksum.pm 2015-01-19 22:19:29 +0000
@@ -82,10 +82,10 @@
82 $query = join(', ',82 $query = join(', ',
83 map { 83 map {
84 my $col = $_;84 my $col = $_;
85 if ( $col =~ m/\+ 0/ ) {85 if ( $col =~ m/UNIX_TIMESTAMP/ ) {
86 # Alias col name back to itself else its name becomes86 # Alias col name back to itself else its name becomes
87 # "col + 0" instead of just "col".87 # "col + 0" instead of just "col".
88 my ($real_col) = /^(\S+)/;88 my ($real_col) = /^UNIX_TIMESTAMP\((.+?)\)/;
89 $col .= " AS $real_col";89 $col .= " AS $real_col";
90 }90 }
91 elsif ( $col =~ m/TRIM/ ) {91 elsif ( $col =~ m/TRIM/ ) {
@@ -216,7 +216,7 @@
216 my $type = $tbl_struct->{type_for}->{$_};216 my $type = $tbl_struct->{type_for}->{$_};
217 my $result = $q->quote($_);217 my $result = $q->quote($_);
218 if ( $type eq 'timestamp' ) {218 if ( $type eq 'timestamp' ) {
219 $result .= ' + 0';219 $result = "UNIX_TIMESTAMP($result)";
220 }220 }
221 elsif ( $float_precision && $type =~ m/float|double/ ) {221 elsif ( $float_precision && $type =~ m/float|double/ ) {
222 $result = "ROUND($result, $float_precision)";222 $result = "ROUND($result, $float_precision)";
223223
=== modified file 't/lib/RowChecksum.t'
--- t/lib/RowChecksum.t 2012-10-30 15:38:31 +0000
+++ t/lib/RowChecksum.t 2015-01-19 22:19:29 +0000
@@ -126,11 +126,11 @@
126 tbl => $tbl,126 tbl => $tbl,
127 func => 'SHA1',127 func => 'SHA1',
128 ),128 ),
129 q{`film_id`, `title`, `description`, `release_year`, `language_id`, `original_language_id`, `rental_duration`, `rental_rate`, `length`, `replacement_cost`, `rating`, `special_features`, `last_update` + 0 AS `last_update`, }129 q{`film_id`, `title`, `description`, `release_year`, `language_id`, `original_language_id`, `rental_duration`, `rental_rate`, `length`, `replacement_cost`, `rating`, `special_features`, UNIX_TIMESTAMP(`last_update`) AS `last_update`, }
130 . q{SHA1(CONCAT_WS('#', }130 . q{SHA1(CONCAT_WS('#', }
131 . q{`film_id`, `title`, `description`, `release_year`, `language_id`, }131 . q{`film_id`, `title`, `description`, `release_year`, `language_id`, }
132 . q{`original_language_id`, `rental_duration`, `rental_rate`, `length`, }132 . q{`original_language_id`, `rental_duration`, `rental_rate`, `length`, }
133 . q{`replacement_cost`, `rating`, `special_features`, `last_update` + 0, }133 . q{`replacement_cost`, `rating`, `special_features`, UNIX_TIMESTAMP(`last_update`), }
134 . q{CONCAT(ISNULL(`description`), ISNULL(`release_year`), }134 . q{CONCAT(ISNULL(`description`), ISNULL(`release_year`), }
135 . q{ISNULL(`original_language_id`), ISNULL(`length`), }135 . q{ISNULL(`original_language_id`), ISNULL(`length`), }
136 . q{ISNULL(`rating`), ISNULL(`special_features`))))},136 . q{ISNULL(`rating`), ISNULL(`special_features`))))},
@@ -142,11 +142,11 @@
142 tbl => $tbl,142 tbl => $tbl,
143 func => 'FNV_64',143 func => 'FNV_64',
144 ),144 ),
145 q{`film_id`, `title`, `description`, `release_year`, `language_id`, `original_language_id`, `rental_duration`, `rental_rate`, `length`, `replacement_cost`, `rating`, `special_features`, `last_update` + 0 AS `last_update`, }145 q{`film_id`, `title`, `description`, `release_year`, `language_id`, `original_language_id`, `rental_duration`, `rental_rate`, `length`, `replacement_cost`, `rating`, `special_features`, UNIX_TIMESTAMP(`last_update`) AS `last_update`, }
146 . q{FNV_64(}146 . q{FNV_64(}
147 . q{`film_id`, `title`, `description`, `release_year`, `language_id`, }147 . q{`film_id`, `title`, `description`, `release_year`, `language_id`, }
148 . q{`original_language_id`, `rental_duration`, `rental_rate`, `length`, }148 . q{`original_language_id`, `rental_duration`, `rental_rate`, `length`, }
149 . q{`replacement_cost`, `rating`, `special_features`, `last_update` + 0)},149 . q{`replacement_cost`, `rating`, `special_features`, UNIX_TIMESTAMP(`last_update`))},
150 'FNV_64 query for sakila.film',150 'FNV_64 query for sakila.film',
151);151);
152152
153153
=== modified file 't/pt-table-checksum/bugs.t'
--- t/pt-table-checksum/bugs.t 2013-10-02 18:26:11 +0000
+++ t/pt-table-checksum/bugs.t 2015-01-19 22:19:29 +0000
@@ -295,6 +295,31 @@
295);295);
296296
297# #############################################################################297# #############################################################################
298# pt-table-checksum has errors when slaves have different system_time_zone
299# https://bugs.launchpad.net/percona-toolkit/+bug/1388870
300# #############################################################################
301
302# make slave set diferent system_time_zone by changing env var TZ.
303diag(`/tmp/12346/stop >/dev/null`);
304diag(`export TZ='HST';/tmp/12346/start >/dev/null`);
305
306$output = output(
307 sub { pt_table_checksum::main(@args, qw(-t sakila.payment)) },
308);
309
310
311is(
312 PerconaTest::count_checksum_results($output, 'diffs'),
313 0,
314 "Bug 1388870 - No false positive reported when system_tz differ on slave"
315);
316
317# restore slave to original system_tz
318diag(`/tmp/12346/stop >/dev/null`);
319diag(`/tmp/12346/start >/dev/null`);
320
321
322# #############################################################################
298# Done.323# Done.
299# #############################################################################324# #############################################################################
300$sb->wipe_clean($master_dbh);325$sb->wipe_clean($master_dbh);
301326
=== modified file 't/pt-table-checksum/samples/chunkidx004.txt'
--- t/pt-table-checksum/samples/chunkidx004.txt 2011-12-27 18:12:40 +0000
+++ t/pt-table-checksum/samples/chunkidx004.txt 2015-01-19 22:19:29 +0000
@@ -2,7 +2,7 @@
2-- sakila.city2-- sakila.city
3--3--
44
5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `city_id`, `city`, `country_id`, `last_update` + 0)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`city` FORCE INDEX(`idx_fk_country_id`) WHERE ((`country_id` >= ?)) AND ((`country_id` <= ?)) AND (country_id > 100) /*checksum chunk*/5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `city_id`, `city`, `country_id`, UNIX_TIMESTAMP(`last_update`))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`city` FORCE INDEX(`idx_fk_country_id`) WHERE ((`country_id` >= ?)) AND ((`country_id` <= ?)) AND (country_id > 100) /*checksum chunk*/
66
7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`city` FORCE INDEX(`idx_fk_country_id`) WHERE ((`country_id` < ?)) AND (country_id > 100) ORDER BY `country_id` /*past lower chunk*/7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`city` FORCE INDEX(`idx_fk_country_id`) WHERE ((`country_id` < ?)) AND (country_id > 100) ORDER BY `country_id` /*past lower chunk*/
88
99
=== modified file 't/pt-table-checksum/samples/chunkidx005.txt'
--- t/pt-table-checksum/samples/chunkidx005.txt 2011-12-27 18:12:40 +0000
+++ t/pt-table-checksum/samples/chunkidx005.txt 2015-01-19 22:19:29 +0000
@@ -2,7 +2,7 @@
2-- sakila.city2-- sakila.city
3--3--
44
5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `city_id`, `city`, `country_id`, `last_update` + 0)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`city` FORCE INDEX(`PRIMARY`) WHERE ((`city_id` >= ?)) AND ((`city_id` <= ?)) AND (country_id > 100) /*checksum chunk*/5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `city_id`, `city`, `country_id`, UNIX_TIMESTAMP(`last_update`))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`city` FORCE INDEX(`PRIMARY`) WHERE ((`city_id` >= ?)) AND ((`city_id` <= ?)) AND (country_id > 100) /*checksum chunk*/
66
7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`city` FORCE INDEX(`PRIMARY`) WHERE ((`city_id` < ?)) AND (country_id > 100) ORDER BY `city_id` /*past lower chunk*/7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`city` FORCE INDEX(`PRIMARY`) WHERE ((`city_id` < ?)) AND (country_id > 100) ORDER BY `city_id` /*past lower chunk*/
88
99
=== modified file 't/pt-table-checksum/samples/n-chunk-index-cols.txt'
--- t/pt-table-checksum/samples/n-chunk-index-cols.txt 2012-06-10 14:43:42 +0000
+++ t/pt-table-checksum/samples/n-chunk-index-cols.txt 2015-01-19 22:19:29 +0000
@@ -2,7 +2,7 @@
2-- sakila.rental2-- sakila.rental
3--3--
44
5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `rental_id`, `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, `last_update` + 0, CONCAT(ISNULL(`return_date`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?)) AND ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` <= ?)) /*checksum chunk*/5REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `rental_id`, `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, UNIX_TIMESTAMP(`last_update`), CONCAT(ISNULL(`return_date`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?)) AND ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` <= ?)) /*checksum chunk*/
66
7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?)) ORDER BY `rental_date`, `inventory_id`, `customer_id` /*past lower chunk*/7REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?)) ORDER BY `rental_date`, `inventory_id`, `customer_id` /*past lower chunk*/
88

Subscribers

People subscribed via source and target branches

to all changes: