InnoDB原生Checkpoint策略及各版本优化详解

4月 18th, 2012
  1. Checkpoint原理

关于Innodb Checkpoint的原理,此处不准备介绍,推荐 How InnoDB performs a checkpoint [2]一文,作者详细讲解了Innodb的Checkpoint原理。

  1. Checkpoint触发条件

  • 每1S
    • 若buffer pool中的脏页比率超过了srv_max_buf_pool_modified_pct = 75,则进行Checkpoint,刷脏页,flush PCT_IO(100)的dirty pages = 200
    • 若采用adaptive flushing,则计算flush rate,进行必要的flush
  • 每10S
    • 若buffer pool中的脏页比率超过了70%,flush PCT_IO(100)的dirty pages
    • 若buffer pool中的脏页比率未超过70%,flush PCT_IO(10)的dirty pages = 20
    • 每10S,必定调用一次log_checkpoint,做一次Checkpoint

Innodb如何计算脏页比率?adaptive flushing时如何计算flush rate?如何进行真正的flush操作,是否使用AIO,将在以下章节中一一分析。

  1. Checkpoint流程

    1. 计算脏页比率

srv0srv.c::srv_master_thread -> buf0buf.c::buf_get_modified_ratio_pct -> buf_get_total_list_len

for (i = 0; i < srv_buf_pool_instances; i++) {

        buf_pool_t*    buf_pool;

        buf_pool = buf_pool_from_array(i);

        *LRU_len += UT_LIST_GET_LEN(buf_pool->LRU);

        *free_len += UT_LIST_GET_LEN(buf_pool->free);

        *flush_list_len += UT_LIST_GET_LEN(buf_pool->flush_list);

    }

ratio = (100 * flush_list_len) / (1 + lru_len + free_len);

脏页比率 = 需要被flush的页面数 / (使用中的页面数 + 空闲页面数 + 1)

其中,所有的值,在buf_pool_t结构中均有统计,无需实际遍历buffer pool进行计算

  1. 计算adaptive flush rate

函数流程:

buf0buf.c::buf_flush_get_desired_flush_rate ->

  1. 从buf_pool_t结构中,获得总dirty page的数量

  2. 计算最近一段时间之内,redo日志产生的平均速度

    redo_avg = (ulint) (buf_flush_stat_sum.redo

            / BUF_FLUSH_STAT_N_INTERVAL

            + (lsnbuf_flush_stat_cur.redo));

    其中,BUF_FLUSH_STAT_N_INTERVAL = 20S,20S内的平均redo产生速度

/** Number of intervals for which we keep the history of these stats.

Each interval is 1 second, defined by the rate at which

srv_error_monitor_thread() calls buf_flush_stat_update(). */

#define
BUF_FLUSH_STAT_N_INTERVAL 20

flush的统计信息,每隔20S会被buf_flush_stat_update函数重置

  1. 计算过去一段时间内,flush的平均速度;与当前需要的flush速度

    lru_flush_avg = buf_flush_stat_sum.n_flushed

                / BUF_FLUSH_STAT_N_INTERVAL

                + (buf_lru_flush_page_count

                 – buf_flush_stat_cur.n_flushed);

        n_flush_req = (n_dirty * redo_avg) / log_capacity;

    其中,BUF_FLUSH_STAT_N_INTERVAL = 20S不变,计算的仍旧是过去20S内的平均flush速度

  2. 若当前所需flush的page数量 > 20S flush的平均数量,则adaptive flushing会尝试进行一次flush操作。flush的dirty pages数量最大是PCT_IO(100),200个dirty pages。
    1. flush dirty pages算法

函数流程:

buf0flu.c::buf_flush_list -> buf_flush_start -> buf_flush_batch -> buf_flush_end -> buf_flush_common

  1. 首先,判断当前是否有正在进行的相同类型的flush (buf_flush_start),有则直接退出
  2. buf_flush_batch -> buf_flush_flush_list_batch -> buf_flush_page_and_try_neighbors -> buf_flush_try_neighbors -> buf_flush_page -> buf_flush_buffered_writes ->
    1. 从flush_list的最后一个页面开始,向前遍历页面 & flush
    2. 对于a)中的page,尝试flush,并且尝试flush该page的neighbors pages (buf_flush_try_neighbors)
      1. 首先计算可选的neighbors范围。所谓neighbors范围,指的是space_id相同,page_no不同的page,只有这些page才是连续的。

        buf_flush_area    = ut_min(

                    ut_min(64, ut_2_power_up((b)->curr_size / 32)),

                    buf_pool->curr_size / 16);

        low = (offset / buf_flush_area) * buf_flush_area;

            high = (offset / buf_flush_area + 1) * buf_flush_area;

            low为当前page,neighbors的范围既为buf_flush_area,最大64

            neighbors为当前page开始,page_no连续递增的buf_flush_area个pages

      2. 所有的dirty pages,在buffer pool中同时以hash表存储,根据(space_id, [page_no_low, page_no_high])到hash表中进行查找,若存在,则flush此dirty page
  3. buf_flush_page -> buf_flush_write_block_low -> log_write_up_to ->
    1. flush脏页之前,必须保证脏页对应的日志已经写回日志文件(log_write_up_to)
    2. 判断是否需要使用double write
      1. 若不需要double write保护,直接调用fil_io进行flush操作,设置type = OS_FILE_WRITE;mode = OS_AIO_SIMULATED_WAKE_LATER
      2. 若需要double write保护,则调用buf_flush_post_to_doublewrite_buf函数
        1. 写到double write就算完成,退出buf_flush_page
  4. buf_flush_batch -> buf_flush_buffered_writes
    1. buf_flush_batch函数,在完成2,3步骤,batch flush之后,调用buf_flush_buffered_writes函数进行真正的write操作
    2. buf_flush_buffered_writes:将double write memory写出到disk
      1. 我的测试中,有7个dirty pages,每个page大小为16k = 16384,因此doublewrite buffer的大小为 16384 * 7 = 114688
      2. doublewrite buffer的写,为同步写,调用fil_io(OS_FILE_WRITE, TRUE)
      3. 同步写之后,调用fil_flush函数,将doublewrite buffer中的内容flush到disk
        1. windows:

          FlushFileBuffers(file);

        2. linux:

          os_file_fsync(file);    or

          fcntl(file, F_FULLFSYNC, NULL);

      4. 在doublewrite buffer被成功flush到disk之后,对应的dirty pages不会再丢失数据。此时再将doublewrite buffer对应的dirty pages写出到disk
        1. fil_io(OS_FILE_WRITE | OS_AIO_SIMULATED_WAKE_LATER, FALSE);
        2. 写dirty pages,采用非同步写 AIO
      5. 在dirty pages都完成异步IO之后,调用buf_flush_sync_datafiles函数,将所有的异步IO操作,flush到磁盘

    /* Wake simulated aio thread to actually post the writes to the operating system */

        os_aio_simulated_wake_handler_threads();

        /* Wait that all async writes to tablespaces have been posted to     the OS */

        os_aio_wait_until_no_pending_writes();

        /* Now we flush the data to disk (for example, with fsync) */

        fil_flush_file_spaces(FIL_TABLESPACE);

  5. 标识当前flush操作结束(buf_flush_end)
  6. 收集当前flush操作的统计信息(buf_flush_common)
    1. Checkpoint info更新

在完成Checkpoint流程中的flush dirty pages之后,Innodb Checkpoint的大部分流程已经完成,只余下最后的修改Checkpoint Info信息。

  1. 流程一

更新Checkpoint Info流程一,流程一每10S调用一次:

srv_master_thread -> log0log.c::log_checkpoint ->

log_buf_pool_get_oldest_modification ->

  • 读取系统中,最老的日志序列号。实现简单,读取lsn flush list中最老日志对应的lsn即可

log_write_up_to(oldest_lsn, LOG_WAIT_ALL_GROUPS, TRUE) ->

  • 将日志flush到oldest_lsn

log_groups_write_checkpoint_info -> log_group_checkpoint ->

fil_io(OS_FILE_WRITE | OS_FILE_LOG, FALSE) ->

  • 遍历所有日志组,分别更新每个日志组对应的Checkpoint Info
  • 构造Checkpoint Info,使用os_aio_log_array进行异步写I/O操作
  1. 流程二

更新Checkpoint Info流程二,在I/O较为繁忙的系统中,流程二每1S调用一次:

srv_master_thread -> log0log.ic::log_free_check -> log0log.c::log_check_margins ->

log_checkpoint_margin -> log_preflush_pool_modified_pages -> log_checkpoint

  • 读取当前日志系统中的最老日志序列号lsn

    根据oldest_lsn与log->lsn(current lsn)之间的差距,判断日志空间是否足够,是否需要进行flush dirty pages操作

  • 读取当前日志系统中最老的Checkpoint Lsn

    根据last_checkpoint_lsn与log->lsn之间的差距,判断是否需要向前推进检查点

  • 若需要flush dirty pages,调用函数log_preflush_pool_modified_pages

    log_preflush_pool_modified_pages -> buf_flush_list(ULINT_MAX, new_oldest)

    • new_oldest参数,指定当前将dirty pages flush到何lsn?sync参数指定当前flush操作是否为同步操作?由函数log_checkpoint_margin计算,代码如下:

    oldest_lsn = log_buf_pool_get_oldest_modification();

        age = log->lsnoldest_lsn;

        if (age > log->max_modified_age_sync) {

            /* A flush is urgent: we have to do a synchronous preflush */

            sync = TRUE;

            advance = 2 * (agelog->max_modified_age_sync);

        } else
    if (age > log->max_modified_age_async) {

            /* A flush is not urgent: we do an asynchronous preflush */

            advance = agelog->max_modified_age_async;

        } else {

            advance = 0;

        }

        ib_uint64_t    new_oldest = oldest_lsn + advance;

        if (checkpoint_age > log->max_checkpoint_age) {

            /* A checkpoint is urgent: we do it synchronously */

            checkpoint_sync = TRUE;

            do_checkpoint = TRUE;

    }

  • log->max_modified_age_(a)sync; log->max_checkpoint_age

    以上参数用于控制是否需要进行log flush,以及是否需要进行Checkpoint。

    参数的计算,在log0log.c::log_calc_max_ages函数中完成,代码较为简单,如下所示:

            margin = smallest_capacityfree;

margin = marginmargin / 10;    /* Add still some extra safety */

            log->log_group_capacity = smallest_capacity;

            log->max_modified_age_async = marginmargin / LOG_POOL_PREFLUSH_RATIO_ASYNC;

            log->max_modified_age_sync = margin margin / LOG_POOL_PREFLUSH_RATIO_SYNC;

            log->max_checkpoint_age_async = marginmargin/LOG_POOL_CHECKPOINT_RATIO_ASYNC;

    log->max_checkpoint_age = margin;

#define
LOG_POOL_CHECKPOINT_RATIO_ASYNC    32

#define
LOG_POOL_PREFLUSH_RATIO_SYNC        16

#define
LOG_POOL_PREFLUSH_RATIO_ASYNC        8

简单来说,margin近似认为是Innodb系统可用的日志空间的9/10;

日志空间消耗超过7/8时,一定要进行异步Flush日志;

日志空间消耗超过15/16时,一定要进行同步Flush日志;

日志空间消耗超过31/32时,一定要进行异步Flush Buffer Pool;

日志空间消耗达到margin上限时,一定要进行同步Flush Buffer Pool

以上判断均在log_checkpoint_margin函数中完成,1S中判断一次。

  • 若需要向前推进检查点,调用函数log_checkpoint,log_checkpoint函数的流程,在前一章节中已经分析。
  1. innodb_flush_method

无论是数据文件,还是日志文件,在完成write操作之后,最后都需要flush到disk。

是否flush?如何进行flush?日志文件与数据文件的flush操作有何不同?通过参数innodb_flush_method控制。

关于innodb_flush_method这个参数的意义及设置,网上有大量的文档。具体可参考 innodb_flush_method与File I/OSAN vs Local-disk::innodb_flush_method performance benchmarks 两文。

接下来我主要从源码层面简单分析以下innodb_flush_method参数的使用(在innodb 5.5-5.6中,此参数的名字修改为innobase_file_flush_method)。

  1. 初始化

函数处理流程:

srv0start.cc::innobase_start_or_create_for_mysql

    if (srv_file_flush_method_str == NULL) {

        /* These are the default options */

        srv_unix_file_flush_method = SRV_UNIX_FSYNC;

        srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED;

    } else if (0 == ut_strcmp(srv_file_flush_method_str, “fsync”)) {

        srv_unix_file_flush_method = SRV_UNIX_FSYNC;

    } else if (0 == ut_strcmp(srv_file_flush_method_str, “O_DSYNC”)) {

        srv_unix_file_flush_method = SRV_UNIX_O_DSYNC;

    } else if (0 == ut_strcmp(srv_file_flush_method_str, “O_DIRECT”)) {

        srv_unix_file_flush_method = SRV_UNIX_O_DIRECT;

    } else if (0 == ut_strcmp(srv_file_flush_method_str, “littlesync”)) {

        srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC;

    } else if (0 == ut_strcmp(srv_file_flush_method_str, “nosync”)) {

        srv_unix_file_flush_method = SRV_UNIX_NOSYNC;

}

根据用户指定的srv_file_flush_method_str的不同,设置srv_unix_file_flush_method的不同取值,innodb内部,通过判断此参数,来确定以何种模式open file,以及是否flush write。

简单起见,此处只拷贝了linux部分处理代码,未包括windows部分。

  1. open file

Innodb系统启动阶段,设置完成srv_unix_file_flush_method参数之后,可以进行I/O操作,I/O操作的总入口为函数fil0fil.c::fil_io,相信大家已经看过上面的分析之后,对此函数不会陌生。

fil0fil.c::fil_io函数中,处理了file open的过程,函数流程如下:

fil0fil.c::fil_io -> fil_node_perpare_for_io -> fil_node_open_file ->

if (space->purpose == FIL_LOG) {

        node->handle = os_file_create(innodb_file_log_key, node->name, OS_FILE_OPEN,

                     OS_FILE_AIO, OS_LOG_FILE, &ret);

    } else
if (node->is_raw_disk) {

        node->handle = os_file_create(innodb_file_data_key, node->name,

                     OS_FILE_OPEN_RAW, OS_FILE_AIO, OS_DATA_FILE, &ret);

    } else {

        node->handle = os_file_create(innodb_file_data_key, node->name, OS_FILE_OPEN,

                     OS_FILE_AIO, OS_DATA_FILE, &ret);

    }

根据当前文件类型不同,底层依赖的硬件环境不同,调用os_file_create宏定义open对应的文件。os_file_create宏定义对应的函数是os_file_create_func.

os_file_create_func函数处理open file的流程:

  • Log file将O_DSYNC转化为O_SYNC,O_DSYNC设置只对data file有用

    if (type == OS_LOG_FILE && srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {

            create_flag = create_flag | O_SYNC;

        file = open(name, create_flag, os_innodb_umask);

  • Data file与O_DIRECT组合,需要禁用底层os file cache

    /* We disable OS caching (O_DIRECT) only on data files */

    if (type != OS_LOG_FILE && srv_unix_file_flush_method == SRV_UNIX_O_DIRECT)

        os_file_set_nocache(file, name, mode_str);

  1. flush data

fil0fil.c::fil_io函数打开file之后,可以进行file的write与必要的flush操作,write操作在前面的章节中已经分析,本章主要看srv_unix_file_flush_method参数对于flush操作的影响。

  • srv_unix_file_flush_method = SRV_UNIX_NOSYNC

    无论是log file,还是data file,一定只write,但不flush

  • srv_unix_file_flush_method = SRV_UNIX_O_DSYNC

    Log file: 不flush

    if (srv_unix_file_flush_method != SRV_UNIX_O_DSYNC

        && srv_unix_file_flush_method != SRV_UNIX_NOSYNC)

        fil_flush(group->space_id);

    当然,其他情况下,Log file是否一定flush?还与参数srv_flush_log_at_trx_commit的设置有关

    Data file:

  • srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC

    Data file: 不flush

  • srv_unix_file_flush_method =
  1. 未明之处

innodb_flush_method参数,在使用系统native aio时,好像对于data file完全无影响,还需要进一步的理解与调研。

  1. Percona版本优化

关于Percona XtraDB的优化,推荐一篇十分好的文章:XtraDB: The Top 10 enhancements [11]. 该文详细列举了XtraDB对于原生InnoDB引擎做的最重要的10个优化,虽然文章是2009年8月写的,但是主要优化都已经存在了,每一个都值得一读。

当然,在本文中,我接下来主要讨论XtraDB在Checkpoint与Insert Buffer两个方面做的优化。Checkpoint与Insert Buffer优化,都属于XtraDB优化中的一个大类:I/O优化,可见网文:Improved InnoDB I/O Scalability [12].

增加innodb_io_capacity选项。原生innodb中的参数srv_io_capacity,写死的是200,XtraDB中增加此选项,用户可以根据系统的硬件不同而设置不同的io_capacity。但是,io_capacity与系统的实际I/O能力还是有所区别,网文与其中的讨论:MYSQL 5.5.8 and Percona Server: being adaptive [13]给出了fusion-io下,innodb_io_capacity选项具体如何设置更为合理。

源代码,参考的版本包括Percona-Server-5.5.15-rel21.0; Percona-Server-5.5.18-rel23.0; Percona-Server-5.1.60;

  1. Flush & Checkpoint优化

XtraDB对于Flush & Checkpoint的优化,主要在于新增了系统变量:innodb_adaptive_checkpoint

此变量可设置的值包括:none, reflex, estimate, keep_average,分别对应于0/1/2/3;同时改变量要与Innodb自带的innodb_adaptive_flushing变量配合使用

关于每种设置的不同含义,[12]中有详尽介绍,此处给出简单说明:

  • none

    原生innodb adaptive flushing策略

  • reflex

    与innodb基于innodb_max_dirty_pages_pct的flush策略类似。不同之处在于,原生innodb是根据dirty pages的量来flush;而此处根据dirty pages的age进行flush。每次flush的pages根据innodb_io_capacity计算

  • estimate

    与reflex策略类似,都是基于dirty page的age来flush。不同之处在于,每次flush的pages不再根据innodb_io_capacity计算,而是根据[number of modified blocks], [LSN progress speed]和[average age of all modified blocks]计算

  • keep_average

    原生Innodb每1S触发一次dirty page的flush,此参数降低了flush的时间间隔,从1S降低为0.1S

注:在最新的Percona XtraDB版本中,reflex策略已经被废弃;estimate,keep_average策略的算法,或多或少也与网文中提到的有所出入,应该是算法优化后的结果,具体算法参考以下的几个小章节。

  1. reflex

此策略,Percona XtraDB 5.1.60版本中存在,但是在5.5版本中被删除,源代码级别彻底删除,可能并无太多的意义,功能与estimate重合,不建议使用。

  1. estimate

函数处理流程:

    // 1. innodb_adaptive_checkpoint参数必须与innodb_adaptive_flushing同时设置

    if (srv_adaptive_flushing && srv_adaptive_flushing_method == 1)

        // 2. 获取当前最老dirty page的lsn

        oldest_lsn = buf_pool_get_oldest_modification();

        // 3. 若当前未flush的日志量,超过Checkpoint_age的1/4,则进行flush

        if ((log_sys->lsn) – oldest_lsn) > (log_sys->max_checkpoint_age/4)

            // 4. estimate flush策略

  • 遍历buffer pool的flush_list链表,统计以下信息
    • n_blocks:    链表中的page数量
    • level:        链表所有page刷新的紧迫程度的倒数

      level += log_sys->max_checkpoint_age –

              (lsn – oldest_modification);

      最新修改的page,level贡献越大,紧迫程度越小;越老的page,紧迫程度越大。

      关于log_sys->max_checkpoint_age的功能,可参考 Checkpoint Info更新-流程二 章节。

  • 需要flush的dirty pages数量bpl,计算公式如下:

    bpl = n_blocks * n_blocks * (lsn – lsn_old) / level;

    其中:lsn_old为上一次flush时记录下的lsn

  • 调用buf_flush_list函数,进行flush

    buf_flush_list(bpl, oldest_lsn + (lsn – lsn_old));

 

  1. keep_average

keep_average策略,将原生的Innodb,每1S flush一次dirty pages,改为每0.1S做一次。

    if (srv_adaptive_flushing && srv_adpative_flushing_method == 2)

        next_itr_time -= 900;

Innodb,每次将netx_itr_time加1000ms,然后sleep这1000ms时间。进入keep_average策略,将next_itr_time减去900,那么下一次也就只会sleep 100ms时间。

接下来则是分析keep_average策略如何计算当前需要flush多少dirty pages。下图能够较为清晰的说明keep_average策略:
Percona keep_average flush策略图

图表 51 Percona keep_average flush策略

图中名词解释:

prev_flush_info.count

上一次flush前,buffer pool中的dirty pages数量

new_blocks_sum

上次记录prev_flush_info.count之后,系统新产生的dirty pags

blocks_sum

当前系统,buffer pool中的dirty pages数量

flushed_blocks_sum

    flushed_blocks_sum = new_blocks_sum + prev_flush_info.count – blocks_sum;

    上次的flush数量+循环间其余flush的数量

Next Round

本次flush dirty pages前,记录新的prev_flush_info.count = blocks_sum

n_pages_flushed_prev

此参数未标出,表示上次keep_average策略成功flush了多少dirty pages

计算本次应该flush的量:

n_flush = blocks_sum * (lsn – lsn_old) / log_sys->max_modified_age_async;

公式分析:

    系统中的dirty pages,有多少比率需要在此时被flush

log_sys->max_modified_age_async

    日志异步flush的临界值,若当前系统的lsn间隔大于此值,则启动异步flush。

    此参数为原生Innodb所有,Percona此处借用来计算。

    关于log_sys->max_modified_age_async参数在Innodb中的作用及设置,

可参考 Checkpoint Info更新-流程二 章节。

flush量微调:

    if (flushed_blocks_sum > n_pages_flushed_prev)

        n_flush -= (flushed_blocks_sum – n_pages_flushed_prev);

    若flushed_blocks_sum > n_pages_flushed_prev,两次之间的实际flush量大于

    上次keep_average的flush量,那么本次keep_average需要flush量应相应减少。

  1. MySQL 5.6.6 Flushing优化

InnoDB自带的flushing策略有问题,Percona的XtraDB对其做了优化,提出了estimate与keep_average两种新的flushing策略。在MySQL 5.6.6-labs版本中,Oracle InnoDB团队提出了自己的新的flushing算法[16],不同于已有的Percona算法,以下对此新算法作简要分析。关于新flushing算法的测试结果,可见[14][15]。

首先,在MySQL 5.6-labs版本中,dirty pages的flushing操作,从InnoDB主线程中移出,新开一个buf_flush_page_cleaner_thread (page_cleaner)线程来进行。仍旧是每次休眠1s,然后进行一次flushing尝试。

  1. 处理流程

新的flushing算法的处理流程如下:

buf0flu.cc::buf_flush_page_cleaner_thread();

// 回收buffer pool LRU链表,此处不考虑

page_cleaner_flush_LRU_tail();

// 根据用户指定的参数,判断是否需要进行dirty pages的flushing

    // 每1S调用一次此函数

page_cleaner_flush_pages_if_needed();

// 根据用户指定的innodb_flushing_avg_loops参数(默认取值为30),

        // 计算前innodb_flushing_avg_loops个flushing的平均速度

        // 1. avg_page_rate:     平均的dirty pages的flushing速度

        // 2. lsn_avg_rate:        平均的日志产生速度

if (++n_iterations >= srv_flushing_avg_loops)

// 计算目前系统中的最老未flush的dirty page的日志年龄

age = cur_lsn – oldest_lsn;

// 根据系统中的脏页数量,计算本次需要flushing的页面数量:

        // 1. 计算系统中脏页面占用的比率[0, 100]

        // 2. 若用户未指定脏页的lower water mark – innodb_max_dirty_pages_pct_lwm

        //     [0, 99]默认值为50. 则采用原有的srv_max_buf_pool_modified_pct参数判断

        //     超过此参数则返回100.

        // 3. 若用户指定了lower water mark,并且脏页比率超过此值,则返回

        //     dirty_pct * 100 / (srv_max_buf_pool_modified_pct + 1);否则返回0

        //

        // 注意:

        // 根据innodb_max_dirty_pages_pct_lwm与innodb_max_buf_pool_modified_ptc

        // 两参数的设置情况,最终的返回值主要与后者相关,后者设置的越小,计算出

        // 的flushing量越大,甚至会超过100 (超过innodb_io_capacity)。但是,由于后者

        // 默认设置为75,因此在默认设置下,返回值的范围是[0, 132],一次flush的量,

        // 并不会比innodb_io_capacity多多少。

pct_for_dirty = af_get_pct_for_dirty();

// 根据系统中最老日志的年龄,计算本次需要flushing的页面数量:

        // 1. 根据系统参数innodb_adaptive_flushing_lwm,默认取值为10,区间为[0, 70]

//     计算对应的lower water mark

        //     lsn age: af_lwm = (srv_adaptive_flushing_lwm * log_get_capacity()) / 100

        // 2. 若当前最老日志的年纪小于lower water mark age,则不需要根据日志flushing

        // 3. 获得系统的异步日志刷新阀值:max_async_age,此值的具体含义可见3.4.1.2

        //     章节,近似等于日志空间的7/8

        // 4. 若当前系统的日志年纪小于异步刷新阀值,并且系统参数

//     innodb_adaptvie_flushing未开启,则不需要根据日志flushing

        // 5. 不满足以上条件,需要根据日志进行flushing,返回值pct_for_lsn的计算如下:

        //         lsn_age_factor = (age * 100) / max_async_age; 取值区间近似为(0, 114]

        //        pct_for_lsn =     srv_max_io_capacity / srv_io_capacity

        //                    * (lsn_age_factor * sqrt(lsn_age_factor)) / 7.5;

        //        pct_for_lsn的取值范围近似为(0, 600]

        //        其中,innodb_max_io_capacity参数为硬件能够支撑的最大IOPS,默认值

        //        4000,innodb_io_capacity默认值为400

        // 注意:

// 1.基于日志的flushing,一次flushing的速度与日志的产生速度正相关,

        //日志产生的速度越快,需要flushing的dirty pages数量也越多,速度也更快。

        // 2. 基于日志的flushing,其返回值有可能超过100,也就是一次flush的量

        // 超过innodb_io_capacity,甚至是超过innodb_max_io_capacity的系统参数。

        // 3. 在默认参数环境下,基于日志的flush策略,其速度有可能远远大于基于

        // dirty pages的策略。其主要目的是为了保证日志回收足够快,从而不至于出现

        // 由于日志空间不足而导致的async甚至是sync flushing出现。

pct_for_lsn = af_get_pct_for_lsn(age);

// 真正需要flushing的页面数量,是根据脏页数量/日志年龄 计算出来的

// 本次flushing页面数量的较大值

        pct_total = ut_max(pct_for_dirty, pct_for_lsn);

// 根据本次计算出来的flushing页面数量,与上次flush的平均速度,再次

        // 取平均值,获得最终需要flushing的页面数量

n_pages = (PCT_IO(pct_total) + avg_page_rate) / 2;

// 当然,需要根据用户设置的innodb_max_io_capacity参数,进行一次微调

        // 保证一次flushing的页面数量,不会超过硬件IO能力的处理上限

if (n_pages > srv_max_io_capacity)

n_pages = srv_max_io_capacity;

// 开始进行本次真正的flushing操作

page_cleaner_do_flush_batch();

  1. 新增参数

MySQL 5.6.6-labs版本中,针对新的flush策略,新增了如下几个参数,具体参数的功能及意义,在上面的流程分析中已经详细给出。

  • innodb_adaptive_flushing_lwm
  • innodb_max_dirty_pages_pct_lwm
  • innodb_max_io_capacity
  • innodb_io_capacity
  • innodb_flushing_avg_loops

 

  1. 参考文献

[1] http://blogs.innodb.com/wp/2010/09/mysql-5-5-innodb-change-buffering/        Mysql 5.5: InnoDB Change Buffering

[2] http://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/    How InnoDB performs a checkpoint

[3] http://www.percona.com/doc/percona-server/5.1/scalability/innodb_io.html?id=percona-server:features:innodb_io_51&redirect=2    Improved InnoDB I/O Scalability

[4] http://www.facebook.com/note.php?note_id=408059000932    InnoDB fuzzy checkpoints

[5] http://en.wikipedia.org/wiki/Overlapped_I/O        Windows Overlapped I/O

[6] http://tinyclouds.org/iocp-links.html            Asynchronous I/O in Windows for Unix Programmers

[7] http://tiaozhanshu.com/libaio-api.html            Linux下原生异步I/O接口Libaio的用法

[8] http://blog.csdn.net/zhaiwx1987/article/details/7165100    由percona5.5参数innodb_adaptive_flushing_method想到的….

[9] http://themattreid.com/wordpress/2012/01/06/san-vs-local-disk-innodb_flush_method-performance-benchmarks/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+Themattreid+%28TheMattReid+-+MySQL+DBA%29    SAN vs Local-disk :: innodb_flush_method performance benchmarks

[10] http://www.orczhou.com/index.php/2009/08/innodb_flush_method-file-io/    innodb_flush_method与File I/O

[11] http://www.mysqlperformanceblog.com/2009/08/13/xtradb-the-top-10-enhancements/    XtraDB: The Top 10 enhancements

[12] http://www.percona.com/docs/wiki/percona-xtradb:patch:innodb_io    Improved InnoDB I/O Scalabilty

[13] http://www.mysqlperformanceblog.com/2010/12/20/mysql-5-5-8-and-percona-server-being-adaptive/    MYSQL 5.5.8 and Percona Server: being adaptive

[14] http://dimitrik.free.fr/blog/archives/2012/04/mysql-performance-improved-adaptive-flushing-in-56labs.html MySQL Performance: Improved Adaptive Flushing in 5.6-labs

[15] http://dimitrik.free.fr/blog/archives/04-01-2012_04-30-2012.html#142    MySQL Performance: 5.5 and 5.6-labs @TPCC-like

[16] http://blogs.innodb.com/wp/2012/04/new-flushing-algorithm-in-innodb/    New flushing algorithm in InnoDB

  1. repls
    4月 18th, 201220:54

    “若buffer pool中的脏页比率超过了srv_max_buf_pool_modified_pct = 75,则进行Checkpoint”
    你是怎么看待checkpoint的? 我一直觉得这一步不应该称作进行checkpoint,而只是flush dirty page,虽然两者很相似。
    我觉得innodb里发生checkpoint的几个条件:
    1.在每1s,利用函数log_free_check() 判断是否需要进行checkpoint。根据lsn_age和lsn checkpoint_age决定进行同步刷还是异步刷。
    2.然后就是每10s进行一次checkpoint,这一步不需要进行条件判断,必然进行。

    • hedengcheng
      4月 19th, 201209:31

      确切的说,我写的是dirty page的flush策略,但是目前网上的一般都称之为Checkpoint策略吧。

  2. myownstars
    4月 29th, 201413:40

    Checkpoint触发条件提及的per-1s和per-10s,到了5.6应该统统由page_cleaner负责的吧(而非master thread),因为mysql版本更新很快,最好标注版本号

    • hedengcheng
      5月 4th, 201409:18

      好的,以后注意标明版本。

  3. www.883838.com
    12月 31st, 201515:14

    创业相当于千军万马过独木桥,成功者往往是没退路的人。有太多选项的“人才”不适合创业。