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. [转]ASP.NET MVC HtmlHelper扩展之Calendar日期时间选择

    本文转自:http://blog.bossma.cn/asp_net_mvc/asp-net-mvc-htmlhelper-calendar-datetime-select/ 这里我们扩展HtmlHe ...

  2. NPOI 导出Excel 2007, 2013问题

    NPOI默认有两个命名空间HSSF为Excel 2003 版本,若导出2007 及以上后缀名打开excel 则会报错,NPOI也提供了一个07及以上的版本空间XSSF,具体操作列下: NPOI.XSS ...

  3. NHibernate3.2学习笔记

    一.开发环境 数据库:SQLServer2008 编译器:VS2010 .Net版本:.Net Framework 4.0 二.涉及第三方程序集 NHibernate.dll:版本3.2 Iesi.C ...

  4. 重新学习Java——Java基本的程序设计结构(二)

    上一节简单回顾了Java基本的一些程序设计的知识,这一节将继续根据<Java核心技术>这本书,进行这方面知识的复习与探索. 1. 字符串 Java字符串实际上就是Unicode字符序列.例 ...

  5. C语言特殊知识点解析

    1 数组 1.1 概念 数组是指某种数据类型,在内存上按照顺序存储.中括号([ ])是数组的标识,中括号内的数值标识该种数据类型变量的个数,中括号也有取值的作用. 1.2 数组使用 int a[10] ...

  6. 启用adb wifi无线调试功能(无需root)

    1  工具 电脑.手机 2  前提 电脑和手机出于同一网段 3  步骤 以管理员方式打开cmd,运行 adb tcpip 5555(执行tcpip调试模式) adb connect  192.168. ...

  7. Spartan6系列之GTP Transceiver的介绍与使用

    1.       什么是GTP transceiver? GTP transceiver是FPGA里一种线速度达500Mb/sà6.6Gb/s的收发器,利用FPGA内部可编程资源可对其进行灵活地配置, ...

  8. HDU_1520_Anniversary party_树型dp

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1520 Anniversary party Time Limit: 2000/1000 MS (Java ...

  9. PHP封装数据库

    (1)按照步骤封装数据库 ①引入抽象类和抽象方法,即引入模板: ②继承抽象类,注意参数(规定几个就传入几个): ③逐个写入抽象方法,必须一一对应:(抽象方法必须一一引入,否则会报错-->有个抽象 ...

  10. 搭建Cookie池

    很多时候我们在对网站进行数据抓取的时候,可以抓取一部分页面或者接口,这部分可能没有设置登录限制.但是如果要抓取大规模数据的时候,没有登录进行爬取会出现一些弊端.对于一些设置登录限制的页面,无法爬取对于 ...