gh-ost: Gh-ost breaks when using unsigned MEDIUMINT

When using unsigned integer data types, binary log behavior changes if the inserted number is higher than the max signed integer value.

The original table

CREATE TABLE `test_table` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `column1` int(11) NOT NULL,
  `column2` int(11) NOT NULL,
  `column3` mediumint(8) unsigned NOT NULL,
  `column4` tinyint(3) unsigned NOT NULL,
  `column5` int(11) NOT NULL,
  `column6` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id_level_id` (`column1`,`column2`)
) ENGINE=InnoDB AUTO_INCREMENT=4647930180 DEFAULT CHARSET=utf8

The new schema;

CREATE TABLE `test_table` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `column1` int(11) NOT NULL,
  `column2` smallint(5) unsigned NOT NULL,
  `column3` mediumint(8) unsigned NOT NULL,
  `column4` tinyint(3) unsigned NOT NULL,
  `column6` int(11) NOT NULL,
  PRIMARY KEY (`column1`,`column2`),
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4632662231 DEFAULT CHARSET=utf8

We were experiencing the same issue explained on the issue #348 . Alter was never starting, even for hours. The table has more than 4 billion records and receiving 200 million records per week. So we tested the version that has included in this comment: https://github.com/github/gh-ost/issues/348#issuecomment-270592112 and it was quite fast. It was estimating 35 hours to complete which is perfectly acceptable for us.

We then executed the following statement

./gh-ost --max-load=Threads_running=800 --critical-load=Threads_running=1200 --chunk-size=50000 --dml-batch-size=100 --user="root" --password="" --host=localhost --database="test" --table="test_table" --verbose --alter="ADD UNIQUE INDEX (id), DROP COLUMN column5, DROP INDEX column1_column2, DROP PRIMARY KEY,ADD PRIMARY KEY(column1,column2),MODIFY column2 SMALLINT UNSIGNED NOT NULL, MODIFY column3 INT NOT NULL" --cut-over=default --exact-rowcount --concurrent-rowcount --default-retries=120 --panic-flag-file=ghost.panic.flag --postpone-cut-over-flag-file=ghost.postpone.flag

These are from binary logs;

### INSERT INTO `test`.`test_table`
### SET
###   @1=4632662255 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=13382498 /* INT meta=0 nullable=0 is_null=0 */
###   @3=536 /* SHORTINT meta=0 nullable=0 is_null=0 */
###   @4=8388607 /* MEDIUMINT meta=0 nullable=0 is_null=0 */
###   @5=3 /* TINYINT meta=0 nullable=0 is_null=0 */
###   @6=1483892218 /* INT meta=0 nullable=0 is_null=0 */
### INSERT INTO `test`.`test_table`
### SET
###   @1=4632662255 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=13382498 /* INT meta=0 nullable=0 is_null=0 */
###   @3=536 /* SHORTINT meta=0 nullable=0 is_null=0 */
###   @4=-6777216 (10000000) /* MEDIUMINT meta=0 nullable=0 is_null=0 */
###   @5=3 /* TINYINT meta=0 nullable=0 is_null=0 */
###   @6=1483892218 /* INT meta=0 nullable=0 is_null=0 */

Since the format is different, I guess we hit some kind of bug. This issue looks like similar to this: https://github.com/github/gh-ost/issues/157 However my version is 1.0.32. MySQL version: Server version: 5.7.10-3-log Percona Server (GPL), Release 3, Revision 63dafaf

The error is;

2017-01-08 17:16:03 ERROR Error 1264: Out of range value for column 'column3' at row 1; query=
                        replace /* gh-ost `test`.`test_table` */ into
                                `test`.`test_table`
                                        (`id`, `column1`, `column2`, `column3`, `column4`, `column6`)
                                values
                                        (?, ?, ?, ?, ?, ?)
                ; args=[4632662255, 13382498, 536, 10000000, 3, 1483892218]

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 27 (20 by maintainers)

Most upvoted comments

HI! The test-case in the comments confirms storage is 3 bytes. Aleks (author of TwinDB recovery tool) also confirmed it on FB after I wrote that post.

I have no reason to dispute InnoDB will use 4 bytes when decoding an individual page in memory. But this is a pretty limited case; since the full buffer pool in memory is the same as the on-disk representation (except when the punch-hole compression is used or encryption). i.e. 16KiB pages do not become variable-size pages in memory.

(I am not sure of this advice as it applies to client/server protocol. It could also be using the full 4 bytes).

I’ve created a tangential issue to this based on the alter statement used in this issue (https://github.com/github/gh-ost/issues/357).

tl;dr The docs suggest you need a shared unique/PK index between the old and new tables, but in this case the PK becomes a unique key and a new PK is created from a non-unique index, so there is technically no shared index. The new issue is to determine if there are any caveats to that and if we need to update the documentation.

Hi Shlomi,

Sorry for the late response.

We have experienced non gh-ost related issues on our database and had to fix them first. So, we stopped the alter. (This is a high load database with 1.5+ billion queries daily, 250000 records added per week. )

We’ve just started gh-ost again after fixing the issues, I’ll let you know how it goes. It is estimating 35 hours.

It’s been running for more than 22 hours and estimating 12 more hours. So far there are no issues. It looks like it’s been fixed but I’ll get back to you once it finalises the migration.