Skip to content

Commit

Permalink
MYSQL-45: InnoDB should allow flushing to be gated by age of pages
Browse files Browse the repository at this point in the history
Currently using innodb_max_dirty_pages_pct especially with a large
innodb_io_capacity does not enforce any lower limit on the number of
dirty pages, and this can increase the number of writes dramatically.
While normally this would be OK (flush if you can) this is not
desirable behavior on SSDs where write lifetime is limited.

In order to reduce and control the frequency of write requests to
SSD, this change introduces a new variable named
innodb_flush_dirty_pages_age which can be used to set the minimum age
of dirty pages to be flushed from the buffer pool if the number of
dirty pages is below the maximum percentage. This impacts the
frequency with which frequently modified pages are flushed, which can
help reduce IO load in systems with frequently modified pages (hot
spots).
  • Loading branch information
Davi Arnaut committed Feb 11, 2012
1 parent 9256eea commit 436e5ff
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
SET @start_global_value = @@global.innodb_flush_dirty_pages_age;
SELECT @start_global_value;
@start_global_value
0
Valid values are zero or above
select @@global.innodb_flush_dirty_pages_age >=0;
@@global.innodb_flush_dirty_pages_age >=0
1
select @@global.innodb_flush_dirty_pages_age;
@@global.innodb_flush_dirty_pages_age
0
select @@session.innodb_flush_dirty_pages_age;
ERROR HY000: Variable 'innodb_flush_dirty_pages_age' is a GLOBAL variable
show global variables like 'innodb_flush_dirty_pages_age';
Variable_name Value
innodb_flush_dirty_pages_age 0
show session variables like 'innodb_flush_dirty_pages_age';
Variable_name Value
innodb_flush_dirty_pages_age 0
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';
VARIABLE_NAME VARIABLE_VALUE
INNODB_FLUSH_DIRTY_PAGES_AGE 0
select * from information_schema.session_variables where variable_name='innodb_flush_dirty_pages_age';
VARIABLE_NAME VARIABLE_VALUE
INNODB_FLUSH_DIRTY_PAGES_AGE 0
set global innodb_flush_dirty_pages_age=10;
select @@global.innodb_flush_dirty_pages_age;
@@global.innodb_flush_dirty_pages_age
10
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';
VARIABLE_NAME VARIABLE_VALUE
INNODB_FLUSH_DIRTY_PAGES_AGE 10
select * from information_schema.session_variables where variable_name='innodb_flush_dirty_pages_age';
VARIABLE_NAME VARIABLE_VALUE
INNODB_FLUSH_DIRTY_PAGES_AGE 10
set session innodb_flush_dirty_pages_age=1;
ERROR HY000: Variable 'innodb_flush_dirty_pages_age' is a GLOBAL variable and should be set with SET GLOBAL
set global innodb_flush_dirty_pages_age=1.1;
ERROR 42000: Incorrect argument type to variable 'innodb_flush_dirty_pages_age'
set global innodb_flush_dirty_pages_age=1e1;
ERROR 42000: Incorrect argument type to variable 'innodb_flush_dirty_pages_age'
set global innodb_flush_dirty_pages_age="foo";
ERROR 42000: Incorrect argument type to variable 'innodb_flush_dirty_pages_age'
set global innodb_flush_dirty_pages_age=-7;
Warnings:
Warning 1292 Truncated incorrect innodb_flush_dirty_pages_age value: '-7'
select @@global.innodb_flush_dirty_pages_age;
@@global.innodb_flush_dirty_pages_age
0
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';
VARIABLE_NAME VARIABLE_VALUE
INNODB_FLUSH_DIRTY_PAGES_AGE 0
SET @@global.innodb_flush_dirty_pages_age = @start_global_value;
SELECT @@global.innodb_flush_dirty_pages_age;
@@global.innodb_flush_dirty_pages_age
0
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

#
# 2010-01-25 - Added
#

--source include/have_innodb.inc

SET @start_global_value = @@global.innodb_flush_dirty_pages_age;
SELECT @start_global_value;

#
# exists as global only
#
--echo Valid values are zero or above
select @@global.innodb_flush_dirty_pages_age >=0;
select @@global.innodb_flush_dirty_pages_age;
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
select @@session.innodb_flush_dirty_pages_age;
show global variables like 'innodb_flush_dirty_pages_age';
show session variables like 'innodb_flush_dirty_pages_age';
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';
select * from information_schema.session_variables where variable_name='innodb_flush_dirty_pages_age';

#
# show that it's writable
#
set global innodb_flush_dirty_pages_age=10;
select @@global.innodb_flush_dirty_pages_age;
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';
select * from information_schema.session_variables where variable_name='innodb_flush_dirty_pages_age';
--error ER_GLOBAL_VARIABLE
set session innodb_flush_dirty_pages_age=1;

#
# incorrect types
#
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_flush_dirty_pages_age=1.1;
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_flush_dirty_pages_age=1e1;
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_flush_dirty_pages_age="foo";

set global innodb_flush_dirty_pages_age=-7;
select @@global.innodb_flush_dirty_pages_age;
select * from information_schema.global_variables where variable_name='innodb_flush_dirty_pages_age';

#
# cleanup
#
SET @@global.innodb_flush_dirty_pages_age = @start_global_value;
SELECT @@global.innodb_flush_dirty_pages_age;
8 changes: 8 additions & 0 deletions storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11683,6 +11683,13 @@ static MYSQL_SYSVAR_UINT(trx_rseg_n_slots_debug, trx_rseg_n_slots_debug,
NULL, NULL, 0, 0, 1024, 0);
#endif /* UNIV_DEBUG */

static MYSQL_SYSVAR_UINT(flush_dirty_pages_age, srv_buf_flush_dirty_pages_age,
PLUGIN_VAR_RQCMDARG,
"Minimum age (since the last flush) of pages to be flushed from "
"the buffer pool if the number of dirty pages is below the maximum "
"percentage.",
NULL, NULL, 0, 0, UINT_MAX32, 0);

static struct st_mysql_sys_var* innobase_system_variables[]= {
MYSQL_SYSVAR(additional_mem_pool_size),
MYSQL_SYSVAR(autoextend_increment),
Expand Down Expand Up @@ -11756,6 +11763,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= {
#ifdef UNIV_DEBUG
MYSQL_SYSVAR(trx_rseg_n_slots_debug),
#endif /* UNIV_DEBUG */
MYSQL_SYSVAR(flush_dirty_pages_age),
NULL
};

Expand Down
2 changes: 2 additions & 0 deletions storage/innobase/include/srv0srv.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ extern ulint srv_buf_pool_curr_size; /*!< current size in bytes */
extern ulint srv_mem_pool_size;
extern ulint srv_lock_table_size;

extern uint srv_buf_flush_dirty_pages_age;

extern ulint srv_n_file_io_threads;
extern my_bool srv_random_read_ahead;
extern ulong srv_read_ahead_threshold;
Expand Down
36 changes: 33 additions & 3 deletions storage/innobase/srv/srv0srv.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ UNIV_INTERN ulint srv_buf_pool_curr_size = 0;
UNIV_INTERN ulint srv_mem_pool_size = ULINT_MAX;
UNIV_INTERN ulint srv_lock_table_size = ULINT_MAX;

UNIV_INTERN uint srv_buf_flush_dirty_pages_age = 0;

/* This parameter is deprecated. Use srv_n_io_[read|write]_threads
instead. */
UNIV_INTERN ulint srv_n_file_io_threads = ULINT_MAX;
Expand Down Expand Up @@ -509,6 +511,9 @@ thread ensures that we flush the log files at least once per
second. */
static time_t srv_last_log_flush_time;

/* Records the time of the last flush limit calculation. */
static time_t srv_last_flush_limit_time;

/* The master thread performs various tasks based on the current
state of IO activity and the level of IO utilization is past
intervals. Following macros define thresholds for these conditions. */
Expand Down Expand Up @@ -2668,6 +2673,8 @@ srv_master_thread(
{
buf_pool_stat_t buf_stat;
srv_slot_t* slot;
ib_uint64_t flush_lsn_limit = 0;
ib_uint64_t new_flush_lsn_limit = srv_start_lsn;
ulint old_activity_count;
ulint n_pages_purged = 0;
ulint n_bytes_merged;
Expand Down Expand Up @@ -2844,7 +2851,9 @@ srv_master_thread(
#endif
/* If i/os during the 10 second period were less than 200% of
capacity, we assume that there is free disk i/o capacity
available, and it makes sense to flush srv_io_capacity pages.
available, and it makes sense to flush srv_io_capacity pages
unless there is a need to reduce the frequency of write requests
to the storage device (which might be the case for SSDs).
Note that this is done regardless of the fraction of dirty
pages relative to the max requested by the user. The one second
Expand All @@ -2856,12 +2865,33 @@ srv_master_thread(
n_ios = log_sys->n_log_ios + buf_stat.n_pages_read
+ buf_stat.n_pages_written;

/* Constantly record the youngest age at which a dirty page
might be flushed. */
if (difftime(time(NULL), srv_last_flush_limit_time)
> srv_buf_flush_dirty_pages_age) {

/* The limit prevents the flushing of pages dirtied within
the last srv_buf_flush_dirty_pages_age seconds. */
flush_lsn_limit = new_flush_lsn_limit;
new_flush_lsn_limit = log_get_lsn();
srv_last_flush_limit_time = time(NULL);
}

/* If not limited by age, try to keep the number of modified pages
in the buffer pool as low as possible. */
if (!srv_buf_flush_dirty_pages_age) {
flush_lsn_limit = IB_ULONGLONG_MAX;
}

srv_main_10_second_loops++;
if (n_pend_ios < SRV_PEND_IO_THRESHOLD
&& (n_ios - n_ios_very_old < SRV_PAST_IO_ACTIVITY)) {
&& (n_ios - n_ios_very_old < SRV_PAST_IO_ACTIVITY)
&& flush_lsn_limit) {

srv_main_thread_op_info = "flushing buffer pool pages";
buf_flush_list(PCT_IO(100), IB_ULONGLONG_MAX);
buf_flush_list(PCT_IO(100), flush_lsn_limit);

flush_lsn_limit = 0;

/* Flush logs if needed */
srv_sync_log_buffer_in_background();
Expand Down

0 comments on commit 436e5ff

Please sign in to comment.