最近在测试5.6.11的写性能,当我完成一项测试,关闭workload,习惯性设置innodb_max_dirty_pages_pct为0,然后等待脏页刷完再shutdown。
发现存在刷脏的抖动:
time flushed Innodb_data_written
11:49:57 3692 117.3m
11:49:58 1125 33.5m|
11:49:59 4187 134.6m
11:50:00 1241 35.0m
11:50:01 4076 127.4m
从pstack的采样结果来看,page cleaner线程频繁出现这样的backtrace。
buf_flush_page_cleaner_thread->page_cleaner_sleep_if_needed->os_thread_sleep
加了两个计数来监控,也发现page cleaner线程平均sleep时间过长(750,000 ms 左右)。
那么为什么会出现波动呢?
检查发现,机器上有一个heartbeat脚本每隔两秒钟更新一条记录。这会导致如下条件成立:
2385 if (srv_check_activity(last_activity)
2386 || buf_get_n_pending_read_ios()
2387 || n_flushed == 0) {
2388 page_cleaner_sleep_if_needed(next_loop_time);
2389 }
srv_check_activity(last_activity) 为非0值。目前的判断太过粗糙了。一条简单的UPDATE 会造成page cleaner线程的巨大波动。
相应的问题,已经report到buglist上:http://bugs.mysql.com/bug.php?id=69174
好吧,对我而言办法比较土,临时的解决方案就是增加一个变量,来限制最大sleep时间,这样就可以获得一个平缓的刷脏频率:
time flushed Innodb_data_written
11:51:43 3971 120.6m
11:51:44 3987 124.6m
11:51:45 3859 124.6m
11:51:46 3992 121.0m
11:51:47 3855 120.7m
另外,当srv_check_activity(last_activity)返回非0值后,会走不同的逻辑:
2393 if (srv_check_activity(last_activity)) {
2394 last_activity = srv_get_activity_count();
2395
2396 /* Flush pages from end of LRU if required */
2397 n_flushed = buf_flush_LRU_tail();
2398
2399 /* Flush pages from flush_list if required */
2400 n_flushed += page_cleaner_flush_pages_if_needed();
buf_flush_LRU_tail() : 依次遍历每个Buffer pool instance,从LRU尾部开始扫描,直到第srv_LRU_scan_depth个page停止,按批次刷LRU,每次期望刷100个page(一个CHUNK), 每个Bp会轮srv_LRU_scan_depth/100次循环
这里存在的问题,Mark Callaghan Report在这个Bug上:http://bugs.mysql.com/bug.php?id=69170
每一个CHUNK的循环,都是从LRU的尾部开始的,因为这中间会去释放bp的Mutex。
这样问题就比较明显了,如果有很多脏页,例如,我们假设LRU上的都是脏页.从函数buf_flush_LRU_list_batch的逻辑我们可以知道
1.如果这个Page是脏的,不可以替换,将其IO-FIX,并分发IO请求
2.回到LRU尾部,跳过IO-FIX的page,发发现新的脏页,同样将其IO-FIX,并返回到LRU尾部。
可以看到这里时间复杂度是O(N*N).当然如果是快速存储设备,可能在回到LRU尾部重新扫描时,之前IOFIX的page已经完成了IO,因此可以直接放到FreeList上。因此快速存储设备最优可以到达O(N)
按照Inaam的说法,5.6.12对此会有优化,拭目以待。另外在5.6.12中,可能会有很多sleep被替换成condition wait,希望这些能对写负载有帮助。