http://d.hatena.ne.jp/sh2/20111121

2010年1月の記事SystemTapでMySQLのDisk I/Oを分析するの続きです。以前作成したSystemTapスクリプトは、実はMySQL 5.5のDisk I/Oを分析することができませんでした。というのも、MySQL 5.5からInnoDBが非同期I/Oを行うようになったのですが、以前のスクリプトは非同期I/Oに対応していなかったためです。本日はMySQL 5.5におけるInnoDBの非同期I/Oについて、確認していきたいと思います。

非同期I/Oとは

非同期I/Oとは、I/O処理をブロックされることなしに行う方式のことです。通常のI/O処理はそれが完了するまで待たされてしまうのですが、非同期I/Oを用いることでI/O処理の完了を待つことなしに他の処理を進めることができます。以下のウェブサイトでとても詳しく解説されています。

詳解 Linuxカーネル 第3版をお持ちの方は、16.4.1章「Linux 2.6における非同期I/O」も合わせてご覧ください。

MySQL 5.5のInnoDBにおける非同期I/Oの利用

InnoDBは元々Windowsでのみ非同期I/Oを行っており、それ以外のプラットフォームでは「simulated AIO」と呼ばれる擬似的な非同期I/Oを行っていました。これはSQLを実行するコネクションスレッドI/O要求をキューイングし、I/OヘルパースレッドI/O要求をキューから取り出して実際のI/Oを行うというものです。コネクションスレッドI/Oの完了を待たなくて良いという意味では確かに非同期ですが、I/Oヘルパースレッドが行う処理はあくまで同期I/Oであるため、この方式はsimulated AIOと呼ばれていました。I/Oヘルパースレッドの数はinnodb_read_io_threads、innodb_write_io_threadsという二つのパラメータで設定することが可能です。

MySQL 5.5のInnoDBでは、LinuxにおいてOSネイティブの非同期I/Oが利用されるようになりました。このためにはあらかじめlibaioパッケージをインストールしておく必要がありますが、設定パラメータinnodb_use_native_aioはデフォルトで有効になっており、何もしなければ自動的に非同期I/Oが利用されるようになっています。ただし現在のLinuxカーネルでは、Direct I/Oを利用している場合にのみ非同期I/Oが非同期になるという点に注意してください。つまり、非同期I/Oを利用するにはinnodb_flush_method = O_DIRECTの設定が必須となります。

非同期I/Oが有効化されている場合、コネクションスレッドI/O要求をキューイングするのではなく、直接非同期I/Oを発行するようになります。そしてI/Oヘルパースレッドは自分自身がI/O処理を行うのではなく、コネクションスレッドが行った非同期I/Oの完了を待機して後処理を行うという役割に変化します。具体的にはコネクションスレッドがio_submit(2)を行い、I/Oヘルパースレッドがio_getevents(2)を行うようになります。

これがどのようなメリットをもたらすかというと、I/Oヘルパースレッドの数を増やすことなしに一度に処理できるI/O要求の数を増やすことができるということになります。MySQL 5.1+InnoDB Pluginの構成においてinnodb_read_io_threadsとinnodb_write_io_threadsはそれぞれデフォルトで4であり、読み込みで4つ、書き込みで4つまでのI/O要求を一度に処理することができました。しかし、MySQL 5.5においてはOSストレージサブシステムが許す限りのI/O要求を一度に処理することができるようになります。例えばSATA Native Command Queuingでは、デバイスあたり最大32個のI/O要求をキューイングすることが可能となっています。

分析例

前回作成したSystemTapスクリプトを、非同期I/Oをプローブできるように改造しました。スクリプトは本エントリの末尾にあります。JdbcRunnerのTiny TPC-Cを用い、多重度16で負荷をかけたときのI/Oの様子を以下に示します。最初はMySQL 5.0.77のデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH r/s rKB/s wTH w/s wKB/s rTH r/s rKB/s wTH w/s wKB/s File
0 0 0 17 164 629 0 0 0 0 0 0 /u02/mysql/ib_logfile1
8 8 128 7 15 14496 0 0 0 0 0 0 /u02/mysql/ibdata1
17 354 6992 1 160 2656 0 0 0 0 0 0 /u02/mysql/tpcc/customer.ibd
0 0 0 1 5 80 0 0 0 0 0 0 /u02/mysql/tpcc/history.ibd
2 22 464 0 0 0 0 0 0 0 0 0 /u02/mysql/tpcc/item.ibd
6 8 128 1 12 224 0 0 0 0 0 0 /u02/mysql/tpcc/new_orders.ibd
17 58 928 1 56 1008 0 0 0 0 0 0 /u02/mysql/tpcc/order_line.ibd
10 20 320 1 14 240 0 0 0 0 0 0 /u02/mysql/tpcc/orders.ibd
17 1057 20656 1 513 9264 0 0 0 0 0 0 /u02/mysql/tpcc/stock.ibd

rTHはそのファイルを読み込んだスレッドの数、r/sは1秒あたりの読み込み回数、rKB/sは1秒あたりに読み込んだ容量(KB)を表しています。非同期I/Oは行われておらず、全て同期I/Oであることが分かります。テーブルスペースファイルに対して読み込みを行うスレッドは最大でコネクションスレッド数+innodb_read_io_threadsの17個、書き込みを行うスレッドはinnodb_write_io_threadsの1つだけとなっています。

ここでお詫びですが、2009年の記事SSDの真の性能を引き出す MySQL 5.1.38 InnoDB Plugin - SH2の日記において、アーキテクチャの図が間違っていました。innodb_read_io_threadsは先読みだけを行うスレッドであり、通常のページ読み込みはコネクションスレッド自身が行います。コメント欄にあるid:mogwaingさんのご指摘が正しいです。どうもありがとうございました。描き直した図を以下に示します。

先読みというのは、ある程度連続したページの読み込みが要求された際に、フルスキャンだと判断して次に読まれるであろう先の方のデータをあらかじめ読み込んでおく仕組みのことです。詳しくはマニュアル7.7. Changes in the Read-Ahead Algorithmをご覧ください。

次はMySQL 5.1.60+InnoDB Pluginのデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH r/s rKB/s wTH w/s wKB/s rTH r/s rKB/s wTH w/s wKB/s File
0 0 0 17 222 886 0 0 0 0 0 0 /u02/mysql/ib_logfile1
6 6 96 4 12 11440 0 0 0 0 0 0 /u02/mysql/ibdata1
16 458 7328 4 157 2528 0 0 0 0 0 0 /u02/mysql/tpcc/customer.ibd
5 10 160 4 24 480 0 0 0 0 0 0 /u02/mysql/tpcc/history.ibd
6 9 144 0 0 0 0 0 0 0 0 0 /u02/mysql/tpcc/item.ibd
4 6 96 0 0 0 0 0 0 0 0 0 /u02/mysql/tpcc/new_orders.ibd
17 70 1120 4 46 976 0 0 0 0 0 0 /u02/mysql/tpcc/order_line.ibd
14 29 464 3 21 352 0 0 0 0 0 0 /u02/mysql/tpcc/orders.ibd
16 1089 17424 4 385 7104 0 0 0 0 0 0 /u02/mysql/tpcc/stock.ibd

MySQL 5.0.77と比べると、テーブルスペースファイルに対して書き込みを行っているスレッドの数が増えていることが分かります。非同期I/Oは行われていません。

最後に、MySQL 5.5.18のデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH r/s rKB/s wTH w/s wKB/s rTH r/s rKB/s wTH w/s wKB/s File
0 0 0 17 156 651 0 0 0 0 0 0 /u02/mysql/ib_logfile0
16 40 640 7 18 17072 0 0 0 4 62 992 /u02/mysql/ibdata1
16 306 4896 0 0 0 0 0 0 7 145 2320 /u02/mysql/tpcc/customer.ibd
1 1 16 0 0 0 3 5 80 1 1 16 /u02/mysql/tpcc/history.ibd
13 22 352 0 0 0 0 0 0 0 0 0 /u02/mysql/tpcc/item.ibd
9 22 352 0 0 0 0 0 0 2 36 576 /u02/mysql/tpcc/new_orders.ibd
16 80 1280 0 0 0 6 11 176 6 110 1760 /u02/mysql/tpcc/order_line.ibd
11 109 1744 0 0 0 0 0 0 2 12 192 /u02/mysql/tpcc/orders.ibd
16 1178 18848 0 0 0 0 0 0 7 701 11216 /u02/mysql/tpcc/stock.ibd

テーブルスペースファイルに対する書き込みが非同期I/Oになっていることが分かります。また先読みも非同期I/Oになったため、historyテーブルとorder_lineテーブルに対して先読みが行われていることが判別できます。

このように、MySQL 5.5でI/Oの仕組みが変わりました。ただ性能面ではMySQL 5.1+InnoDB Pluginの時点で既にかなり良くなっているため、そこからの改善幅はあまり大きくはないと思います。おそらく、よほど高性能なストレージを持ってこない限りI/O性能の違いは分からないでしょう。

SystemTapスクリプト

今回作成したSystemTapスクリプトを以下に示します。前回と同様Perlスクリプトとのセットになっているため、以下のようにパイプでつなげて実行してください。

# stap io2.stp | perl io2.pl

SystemTapスクリプトです。

global PROCNAME = "mysqld"
global fds probe begin {
printf("start\n")
} probe syscall.read, syscall.pread {
if (execname() == PROCNAME) {
fds[pid(), tid()] = fd
}
} probe syscall.read.return, syscall.pread.return {
if (execname() == PROCNAME && $return != -1) {
printf("read\t%d\t%d\t%d\t%d\n", pid(), tid(), fds[pid(), tid()], $return)
}
} probe syscall.write, syscall.pwrite {
if (execname() == PROCNAME) {
fds[pid(), tid()] = fd
}
} probe syscall.write.return, syscall.pwrite.return {
if (execname() == PROCNAME && $return != -1) {
printf("write\t%d\t%d\t%d\t%d\n", pid(), tid(), fds[pid(), tid()], $return)
}
} probe syscall.io_submit {
if (execname() == PROCNAME) {
for (i = 0; i < nr; i++) {
if ($iocbpp[i]->aio_lio_opcode == 0) {
// IOCB_CMD_PREAD
printf("aio_read\t%d\t%d\t%d\t%d\n",
pid(), tid(), $iocbpp[i]->aio_fildes, $iocbpp[i]->aio_nbytes)
} else if ($iocbpp[i]->aio_lio_opcode == 1) {
// IOCB_CMD_PWRITE
printf("aio_write\t%d\t%d\t%d\t%d\n",
pid(), tid(), $iocbpp[i]->aio_fildes, $iocbpp[i]->aio_nbytes)
}
}
}
} probe syscall.close {
if (execname() == PROCNAME) {
printf("close\t%d\t%d\t%d\n", pid(), tid(), fd)
}
} probe timer.s(1) {
printf("print\n")
}

集計用のPerlスクリプトです。

#!/usr/bin/perl

use strict;
use warnings; my (%file_map, $do_print, %has_io, %rthreads, %rtimes, %rbytes, %wthreads, %wtimes, %wbytes,
%aio_rthreads, %aio_rtimes, %aio_rbytes, %aio_wthreads, %aio_wtimes, %aio_wbytes); $do_print = 0; while (my $line = <>) {
chomp($line);
my ($call, $pid, $tid, $fd, $size) = split(/\t/, $line); if (($call eq 'read') or ($call eq 'write')
or ($call eq 'aio_read') or ($call eq 'aio_write')) { if (!defined($file_map{"$pid,$fd"})) {
$file_map{"$pid,$fd"} = readlink("/proc/$pid/fd/$fd") or next;
} my $file = $file_map{"$pid,$fd"}; if (substr($file, 0, 1) eq '/') {
$do_print = 1;
$has_io{$file} = 1; if ($call eq 'read') {
$rthreads{$file}->{$tid} = 1;
$rtimes{$file}++;
$rbytes{$file} += $size;
} elsif ($call eq 'write') {
$wthreads{$file}->{$tid} = 1;
$wtimes{$file}++;
$wbytes{$file} += $size;
} elsif ($call eq 'aio_read') {
$aio_rthreads{$file}->{$tid} = 1;
$aio_rtimes{$file}++;
$aio_rbytes{$file} += $size;
} elsif ($call eq 'aio_write') {
$aio_wthreads{$file}->{$tid} = 1;
$aio_wtimes{$file}++;
$aio_wbytes{$file} += $size;
}
} } elsif ($call eq 'close') {
if (defined($file_map{"$pid,$fd"})) {
delete($file_map{"$pid,$fd"});
} } elsif ($call eq 'print') {
my ($sec, $min, $hour, $day, $mon, $year) = localtime(); printf "%04d-%02d-%02d %02d:%02d:%02d\n",
$year + 1900, $mon + 1, $day, $hour, $min, $sec; if ($do_print == 1) {
print "-------- Synchronous I/O -------- ------- Asynchronous I/O --------\n";
print "rTH r/s rKB/s wTH w/s wKB/s rTH r/s rKB/s wTH w/s wKB/s File\n"; foreach my $file (sort keys %has_io) {
printf "%3d %5d %6d %3d %5d %6d %3d %5d %6d %3d %5d %6d %s\n",
scalar(keys(%{$rthreads{$file}})),
defined($rtimes{$file}) ? $rtimes{$file} : 0,
defined($rbytes{$file}) ? $rbytes{$file} / 1024 : 0,
scalar(keys(%{$wthreads{$file}})),
defined($wtimes{$file}) ? $wtimes{$file} : 0,
defined($wbytes{$file}) ? $wbytes{$file} / 1024 : 0,
scalar(keys(%{$aio_rthreads{$file}})),
defined($aio_rtimes{$file}) ? $aio_rtimes{$file} : 0,
defined($aio_rbytes{$file}) ? $aio_rbytes{$file} / 1024 : 0,
scalar(keys(%{$aio_wthreads{$file}})),
defined($aio_wtimes{$file}) ? $aio_wtimes{$file} : 0,
defined($aio_wbytes{$file}) ? $aio_wbytes{$file} / 1024 : 0,
$file;
} print "\n"; $do_print = 0;
%has_io = %rthreads = %rtimes = %rbytes = %wthreads = %wtimes = %wbytes = ();
%aio_rthreads = %aio_rtimes = %aio_rbytes = %aio_wthreads = %aio_wtimes = %aio_wbytes = ();
} } else {
print "$call\n";
}
}

SystemTapでMySQL 5.5のDisk I/Oを分析する的更多相关文章

  1. mysql死锁-查询锁表进程-分析锁表原因【转】

    查询锁表进程: 1.查询是否锁表 show OPEN TABLES where In_use > 0;   2.查询进程     show processlist   查询到相对应的进程===然 ...

  2. MySQL CPU %sys 高的案例分析(三)

    [现象] 最近有台服务器晚上CPU告警,系统抓取的故障期间的snapshot显示CPU %sys较高,同时context switch在300K以上. 是否过高的context switch引起的%s ...

  3. MySQL慢日志线上问题分析及功能优化

    本文来源于数据库内核专栏. MySQL慢日志(slow log)是MySQL DBA及其他开发.运维人员需经常关注的一类信息.使用慢日志可找出执行时间较长或未走索引等SQL语句,为进行系统调优提供依据 ...

  4. MYSQL索引结构原理、性能分析与优化

    [转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...

  5. mysql优化(三)–explain分析sql语句执行效率

    mysql优化(三)–explain分析sql语句执行效率 mushu 发布于 11个月前 (06-04) 分类:Mysql 阅读(651) 评论(0) Explain命令在解决数据库性能上是第一推荐 ...

  6. 用systemtap对sysbench IO测试结果的分析1

    http://www.actionsky.com/docs/archives/171  2016年5月6日  黄炎 近期在一些简单的sysbench IO测试中, 遇到了一些不合常识的测试结果. 从结 ...

  7. Mysql视图的作用及其性能分析

    定义:视图是从一个或几个基本表导出的表,它与基本表不同,是一个虚表. 作用: 1.简化操作,不用进行多表查询. 2.当不同种类的用用户共享同一个数据库时,非常灵活,(用户以不同的 方式看待同一数据. ...

  8. MySQL学习系列2--MySQL执行计划分析EXPLAIN

    原文:MySQL学习系列2--MySQL执行计划分析EXPLAIN 1.Explain语法 EXPLAIN SELECT …… 变体:   EXPLAIN EXTENDED SELECT …… 将执行 ...

  9. 一:MySQL数据库的性能的影响分析及其优化

    MySQL数据库的性能的影响分析及其优化 MySQL数据库的性能的影响 一. 服务器的硬件的限制 二. 服务器所使用的操作系统 三. 服务器的所配置的参数设置不同 四. 数据库存储引擎的选择 五. 数 ...

随机推荐

  1. 枚举+贪心 HDOJ 4932 Miaomiao's Geometry

    题目传送门 /* 题意:有n个点,用相同的线段去覆盖,当点在线段的端点才行,还有线段之间不相交 枚举+贪心:有坑点是两个点在同时一条线段的两个端点上,枚举两点之间的距离或者距离一半,尽量往左边放,否则 ...

  2. O - Combinations (组合数学)

    Description Computing the exact number of ways that N things can be taken M at a time can be a great ...

  3. Linux系统下强制踢掉登录用户

    1,利用who命令,找出用户登录的终端代号 who root     pts/0        2017-03-14 22:30 (223.1.1.1) root     pts/1        2 ...

  4. 283 Move Zeroes 移动零

    给定一个数组 nums, 编写一个函数将所有 0 移动到它的末尾,同时保持非零元素的相对顺序.例如, 定义 nums = [0, 1, 0, 3, 12],调用函数之后, nums 应为 [1, 3, ...

  5. JSP&&EL&&JSTL

    JSP = JAVA + HTML + JSP自身的东西 JSP的脚本         <%!   %>        :翻译成Servlet中的成员内容. 定义变量,方法,类. -- ...

  6. Java编程思想读书笔记_第三章

    本章提到的关于==的部分,一个完整的实验如下: class Test { public static void main(String[] args) { Integer i = new Intege ...

  7. [ CQOI 2014 ] 数三角形

    \(\\\) Description 求 \(N\times M\) 的网格图上有多少个格点构成的三角形. 当三点共线的时候我们不认为这是一个三角形. \(n,m\le 10^4\) \(\\\) S ...

  8. Selenium基于Python web自动化基础二 -- 免登录、等待及unittest单元测试框架

    一.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...

  9. Ajax异步刷新省市级联

    省市级联在web前端用户注册使用非常广泛.Ajax异步刷新省市级联.如图:选择不同的区,自动加载相应的街. <TD class=field>位 置:</TD> <TD&g ...

  10. HDU_1864_最大报销额_01背包

    最大报销额 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status ...