Showing posts with label Alter table. Show all posts
Showing posts with label Alter table. Show all posts

Monday, 11 February 2013

Unique constraint and AUTO_INCREMENT. Need of the day!!!



Recently one of our clients reported us disk space issues for volume containing the binary logs(Master) and relay logs on the slave. Our alerting reported the same too, but before we could actually find out, the slaves choked up (could not add more relay logs due to space issues). We were taken by surprise as to what made the Master generate so many binlogs at such a rapid rate, when client didn't change a code line(Suspecting initially the client scripts could have gone wrong).

So now the slaves out of hand and mysql on the master un-responsive due to binary logs full, we needed to purge few binlogs manually to continue investigation. After observing the processlist for sometime, we noticed something unusual with one of the tables (pretty large one 195G). The table is used by internal scripts and was not web facing, that gave us some room to breath. So moving further, the unusual thing was the series of updates on same "ID" value, which should not be the case as per client.

Next we started with the table structure analysis (using "SHOW CREATE TABLE ...") to notice that the AUTO_INCREMENT value was not incremental  Also the last_insert_id() seemed suspicious, a very well known number. Something we learnt at college days. BANG!!!

We had hit the max value for INT datatype 2^32 and the column by default is type "SIGNED". 2147483647 was the magic number.

We were depressed as ALTERing such a huge table is going to be a nightmare. After some assistance from client we were able to drop the historical partition data to shrink the table. Still we had to deal with the ALTER and removal of "what now seemed to be garbage data with ID 2147483647". 

The ALTER was done but was still unsure about the start point for the auto_increment attribute on the table. So after checking the table structure again we noticed the IDs now were getting generated as required.

Further moving on to the investigation of the issue, like "what made such a huge pile-up of binary logs", I noticed that the auto_increment behavior was real unsubtle. The auto_increment column after reaching the max value of INT type, kept on generating the same number. In our clients case for almost 1.7Million records. Next as I mentioned above a series of updates running on the same ID (using a single condition in the where clause "... ID=magic number"), the updates actually changed 1.7Mil rows for every run. The binlog format was set to "mixed" and as per its working all updates were logged in "row" based formats. This started a chain reaction which generated Binary logs eventually filling up the volume, slave got overwhelmed with this and it filled up 100G volume for relay logs too. Next the updates started stalling (since unable to write the statements to binlogs), and finally timing out.

Now everything is back on track, and we decided to rebuild the slaves again. 

However, I realized the fact that, how a unique constraint on AUTO_INCREMENT column would have saved our day. A duplicate key error would have been sufficient for us to let know that INT has maxed out.

Monday, 19 November 2012

Beware !!! Danger ahead "sql_log_bin"...


Some days ago, one of our clients reported that data for present day then, was missing from the slaves, upon querying the table data, we were taken by surprise to find that none of the data was replicated to the slaves, none of the slaves. The large slave lag cleared in seconds, which was not the case earlier with the client generating more than a Gig of data daily. 

To start the investigation we started checking if the relay log is writing the event data properly, checking the relay we saw that the latest data was absent from that. Strange. It was strange because, none of the problems occurred on the slave or master which would have caused the issue, the "exec_master_log_pos" wasn't changing anymore.

The only thing happened the prior weekend was a table conversion. We had a adequately large, almost 40G table to convert to InnoDB, the client needed timelines for that, hence we started the process on one of the slaves. We got the timeline, client was OK with it, then we proceeded with the master's table. Just as obvious we didn't want this statement to replicate to the slaves, hence asked the upcoming team to turn off "sql_log_bin" before starting the alter. 

Boom !!! We had done this before, using sql_log_bin was a cake walk on 5.0 and 5.1, but we were on a Percona 5.5, also we had executed "SET GLOBAL sql_log_bin=OFF", had it been executed on 5.1 or 5.0, this would have returned an error, since its a "SESSION" variable. However in mysql 5.5 this became a "GLOBAL" and "SESSION" variable, all the threads executing DMLs on the servers were not logged in binary logs. The replication had no issues, however it became a pain when we discovered it, and that was almost after a day. 

We had no choice but to refresh the slaves, but the data was huge 1.2T, after 3 days of boredom, we finally had the slaves in sync.

If there was a "Remove feature request" in MySQL or Percona, I would strongly recommend removing the global scope of this configuration.

As an advice, make a habit of using "SESSION" while turning ON or OFF such params or even while viewing the server STATUS.

Happy Replicating...

Saturday, 25 August 2012

Speeding Optimize for InnoDB tables


Speeding up the OPTIMIZE table for InnoDB tables :

Recently we learnt about an incident with one of our client, where we tried to rebuild a huge InnoDB table (180G) to reclaim unused space using command "ALTER TABLE ... ENGINE = INNODB ;", which took forever to rebuild even after turning "fast_index_creation" server variable ON. Let’s dig into details as to why it took such a long time.

Purpose of fast_index_creation :- If a table is altered using ALTER TABLE ... ENGINE=INNODB; then this option will create a temporary table with only clustered index in it, then reload the data from original table and then create secondary indexes on top of it. However there is a serious limitation with this server variable, this works only for secondary indexes created with "ALTER TABLE ADD INDEX ..." and "CREATE INDEX indexname ...".

Percona has overcome this limitation by creating a new server variable known as "expand_fast_index_creation". This option is set to OFF by default.

Please see the below test results for performance improvement on using "expand_fast_index_creation" option over plain "OPTIMIZE TABLE" command. The innodb_buffer_pool_size is set to 256M.

mysql> set expand_fast_index_creation=OFF;
Query OK, 0 rows affected (0.00 sec)

mysql> set profiling=ON;
Query OK, 0 rows affected (0.00 sec)

mysql> optimize table employees;
+---------------------+----------+----------+-------------------------------------------------------------------+
| Table               | Op       | Msg_type | Msg_text                                                          |
+---------------------+----------+----------+-------------------------------------------------------------------+
| employees.employees | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| employees.employees | optimize | status   | OK                                                                |
+---------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (32.42 sec)

mysql> show profile;
+------------------------------+-----------+
| Status                       | Duration  |
+------------------------------+-----------+
| starting                     |  0.000418 |
| checking permissions         |  0.000023 |
| Opening tables               |  0.000034 |
| System lock                  |  0.000016 |
| Waiting for query cache lock |  0.000041 |
| init                         |  0.000008 |
| Opening tables               |  0.000124 |
| System lock                  |  0.000007 |
| setup                        |  0.000043 |
| creating table               |  0.396054 |
| After create                 |  0.000090 |
| copy to tmp table            | 31.557635 |
| rename result table          |  0.373926 |
| end                          |  0.000009 |
| Waiting for query cache lock |  0.000002 |
| end                          |  0.000017 |
| Opening table                |  0.000121 |
| System lock                  |  0.097834 |
| query end                    |  0.000007 |
| closing tables               |  0.000003 |
| freeing items                |  0.000035 |
| cleaning up                  |  0.000006 |
+------------------------------+-----------+
22 rows in set (0.00 sec)

mysql> set profiling=OFF;
Query OK, 0 rows affected (0.00 sec)

As you can notice from the PROFILE that the major part of time was spent in copying the data to tmp table. This involves inserting individual records into the tmp file and the corresponding indexes, after this the index will be sorted.

mysql> set expand_fast_index_creation=ON;     #### Server variable is turned ON ####
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%expand%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| expand_fast_index_creation | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> set profiling=ON;
Query OK, 0 rows affected (0.00 sec)

mysql> optimize table employees;
+---------------------+----------+----------+-------------------------------------------------------------------+
| Table               | Op       | Msg_type | Msg_text                                                          |
+---------------------+----------+----------+-------------------------------------------------------------------+
| employees.employees | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| employees.employees | optimize | status   | OK                                                                |
+---------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (15.31 sec)

mysql> show profile;
+------------------------------+----------+
| Status                       | Duration |
+------------------------------+----------+
| starting                     | 0.000055 |
| checking permissions         | 0.000021 |
| Opening tables               | 0.000031 |
| System lock                  | 0.000014 |
| Waiting for query cache lock | 0.000051 |
| init                         | 0.000019 |
| Opening tables               | 0.000141 |
| System lock                  | 0.000010 |
| setup                        | 0.000042 |
| creating table               | 0.334066 |
| After create                 | 0.000093 |
| copy to tmp table            | 9.129202 |
| restoring secondary keys     | 5.676336 |
| rename result table          | 0.164292 |
| end                          | 0.000011 |
| Waiting for query cache lock | 0.000003 |
| end                          | 0.000021 |
| Opening table                | 0.000146 |
| System lock                  | 0.003445 |
| query end                    | 0.000006 |
| closing tables               | 0.000003 |
| freeing items                | 0.000030 |
| cleaning up                  | 0.000005 |
+------------------------------+----------+
23 rows in set (0.00 sec)

In the above results we can see the records are first copied to a tmp table consisting only of clustered index and then secondary indexes are applied (Restoring secondary keys).

However there are some caveats at using this option, one of which is the FOREIGN KEY constraint. If the table has a foreign key constraint, then percona server will consider "expand_fast_index_creation=OFF", since this constraint cannot be disabled. (This is reason we did not notice any performance improvement while rebuilding the table for one of our client.)

Please see the results below, after a foreign key constraint was added to the table.

mysql> set expand_fast_index_creation=ON;
Query OK, 0 rows affected (0.00 sec)

mysql> set profiling=ON;
Query OK, 0 rows affected (0.00 sec)

mysql> optimize table employees;
+---------------------+----------+----------+-------------------------------------------------------------------+
| Table               | Op       | Msg_type | Msg_text                                                          |
+---------------------+----------+----------+-------------------------------------------------------------------+
| employees.employees | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| employees.employees | optimize | status   | OK                                                                |
+---------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (33.86 sec)

mysql> show profile;
+------------------------------+-----------+
| Status                       | Duration  |
+------------------------------+-----------+
| starting                     |  0.000050 |
| checking permissions         |  0.000022 |
| Opening tables               |  0.000031 |
| System lock                  |  0.000014 |
| Waiting for query cache lock |  0.000071 |
| init                         |  0.000009 |
| Opening tables               |  0.000121 |
| System lock                  |  0.000006 |
| setup                        |  0.000049 |
| creating table               |  0.353180 |
| After create                 |  0.000101 |
| copy to tmp table            | 33.049338 |
| rename result table          |  0.460403 |
| end                          |  0.000010 |
| Waiting for query cache lock |  0.000002 |
| end                          |  0.000019 |
| Opening table                |  0.000162 |
| System lock                  |  0.004451 |
| query end                    |  0.000004 |
| closing tables               |  0.000002 |
| freeing items                |  0.000021 |
| cleaning up                  |  0.000003 |
+------------------------------+-----------+
22 rows in set (0.00 sec)

As seen above there are no performance improvements while the table had foreign key constraint. Disabling through "foreign_key_checks" didnt work either. Same case is seen in terms of "UNIQUE" keys.