2014년 4월 22일 화요일

Linux Kernel Parameters and MySQL

Overview

This writing explains relationships between Linux kernel parameter and MySQL server. Especially, It deal with Linux kernel parameter related to frequency of flushing data to disk.

Definition

Kernel parameters related to virtual memory exist in '/proc/sys/vm'.
Explain some of them which are related to frequency of flushing data to disk.

Kernel Parameter
description
Default
dirty_background_ratio
Contains, as a percentage of total available memory that contains free pages and reclaimable pages,
the number of pages at which the background kernel flusher threads will start writing out dirty data.
The total available memory is not equal to total system memory
10(%)
dirty_ratio
Contains, as a percentage of total available memory that contains free pages and reclaimable pages,
the number of pages at which a process which is generating disk writes will itself start writing out dirty data.
The total available memory is not equal to total system memory.
40(%)
dirty_background_bytes
Contains the amount of dirty memory at which the background kernel flusher threads will start writeback.
Note: dirty_background_bytes is the counterpart of dirty_background_ratio.
Only one of them may be specified at a time. When one sysctl is written it is immediately taken into account
to evaluate the dirty memory limits and the other appears as 0 when read.
0
dirty_bytes
Contains the amount of dirty memory at which a process generating disk writes will itself start writeback.
Note: dirty_bytes is the counterpart of dirty_ratio. Only one of them may be specified at a time.
When one sysctl is written it is immediately taken into account to evaluate the dirty memory limits
and the other appears as 0 when read.
Note: the minimum value allowed for dirty_bytes is two pages (in bytes);
any value lower than this limit will be ignored and the old configuration will be retained.
0
dirty_writeback_centisecs
The kernel flusher threads will periodically wake up and write 'old' data out to disk.
This tunable expresses the interval between those wakeups, in 100'ths of a second.
Setting this to zero disables periodic writeback altogether.
500(1/100sec)
dirty_expire_centisecs
This tunable is used to define when dirty data is old enough to be eligible for writeout by the kernel flusher threads.
It is expressed in 100'ths of a second. Data which has been dirty in-memory for longer than this interval will be written out next time a flusher thread wakes up.
3000(1/100sec)

Process

Pdflush daemon wakes up and checks whether dirty page will be written or not every 'dirty_writeback_centisecs'.
Namely, That daemon checks whether a percentage of dirty page in total avaiable memory is more than 'dirty_background_ratio' or not.
If the percentage of dirty page in total avaiable memory is more than 'dirty_background_ratio', Pdflush daemon writes dirty page to disk.


Opinion

If you set a large value into dirty_background_ratio, dirty page can exist more in the filesystem cache.
But if so, Pldflush daemon writes more dirty page into the disk and also Disk I/O would take too long time.
When Disk I/O is performed, Cpu is used. So If Disk I/O would take too long time, Whole system will become slow.
In addition, The data to be changed so there are a lot of cache, The data will be lost after a server failure.
So, I think that reducing the dirty_background_ratio is an efficient.

If you set a large value into dirty_writeback_centisecs, cycle of checking will be longer and there will be a lot of page which is handled at once.
This situation will give a load to server. so Reduce this value until an appropriate value for decreasing page handled at once.

The dirty_expire_centisecs is default 30 seconds. Until this time has passed, pdflush daemon do not actually write on disk. 
Dirty data will be still in filesystem cache for 30 seconds. so This value should be reduced. 
but If you reduce the value too much, that daemon attempts to write too frequently.
so I think It should be reduced suitably.
(However, if you use the the InnoDB storage engine,
Depending on the innodb_flush_method option, It might not seem to be affected by kernel parameters.
In this case, you can adjust innodb_max_dirty_pages_pct value for controlling the frequency of flushing data to disk.)
** innodb_max_dirty_pages_pct : ratio of dirty page to allow in innodb buffer pool


Latest Issue and Kernel Parameters

Lately, we have some issues about Disk I/O.
Some server is under the heavy load due to a sudden increase of Disk I/O.

If most of the Filesystem cache is full of dirty data, cache unmap script set to prevent swap reduces the cache until 1 gigabyte. 
but The all data left in the cache after unmapping is dirty data. so Disk i/o occurs again and It gives a load to the server. 
This is the cause of the issue.

so now, We adjusted kernel parameter of that server as follows.
/proc/sys/vm/dirty_background_ratio : 10 ===> 1
/proc/sys/vm/dirty_expire_centisecs : 3000 ===> 1000
It means that dirty page exist until one percent of total available memory and also data which has been dirty in-memory until 10 seconds.


Details of Mysql_Cache_Unmap

We have mentioned about cache_unmap script with swap in filesystem cache before.
(Reference URL : http://kakaodbe.blogspot.kr/2013/09/mysql-linux-filesystem-cache-2.html)
This chapter deals with how apply the script in server.

we wrote cache_unmap script by referring to yosinori's script. 
( you can see the source code through attached file. ==> mysql_cache_unmap.c )

Periodically for performing cache unmapping, we have been set a crontab as follows.

[ Crontab Setting ]
*/10 * * * * root LD_LIBRARY_PATH=/otp/mysql/lib: /otp/mysql/admin/mysql_cache_unmap --defaults-file=/etc/my.cnf --binary_os_cache_size=1024M > /otp/mysql/admin/mysql_cache_unmap.log2>&1

Options

 --defaults-file   :   you can specify the path to the configuration file of MySQL server
                                through this option. 
  cache_unmap script reads configuration file and
                                obtains file path of binary log and data 
file, innodb redo log.


 --binary_os_cache_size   :   It means the capacity to leave without removing it from the
                                                   Linux OS Cache.
  cache_unmap utility leaves the specified
                                                   capacity in the Linux OS Cache from the binary log that
                                                   occurred recently.


The following is a running log of mysql_cache_unmap script. 

root@host:~ 14:10:09> cat /opt/mysql/admin/mysql_cache_unmap.log
Read configuration
    innodb_data_dir : /opt/mysql/data
    innodb_log_dir : /opt/mysql/data
    binary_log_dir : /opt/mysql/data/mysql-binary
    binary_os_cache_size : 1073741824
    relay_log_dir : /opt/mysql/data/mysql-relay
> unmap_file_all : datafile : /opt/mysql/data/dbname1/tablename1.ibd
> unmap_file_all : datafile : /opt/mysql/data/dbname1/tablename2.ibd
> unmap_file_all : logfile : /opt/mysql/data/ib_logfile0
> unmap_file_all : logfile : /opt/mysql/data/ib_logfile1
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041728 : 0 ~ 60083119
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041727 : 0 ~ 104857974
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041726 : 0 ~ 104857975
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041725 : 0 ~ 104857883
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041724 : 0 ~ 104857852
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041723 : 0 ~ 104857920
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041722 : 0 ~ 104857989
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041721 : 0 ~ 104857781
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041720 : 0 ~ 104857784
> skip unmap file: binary : /opt/mysql/data/mysql-binary.041719 : 0 ~ 104857666
> unmap_file_segment : binary : /opt/mysql/data/mysql-binary.041718 : 0 ~ 34920063 of 104857944
> unmap_file_all : binary : /opt/mysql/data/mysql-binary.041717 : 0 ~ 104857764
> unmap_file_all : binary : /opt/mysql/data/mysql-binary.041716 : 0 ~ 104857852
> unmap_file_all : binary : /opt/mysql/data/mysql-binary.041715 : 0 ~ 104857772
> unmap_file_all : binary : /opt/mysql/data/mysql-binary.041714 : 0 ~ 104857847

As above, mysql_cache_unmap script unmap the caching area of datafile and redo_log_file, binary_log_file from filesystem cache.
CentOs(under 5.x version) consider a Filesystem cache as a top priority. So if you do not have the actual free memory, the memory of the most widely used application would be down to swap.
If MySQL is using the most memory, cached data of MySQL is swapped. So when performing a query in MySQL, MySQL server put up result datas in memory again.
That is why we use mysql_cache_unmap script. we periodically perform this script for decreasing cache size.

Attention 

This script remove all cached contents of redo_log and innodb system data file from filesystem cache.
If innodb_flush_method is "O_DIRECT", innodb data file is performed as direct I/O but innodb redo log is performed as cached I/O. 
so in this case, Avoid unmapping about redo_log file. 
If innodb_flush_method is not "O_DIRECT" or "ALL_O_DIRECT", Cache unmapping about redo log and Innodb data file is not recommended. 
You can apply that by changing the mysql_cache_unmap program.


mysql_cache_unmap script unmap the area of caching by using 'posix_fadvise' method.

[ posix_fadvise SYSNOPSIS ]

#define _XOPEN_SOURCE 600
#include <fcntl.h>
int posix_fadvise(int fd, off_t offset, off_t len, int advice);

The advice applies to a (not necessarily existent) region starting at offset and extending for len bytes (or until the end of the file if len is 0) within the file referred to by fd.
The advice is not binding; it merely constitutes an expectation on behalf of the application. 
On success, zero is returned. On error, an error number is returned.

The following is an excerpt from the mysql_cache_unmap source code. Substantially cache unmapping is done In this part.

#define _XOPEN_SOURCE 600
#include <fcntl.h>
int unmap_file_segment(const char *fpath, size_t start, size_t len){
  int fd = open(fpath, O_RDONLY);
  if (fd < 0){
    fprintf(stderr, "ERROR : Failed to open %s\n", fpath);
    return 1;
  }
  int r = posix_fadvise(fd, start, len, POSIX_FADV_DONTNEED);
  if (r != 0){
    fprintf(stderr, "ERROR : posix_fadvice failed for %s\n", fpath);
  }
  close(fd);
  /* if posix_fadvise is succeeded, then sleep 25 milli seconds */
  usleep(25 * 1000);
  return 0;
}

You can see the value of the 'advice' parameter in this code is "POSIX_FADV_DONTNEED".
There are many kinds of advice. POSIX_FADV_DONTNEED is one of them. 
POSIX_FADV_DONTNEED attempts to free cached pages associated with the specified region.

2014년 4월 21일 월요일

Adding pt-online-schema-change new parameters

Adding new parameter for pt-online-schema-change 


Percona Toolkit has a lot of excellent features(module), Among that pt-online-schema-change is really really useful feature as almost DBAs already experienced.
But they can't cover every requests of the world even though that is the best tool and pt-online-schema-change also.
So we modified a little bit and let pt-online-schema-change adapt for our requirements.

  1) For changing both of PRIMARY KEY change and Partitioning table.
  2) For changing columns' default value and update new default value to columns.
  3) For stable schema change, Adding sleep time between copy of chunks.

We added 4 new parameters to pt-online-schema-change for above features.

  • --prompt-before-copy
    Same as "--ask-pass", this parameter doesn't need any value. if --prompt-before-copy specified, pt-online-schema-change will prompt (wait) user input after creating new table. If you want to check new table schema or have more chanage requirements on new table, you can use this parameter. Especially you may want to use it when change both of PRIMARY KEY and Table partitioning. But you can't change both change by one ALTER statement and also pt-online-schema-change. But you can do both task with --prompt-before-copy of patched pt-online-schema-change. 
  • --skip-copy-columns
    pt-online-schema-change will copy all common columns on both of old and new table. So direct "ALTER TABLE .. DROP fd1, ADD fd1 NOT NULL DEFAULT 'N'" ddl statement and pt-online-schema-change's result would be different. Sometimes we need to drop all column value but column name, pt-online-schema-change is useless at this time. But you can do this with --skip-copy-columns option of patched pt-online-schema-change.
  • --sleep-time-us
    pt-online-schema-change will control speed of row copy based on MySQL server's load(especially mysql status variables). But your query is very lightweight and fast, then pt-online-schema-chnage's rule would not be sufficient. So we made pt-online-schema-change doing sleep between each chunk of copy task by --sleep-time-us parameter. --sleep-time-us use micro seconds unit(1/1,000,000 second). And we usally set 10000(10 milli second) ~ 50000(50 milli second) as --sleep-time-us parameter.
  • --print-sql
    Sometimes we need to check the create table and create trigger DDL ran by pt-onine-schema-change. pt-online-schema-change has debugging mode already, but it may be so verbose to you. We added --print-sql parameter to make pt-online-schema-change print out only CREATE TABLE and CREATE TRIGGER DDL.


Applied parameters would be printed out like below when you run patched pt-online-schema-change.
-- Additional parameters ----------------------------------
  >> skip columns : Not specified
  >> sleep time (us) : 50000
  >> prompting user operation : Yes
-----------------------------------------------------------

So, let's see the simple examples for 1~3 scenarios.


1) For changing both of PRIMARY KEY change and Partitioning table.

Let's say we want to change below normal table to partitioned table based on DATETIME type column (fd2).

CREATE TABLE test.test_partition (
  id INT AUTO_INCREMENT,
  fd1 VARCHAR(10),
  fd2 DATETIME,
  PRIMARY KEY(id)
) ENGINE=InnoDB;

For partitioned table, First we have to add fd2 column as part of PRIMARY KEY.
So we need to below two ALTER statement.

ALTER TABLE test.test_partition DROP PRIMARY KEY, ADD PRIMARY KEY(id, fd2);
ALTER TABLE test.test_partition PARTITION ...

But above two ALTER statement can't be combined as one statement, and also pt-online-schema-change too.
In this case, we could use --prompt-before-copy parameter on patched pt-online-schema-change.

/usr/bin/pt-online-schema-change --alter "DROP PRIMARY KEY, ADD PRIMARY KEY(id, fd2)" D=test,t=test_partition \
--no-drop-old-table \
--no-drop-new-table \
--chunk-size=500 \
--chunk-size-limit=600 \
--defaults-file=/etc/my.cnf \
--host=127.0.0.1 \
--port=3306 \
--user=root \
--ask-pass \
--progress=time,30 \
--max-load="Threads_running=100" \
--critical-load="Threads_running=1000" \
--chunk-index=PRIMARY \
--charset=UTF8MB4 \
--no-check-alter \
--sleep-time-us=50000 \
--prompt-before-copy \
--print-sql \
--execute

Like below, patched pt-online-schema-change will print out some informational messages and prompt(wait) for user confirmation, because --prompt-before-copy is specified.
...
-- Additional parameters ----------------------------------
  >> skip columns : Not specified
  >> sleep time (us) : 50000
  >> prompting user operation : Yes
-----------------------------------------------------------
...
-- Create Triggers ---------------------------------------
CREATE TRIGGER `pt_osc_test_test_partition_ins` AFTER INSERT ON `test`.`test_partition` FOR EACH ROW REPLACE INTO `test`.`_test_partition_new` ...
CREATE TRIGGER `pt_osc_test_test_partition_upd` AFTER UPDATE ON `test`.`test_partition` FOR EACH ROW REPLACE INTO `test`.`_test_partition_new` ...
CREATE TRIGGER `pt_osc_test_test_partition_del` AFTER DELETE ON `test`.`test_partition` FOR EACH ROW DELETE IGNORE FROM `test`.`_test_partition_new` WHERE `test`.`_test_partition_new`.`id` <=> OLD.`id`;
----------------------------------------------------------

Table copy operation is paused temporarily by user request '--prompt-before-copy'.
pt-online-schema-change utility created new table, but not triggers.
   ==> new table name : `test`.`_test_partition_new`

So if you have any custom operation on new table, do it now.
Type 'yes', when you ready to go.
Should I continue to copy [Yes] ? : <== pt-online-schema-change will wait user input after creating new table (_test_partition_new)


At this time, we can run ALTER TABLE PARTITION .. statement on another terminal. After that type "yes" on pt-online-schema-change terminal.

ALTER TABLE _test_partition_new
PARTITION BY RANGE COLUMNS(CRT_DT)
(
 ...
 PARTITION PF_20140420 VALUES LESS THAN ('2014-04-21 00:00:00') ENGINE = InnoDB,
 PARTITION PF_20140421 VALUES LESS THAN ('2014-04-22 00:00:00') ENGINE = InnoDB,
 PARTITION PF_20140422 VALUES LESS THAN ('2014-04-23 00:00:00') ENGINE = InnoDB,
 PARTITION PF_20140423 VALUES LESS THAN ('2014-04-24 00:00:00') ENGINE = InnoDB
);

All after pt-online-schema-change task would be same as original pt-online-schema-change.
Finally we can get the table applied parimary key change and partitioning with just one time of pt-online-schema-change.

Warning: pt-online-schema-change will print warning message out and just terminated when you change primary key spec. So you may want to use --no-check-alter option for change primary key. Of couse you should be careful when you change PRIMARY KEY.




2) For changing columns' default value and update new default value to columns.

Let's say we want to change default value as 'N' of fd1 column and set new defalut value ('N') to fd1 column of existing rows.
In this case pt-online-schema-change can change table schema as DEFAULT 'N', but not existing rows' column value. Because pt-online-schema-change will copy all common columns' value of new and old table.

CREATE TABLE test.test_defaultvalue (
  id INT AUTO_INCREMENT,
  fd1 CHAR(1) DEFAULT 'Y',
  fd2 DATETIME,
  PRIMARY KEY(id)
) ENGINE=InnoDB;

ALTER TABLE test.test_defaultvalue MODIFY fd1 CHAR(1) DEFAULT 'N';

UPDATE test.test_defaultvalue SET fd2='N' WHERE fd2='Y';

So original pt-online-schema-change, we have to update all row's fd2 column value as 'N', and this update will hinder service queries' concurrency (this will lock all rows of table when there's no proper index).

On patched pt-online-schema-change, we can use --skip-copy-columns parameter to prevent pt-online-schema-change from copying some common column.
Like below example, If "--skip-copy-columns='fd1'" is specified, patched pt-online-schema-change just ignore fd1 column not to copy to new table. So new table's all row will get a default value 'N'.

/usr/bin/pt-online-schema-change --alter "MODIFY fd1 CHAR(1) DEFAULT 'N'" D=test,t=test_defaultvalue \
--no-drop-old-table \
--no-drop-new-table \
--chunk-size=500 \
--chunk-size-limit=600 \
--defaults-file=/etc/my.cnf \
--host=127.0.0.1 \
--port=3306 \
--user=root \
--ask-pass \
--progress=time,30 \
--max-load="Threads_running=100" \
--critical-load="Threads_running=1000" \
--chunk-index=PRIMARY \
--charset=UTF8MB4 \
--sleep-time-us=50000 \
--skip-copy-columns='fd1' \
--prompt-before-copy \
--print-sql \
--execute

And you may use --prompt-before-copy and --print-sql options to check TRIGGER and INSERT .. SELECT .. query ran by pt-online-schema-change.



3) For stable schema change, Adding sleep time between copy of chunks.

pt-online-schema-change already has a feature to control the copy speed based on mysql server's status variables. But as I mentioned before, MySQL server's workload is CPU bound and query is sample and fast, pt-online-schema-change's feature might not be sufficient.
So on patched pt-online-schema-change, we added --sleep-time-us parameter. If you specify --sleep-time-us parameter with proper integer value, patched pt-online-schema-change will sleep specified micro seconds after each chunk.

--sleep-time-us parameter will make slower online schema change job, but you can change table schema more stable manner.



Download

https://github.com/kakao/percona-toolkit