ruby on rails on mysql チューニング入門

133
佐佐佐佐 (@eccyan) Ruby on Rails on MySQL チチチチチチチチ

Upload: -

Post on 12-May-2015

14.008 views

Category:

Technology


2 download

DESCRIPTION

Rails 3 系+MySQL を利用しているサービス向けに 1. どのようにボトルネックを探すのか 2. どのような設計を行えばいいのか 3. Rails上でどのようなコードを書けばいいのか の3点に絞ってこのプレゼンをみてチューニングを行えるように資料作成を行いました

TRANSCRIPT

Page 1: Ruby on Rails on MySQL チューニング入門

佐藤大資 (@eccyan)

Ruby on Railson MySQL

チューニング入門

Page 2: Ruby on Rails on MySQL チューニング入門

Ruby on Rails を使えば簡単に誰でもWeb サービスを構築する事が出来ます。

Page 3: Ruby on Rails on MySQL チューニング入門

Ruby on Rails を使えば簡単に誰でもWeb サービスを構築する事が出来ます。

しかし、 SQL を理解せずにActive Record を利用していると・・・

Page 4: Ruby on Rails on MySQL チューニング入門
Page 5: Ruby on Rails on MySQL チューニング入門

そこで今回は、

Page 6: Ruby on Rails on MySQL チューニング入門

そこで今回は、

Page 7: Ruby on Rails on MySQL チューニング入門

そこで今回は、

Page 8: Ruby on Rails on MySQL チューニング入門

そこで今回は、

に絞ってチューニングの取り掛り方法をお伝えします。

Page 9: Ruby on Rails on MySQL チューニング入門

ActiveRecord って何?

ActiveRecord って何?

Page 10: Ruby on Rails on MySQL チューニング入門

ActiveRecord って何?• DB テーブルの 1 行が 1 つのクラス

ActiveRecord って何?

Page 11: Ruby on Rails on MySQL チューニング入門

ActiveRecord って何?• DB テーブルの 1 行が 1 つのクラス• 色々な RDBMS を同じソースコードで(MySQL, PostgreSQL, SQLite, SQL Server, Sybase, and Oracle)

ActiveRecord って何?

Page 12: Ruby on Rails on MySQL チューニング入門

ActiveRecord って何?• DB テーブルの 1 行が 1 つのクラス• 色々な RDBMS を同じソースコードで(MySQL, PostgreSQL, SQLite, SQL Server, Sybase, and Oracle)• SQL 再利用の仕組みがある

ActiveRecord って何?

Page 13: Ruby on Rails on MySQL チューニング入門

ストアドプロシージャ?

ActiveRecord って何?

DELIMITER // DROP PROCEDURE IF EXISTS proc1// CREATE PROCEDURE proc1()BEGIN SELECT VERSION();END;//DELIMITER ;

Page 14: Ruby on Rails on MySQL チューニング入門

ストアドプロシージャ?

ActiveRecord って何?

DELIMITER // DROP PROCEDURE IF EXISTS proc1// CREATE PROCEDURE proc1()BEGIN SELECT VERSION();END;//DELIMITER ;負荷大丈夫?

Page 15: Ruby on Rails on MySQL チューニング入門

ストアドプロシージャ?• 処理速度は早いが、負荷集中する• Master-Slave 構成の場合、更新系の

負荷分散が出来ない• RDBMS 毎に構文やノウハウが変わる

ActiveRecord って何?

Page 16: Ruby on Rails on MySQL チューニング入門

オレオレライブラリ?

ActiveRecord って何?    /**     * ページングクエリ (LIMIT 付き ) を実行する     *     * 注 : $sql は SELECT で始めてください。     *     $sql に SELECT SQL_CALC_FOUND_ROWS を入れないでください。     *     * ページングパラメータ ($paging) について     * <pre>     *  page            ページ番号 (1-)  empty や 0 以下の場合は 1 が仮定される     *  pagesize        ページサイズ empty や 0 以下の場合は最後まで     *  no_count        true: カウント不要 , empty: カウント必要     * </pre>     *     * @param stdclass $db      DB ハンドラ     * @param string $sql       SQL     * @param string $params    パラメータ     * @param array $paging     ページングパラメータ     * @param string $key       null: 結果は array of array     *                          $key を指定すると結果をある列の配列で返す     * @return array            array('total' => 全件数 , 'list' => 結果 )     *                          ただし、 $pageing['no_count'] が true の場合は     *                          結果のみを返します。     */    public function doPagingQuery($db, $sql, $params, $paging, $key = null)    {        if (empty($paging['no_count'])) {            $sql = preg_replace('/SELECT/i',                                'SELECT SQL_CALC_FOUND_ROWS', $sql, 1);        }

        // ページング条件        $this->createLimitClause($limitClause, $params, $paging);        $sql .= ' ' . $limitClause;

        // 実行        $list = $db->query($sql, $params)->result_array();        if ($key) {            foreach ($list as &$value) {                $value = $value[$key];            }        }

        if (empty($paging['no_count'])) {            // 全件数を取得する            $totalResult = $db->query('SELECT FOUND_ROWS() AS total')                              ->row_array();            $total = $totalResult['total'];        }    }

Page 17: Ruby on Rails on MySQL チューニング入門

オレオレライブラリ?

ActiveRecord って何?    /**     * ページングクエリ (LIMIT 付き ) を実行する     *     * 注 : $sql は SELECT で始めてください。     *     $sql に SELECT SQL_CALC_FOUND_ROWS を入れないでください。     *     * ページングパラメータ ($paging) について     * <pre>     *  page            ページ番号 (1-)  empty や 0 以下の場合は 1 が仮定される     *  pagesize        ページサイズ empty や 0 以下の場合は最後まで     *  no_count        true: カウント不要 , empty: カウント必要     * </pre>     *     * @param stdclass $db      DB ハンドラ     * @param string $sql       SQL     * @param string $params    パラメータ     * @param array $paging     ページングパラメータ     * @param string $key       null: 結果は array of array     *                          $key を指定すると結果をある列の配列で返す     * @return array            array('total' => 全件数 , 'list' => 結果 )     *                          ただし、 $pageing['no_count'] が true の場合は     *                          結果のみを返します。     */    public function doPagingQuery($db, $sql, $params, $paging, $key = null)    {        if (empty($paging['no_count'])) {            $sql = preg_replace('/SELECT/i',                                'SELECT SQL_CALC_FOUND_ROWS', $sql, 1);        }

        // ページング条件        $this->createLimitClause($limitClause, $params, $paging);        $sql .= ' ' . $limitClause;

        // 実行        $list = $db->query($sql, $params)->result_array();        if ($key) {            foreach ($list as &$value) {                $value = $value[$key];            }        }

        if (empty($paging['no_count'])) {            // 全件数を取得する            $totalResult = $db->query('SELECT FOUND_ROWS() AS total')                              ->row_array();            $total = $totalResult['total'];        }    }

誰が保守するの?

Page 18: Ruby on Rails on MySQL チューニング入門

オレオレライブラリ?• 属人性が高い• OSS に比べて枯れていない• プラグマブルで無く、代替出来ない

ActiveRecord って何?

Page 19: Ruby on Rails on MySQL チューニング入門

AR で再利用可能なコードを書こう• Arel• Relation• Scope

ActiveRecord って何?

Page 20: Ruby on Rails on MySQL チューニング入門

Scope

ActiveRecord って何?

scope :active,  where(active: true) scope :inactive,  where(active: false)

Page 21: Ruby on Rails on MySQL チューニング入門

Scope

ActiveRecord って何?

scope :active,  where(active: true) scope :inactive,  where(active: false)

  scope :adult_categories,  lambda { # 大人向けカテゴリの ID は 1, 5, 6 active.where(category_id: [1, 5, 6]) }

Page 22: Ruby on Rails on MySQL チューニング入門

負荷の少ないクエリとは?

負荷の少ないクエリとは?

Page 23: Ruby on Rails on MySQL チューニング入門

負荷の少ないクエリとは?• 検索処理が軽い

負荷の少ないクエリとは?

Page 24: Ruby on Rails on MySQL チューニング入門

負荷の少ないクエリとは?• 検索処理が軽い• 並び替え処理が軽い

負荷の少ないクエリとは?

Page 25: Ruby on Rails on MySQL チューニング入門

負荷の少ないクエリとは?• 検索処理が軽い• 並び替え処理が軽い• 結合処理が軽い

負荷の少ないクエリとは?

Page 26: Ruby on Rails on MySQL チューニング入門

負荷の少ないクエリとは?• 検索処理が軽い• 並び替え処理が軽い• 結合処理が軽い

EXPLAIN で調べよう

負荷の少ないクエリとは?

Page 27: Ruby on Rails on MySQL チューニング入門

EXPLAIN• インデックスが必要か確認できる• 結合順序が最適か確認できる

負荷の少ないクエリとは?

Page 28: Ruby on Rails on MySQL チューニング入門

EXPLAIN

負荷の少ないクエリとは?

mysql> EXPLAIN SELECT `answers`.`id` AS t0_r0, `answers`.`old_id` AS t0_r1, `answers`.`question_id` AS t0_r2, `answers`.`user_id` AS t0_r3, `answers`.`question_user_id` AS t0_r4, `answers`.`to_user_id` AS t0_r5, `answers`.`parent_id` AS t0_r6, `answers`.`content` AS t0_r7, `answers`.`ng_word` AS t0_r8, `answers`.`check` AS t0_r9, `answers`.`active` AS t0_r10, `answers`.`created_at` AS t0_r11, `answers`.`updated_at` AS t0_r12, `questions`.`id` AS t1_r0, `questions`.`old_id` AS t1_r1, `questions`.`old_random_key` AS t1_r2, `questions`.`parent_category_id` AS t1_r3, `questions`.`category_id` AS t1_r4, `questions`.`user_id` AS t1_r5, `questions`.`to_user_id` AS t1_r6, `questions`.`type_id` AS t1_r7, `questions`.`is_serious` AS t1_r8, `questions`.`content` AS t1_r9, `questions`.`ng_word` AS t1_r10, `questions`.`display` AS t1_r11, `questions`.`check` AS t1_r12, `questions`.`access_count` AS t1_r13, `questions`.`use_image` AS t1_r14, `questions`.`image_url` AS t1_r15, `questions`.`link_url` AS t1_r16, `questions`.`active` AS t1_r17, `questions`.`answer_last_updated_at` AS t1_r18, `questions`.`created_at` AS t1_r19, `questions`.`updated_at` AS t1_r20, `questions`.`last_answer_id` AS t1_r21 FROM `answers` INNER JOIN `users` ON `users`.`id` = `answers`.`user_id` INNER JOIN `questions` ON `questions`.`id` = `answers`.`question_id` WHERE `answers`.`active` = 1 AND (answers.user_id = 1) AND (answers.question_user_id != 1) AND (answers.to_user_id = 1 OR answers.to_user_id is NULL) AND (questions.active = 1) AND (questions.id IN ( SELECT temp_a.question_id FROM answers as temp_a WHERE temp_a.user_id = 1 )) AND (answers.ng_word IN (0, 1)) GROUP BY answers.question_id ORDER BY questions.answer_last_updated_at desc;

Page 29: Ruby on Rails on MySQL チューニング入門

EXPLAIN

負荷の少ないクエリとは?

+----+--------------------+-----------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------+---------+------------------------------------+------+----------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+--------------------+-----------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------+---------+------------------------------------+------+----------------------------------------------+| 1 | PRIMARY | users | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index; Using temporary; Using filesort || 1 | PRIMARY | answers | ref | index_answers_on_question_id_and_user_id_and_created_at,index_answers_on_question_id_and_created_at,index_answers_on_user_id_and_created_at,index_answers_on_to_user_id_and_created_at,index_answers_on_question_user_id_and_created_at | index_answers_on_user_id_and_created_at | 4 | const | 196 | Using where || 1 | PRIMARY | questions | eq_ref | PRIMARY,index_questions_on_id | PRIMARY | 4 | rio_production.answers.question_id | 1 | Using where || 2 | DEPENDENT SUBQUERY | temp_a | index_subquery | index_answers_on_question_id_and_user_id_and_created_at,index_answers_on_question_id_and_created_at,index_answers_on_user_id_and_created_at | index_answers_on_question_id_and_user_id_and_created_at | 8 | func,const | 4 | Using index; Using where |+----+--------------------+-----------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------+---------+------------------------------------+------+----------------------------------------------+4 rows in set (0.01 sec)

Page 30: Ruby on Rails on MySQL チューニング入門

select_type

負荷の少ないクエリとは?

SIMPLE キーまたは JOIN

PRIMARY 外部クエリを示すSUBQUERY 相関関係のないサブクエリ

DEPENDENT SUBQUERY

相関関係のあるサブクエリ

UNCACHEABLE SUBQUERY

実行毎に結果が変わるサブクエリ

DERIVED FROM 句のサブクエリ

Page 31: Ruby on Rails on MySQL チューニング入門

select_type

負荷の少ないクエリとは?

SIMPLE キーまたは JOIN

PRIMARY 外部クエリを示すSUBQUERY 相関関係のないサブクエリ

DEPENDENT SUBQUERY

相関関係のあるサブクエリ

UNCACHEABLE SUBQUERY

実行毎に結果が変わるサブクエリ

DERIVED FROM 句のサブクエリ

Page 32: Ruby on Rails on MySQL チューニング入門

サブクエリ• SQL 内部で更に SQL を発行し取得する事• 遅いのは相関関係のあるサブクエリ

負荷の少ないクエリとは?

Page 33: Ruby on Rails on MySQL チューニング入門

相関サブクエリクエリとサブクエリが相互関係している

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT answers.question_id FROM answers WHERE question_user_id = questions.user_id );

Page 34: Ruby on Rails on MySQL チューニング入門

相関サブクエリクエリとサブクエリが相互関係している

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT answers.question_id FROM answers WHERE question_user_id = questions.user_id );

Page 35: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT question_id FROM answers WHERE question_user_id = 1 );

Page 36: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT question_id FROM answers WHERE question_user_id = 1 );

| id | select_type || 1 | PRIMARY | | 2 | DEPENDENT SUBQUERY |

Page 37: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT question_id FROM answers WHERE question_user_id = 1 );

| id | select_type || 1 | PRIMARY | | 2 | DEPENDENT SUBQUERY |

なんで!?

Page 38: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE questions.id IN (SELECT question_id FROM answers WHERE question_user_id = 1 );

Page 39: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE EXISTS (SELECT 1 FROM answers WHERE question_user_id = 1 AND answers.question_id = questions.id );

Page 40: Ruby on Rails on MySQL チューニング入門

相関サブクエリさて、このクエリは?

負荷の少ないクエリとは?

EXPLAINSELECT * FROM questions WHERE EXISTS (SELECT 1 FROM answers WHERE question_user_id = 1 AND answers.question_id = questions.id );

Page 41: Ruby on Rails on MySQL チューニング入門

サブクエリの問題点• 可読性が減る• アルゴリズムの変動リスク• インデックス等の環境による遅延リスク

負荷の少ないクエリとは?

Page 42: Ruby on Rails on MySQL チューニング入門

type

負荷の少ないクエリとは?

const PRIMARY KEY/UNIQUE を利用するアクセスeq_ref JOIN で PRIMARY KEY/UNIQUE を利用するアクセス

ref PRIMARY KEY/UNIQUE 以外のインデックスで等価検索を利用するアクセス

range インデックスを利用する範囲検索。index インデックス全体をスキャンするアクセス

( フルインデックススキャン )ALL テーブル全体をスキャンするアクセス

( フルテーブルスキャン )

Page 43: Ruby on Rails on MySQL チューニング入門

type

負荷の少ないクエリとは?

const PRIMARY KEY/UNIQUE を利用するアクセスeq_ref JOIN で PRIMARY KEY/UNIQUE を利用するアクセス

ref PRIMARY KEY/UNIQUE 以外のインデックスで等価検索を利用するアクセス

range インデックスを利用する範囲検索。index インデックス全体をスキャンするアクセス

( フルインデックススキャン )ALL テーブル全体をスキャンするアクセス

( フルテーブルスキャン )

Page 44: Ruby on Rails on MySQL チューニング入門

フルインデックススキャン• Index condition pushdown を狙う高頻度の場合• 実行タイミングを変更–更新時に集計やソートを行う–キュー等を利用して非同期化

• 仕様の変更–件数に制限をかける–別テーブルやデータストアに保存

負荷の少ないクエリとは?

Page 45: Ruby on Rails on MySQL チューニング入門

フルテーブルスキャン• インデックスを追加する• Index Condition Pushdown を狙う高頻度の場合• フルインデックススキャンと同じ

負荷の少ないクエリとは?

Page 46: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)• MySQL 5.6 から• マルチカラムインデックスを有効活用す

負荷の少ないクエリとは?

Page 47: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)• MySQL 5.6 から• マルチカラムインデックスを有効活用す

負荷の少ないクエリとは?

Page 48: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使わない場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 49: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使わない場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 50: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使わない場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 51: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使う場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 52: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使う場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 53: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使う場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 54: Ruby on Rails on MySQL チューニング入門

Index Condition Pushdown(ICP)ICP を使う場合 (Sex = 1, 10 ≦ Age < 20)

負荷の少ないクエリとは?

Sex Age1 15

1 30

2 18

1 40

2 27

2 13

1 14

1 24

1 50

Id Sex Age

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Page 55: Ruby on Rails on MySQL チューニング入門

Extra

負荷の少ないクエリとは?

Using where WHERE の検索条件がインデックスだけでは解決出来ない。

Using index インデックスだけで条件を解決できる。Using filesort クイックソートでソートを行っている。

Using temporary 実行にテンポラリテーブルが必要。

Page 56: Ruby on Rails on MySQL チューニング入門

Extra

負荷の少ないクエリとは?

Using where WHERE の検索条件がインデックスだけでは解決出来ない。

Using index インデックスだけで条件を解決できる。Using filesort クイックソートでソートを行っている。

Using temporary 実行にテンポラリテーブルが必要。

Page 57: Ruby on Rails on MySQL チューニング入門

Using filesort• インデックスを追加する• ソート条件を 1 テーブルに集中させる• ソート実行を早めにさせる

負荷の少ないクエリとは?

Page 58: Ruby on Rails on MySQL チューニング入門

Using filesortこのクエリの場合は?

負荷の少ないクエリとは?

EXPLAIN  SELECT * FROM users    WHERE active = 1  ORDER BY type_id;

+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 191660 | Using where; Using filesort |+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+1 row in set (0.00 sec)

Page 59: Ruby on Rails on MySQL チューニング入門

Using filesortこのクエリの場合は?

負荷の少ないクエリとは?

mysql> SHOW CREATE TABLE users \G;*************************** 1. row *************************** Table: usersCreate Table: CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

・・・

PRIMARY KEY (`id`), KEY `index_users_on_old_id` (`old_id`), KEY `index_users_on_provider_and_uid` (`provider`,`uid`), KEY `index_users_on_old_random_key` (`old_random_key`)) ENGINE=InnoDB AUTO_INCREMENT=174849 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci1 row in set (0.00 sec)

ERROR:No query specified

Page 60: Ruby on Rails on MySQL チューニング入門

Using filesortこのクエリの場合は?

負荷の少ないクエリとは?

EXPLAIN  SELECT * FROM users    WHERE active = 1  ORDER BY type_id;

Page 61: Ruby on Rails on MySQL チューニング入門

Using temporary• GROUP BY / DISTINCT を見直す• 行数を考慮して JOIN 条件の見直す• サブクエリを検討する• テーブル / カラム追加を検討する

負荷の少ないクエリとは?

Page 62: Ruby on Rails on MySQL チューニング入門

Using temporary• GROUP BY / DISTINCT を見直す• 行数を考慮して JOIN 条件の見直す• サブクエリを検討する• テーブル / カラム追加を検討する

負荷の少ないクエリとは?

Page 63: Ruby on Rails on MySQL チューニング入門

高速化の仕組み• インデックス• パーティション

高速化の仕組み

Page 64: Ruby on Rails on MySQL チューニング入門

インデックスとは?• 辞書の索引を付けること• B-tree を利用している• 正確には最下層にデータ保持する

B+tree

高速化の仕組み

Page 65: Ruby on Rails on MySQL チューニング入門

B-tree とは ?定義• 根は葉であるか 2~m の子をもつ• 根・葉以外の節は m/2 以上の子をもつ• 根から葉までの深さが等しい

高速化の仕組み

Page 66: Ruby on Rails on MySQL チューニング入門

B-tree とは ?探索方法• 最左値より小さければ最左部分木へ進む• 最左値より大きければ次の値と比較し、

小さければ最左の次の部分木へ進む• 上記を反復する

高速化の仕組み

Page 67: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

10

Page 68: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

10

5

Page 69: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

5 10

2020

Page 70: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

205

10

47

Page 71: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

20 475

10

5050

Page 72: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

505

10 47

20

7

Page 73: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

505 7

10 47

20

32

Page 74: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

9505 7

10 47

20 329

Page 75: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

50

10 47

20 32

5 9

7

高速化の仕組み

Page 76: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

50

10 47

20 32

5 9

7

高速化の仕組み

Page 77: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

50

10 47

20 32

5 9

7

高速化の仕組み

Page 78: Ruby on Rails on MySQL チューニング入門

B-tree とは ?

高速化の仕組み

5 9 20 32 50

7

10

47

Page 79: Ruby on Rails on MySQL チューニング入門

MySQL(InnoDB) のインデックス探索• リーフページが大量の場合は打ち切り

ルートから探索を行う• 9ページまで読み打ち切る (MySQL

5.6.4)• リーフページサイズは 16KB

高速化の仕組み

Page 80: Ruby on Rails on MySQL チューニング入門

パーティションとは?• 水平分割(行による分割)

高速化の仕組み

Page 81: Ruby on Rails on MySQL チューニング入門

リスク• 分割方法によっては速度が遅くなる• 既存のクエリに分割キー条件を

追加しなければならない場合がある• 分割キーを PK にする必要がある• パーティション変更はサービス停止が必

高速化の仕組み

Page 82: Ruby on Rails on MySQL チューニング入門

リターン• メモリへの読み込みが早くなる• 上記に伴いディスクアクセスも少なくな

る• 可用性の高い設計になる

高速化の仕組み

Page 83: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった• 元々 created_at で分割をしていた• クエリに時間による制限や

ソート以外の条件が多かった• 上記の理由で ID による分割に変更

高速化の仕組み

Page 84: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった

高速化の仕組み

EXPLAIN PARTITIONSSELECT `questions`.* FROM `questions` WHERE `questions`.`active` = 1 ORDER BY created_atLIMIT 10 OFFSET 0 \G;

Page 85: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった

高速化の仕組み

id: 1 select_type: SIMPLE table: questions partitions: p0,p1,p2,p3,…,p509,p510,p511 type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1179993 Extra: Using where; Using filesort

Page 86: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった• ID によるソートを行うように• 取得時に ID の範囲を指定するように

高速化の仕組み

Page 87: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった

高速化の仕組み

EXPLAIN PARTITIONSSELECT `questions`.* FROM `questions` WHERE `questions`.`active` = 1 AND (id > 3) AND (id < 14) LIMIT 10 OFFSET 0 \G;

Page 88: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった

高速化の仕組み

EXPLAIN PARTITIONSSELECT `questions`.* FROM `questions` WHERE `questions`.`active` = 1 AND (id > 3) AND (id < 14) LIMIT 10 OFFSET 0 \G;

           id: 1 select_type: SIMPLE table: questions partitions: p4,p5,p6,p7,p8,p9,p10,p11,p12,p13 type: rangepossible_keys: PRIMARY,index_questions_on_id key: PRIMARY key_len: 4 ref: NULL rows: 9 Extra: Using where1 row in set (0.00 sec)

Page 89: Ruby on Rails on MySQL チューニング入門

テルミーの例新着が遅くなった

高速化の仕組み

           id: 1 select_type: SIMPLE table: questions partitions: p4,p5,p6,p7,p8,p9,p10,p11,p12,p13 type: rangepossible_keys: PRIMARY,index_questions_on_id key: PRIMARY key_len: 4 ref: NULL rows: 9 Extra: Using where1 row in set (0.00 sec)

Page 90: Ruby on Rails on MySQL チューニング入門

Rails でのチューニング• ActiveRecord が生成する SQL を知る

Rails でのチューニング

Page 91: Ruby on Rails on MySQL チューニング入門

Rails でのチューニング• ActiveRecord が生成する SQL を知る• Eager Loading を利用する

Rails でのチューニング

Page 92: Ruby on Rails on MySQL チューニング入門

Rails でのチューニング• ActiveRecord が生成する SQL を知る• Eager Loading を利用する• 更新時コールバックでの集計• バックグラウンドでの集計

Rails でのチューニング

Page 93: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知る

Rails でのチューニング

Page 94: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るto_sql

Rails でのチューニング

pry(main)> Question.uniq.to_sql

Page 95: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るto_sql

Rails でのチューニング

pry(main)> Question.uniq.to_sql=> "SELECT DISTINCT `questions`.* FROM `questions` "

Page 96: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るexplain

Rails でのチューニング

pry(main)> Question.uniq.explain

Page 97: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るexplain

Rails でのチューニング

pry(main)> Question.uniq.explain Question Load (19503.4ms) SELECT DISTINCT `questions`.* FROM `questions` EXPLAIN (9.4ms) EXPLAIN SELECT DISTINCT `questions`.* FROM `questions`=> "EXPLAIN for: SELECT DISTINCT `questions`.* FROM `questions` \n+----+-------------+-----------+------+---------------+------+---------+------+--------+-------+\n| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |\n+----+-------------+-----------+------+---------------+------+---------+------+--------+-------+\n| 1 | SIMPLE | questions | ALL | NULL | NULL | NULL | NULL | 195195 | |\n+----+-------------+-----------+------+---------------+------+---------+------+--------+-------+\n1 row in set (0.01 sec)\n"

Page 98: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知る実装を行う前に pry 等で• 生成されるクエリ (to_sql)• 実行計画 (explain)を確認する。

Rails でのチューニング

Page 99: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るNewRelic• Transaction tracing• Slow SQLの設定をオンにすれば

Rails でのチューニング

Page 100: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るNewRelic

Rails でのチューニング

Page 101: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るrack-mini-profiler• https://github.com/MiniProfiler/rack-

mini-profiler

• 環境毎に gem を入れるだけで簡単

Rails でのチューニング

Page 102: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るrack-mini-profiler

Rails でのチューニング

Page 103: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るmysqldumpslow• スローログを合計時間で集計してくれる• ログがあればローカルで実行できる• gzip も読み込んでくれる• MySQL デフォルト

Rails でのチューニング

Page 104: Ruby on Rails on MySQL チューニング入門

ActiveRecord が生成する SQL を知るmysqldumpslow

Rails でのチューニング

$ mysqldumpslow -t 5 -s t mysql-slow.log*

Reading mysql slow query log from mysql-slow.log

Count: 14591 Time=1.57s (22851s) Lock=0.00s (5s) Rows=10.0 (145910), rio_slave[rio_slave]@8hosts SELECT `questions`.* FROM `questions` WHERE `questions`.`display` = 'S' AND `questions`.`to_user_id` IS NULL AND `questions`.`active` = N AND (answer_last_updated_at != created_at) AND (questions.created_at >= 'S') AND (ng_word IN (N, N)) ORDER BY answer_last_updated_at desc LIMIT N OFFSET N

Page 105: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Page 106: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用するEager Loading とは?• 「積極的に読み込む」• 先にデータを取得しておくこと• インスタンス変数による自動キャッシュ

Rails でのチューニング

Page 107: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Question.limit(10).each do |q| p “#{q.user} posted #{q.content}”end

Page 108: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Question.limit(10).each do |q| p “#{q.user} posted #{q.content}”end Question Load (9.8ms) SELECT `questions`.* FROM `questions` User Load (5.2ms) SELECT `users`.* FROM `users` WHERE User Load (4.1ms) SELECT `users`.* FROM `users` WHERE User Load (4.8ms) SELECT `users`.* FROM `users` WHERE User Load (7.9ms) SELECT `users`.* FROM `users` WHERE User Load (5.7ms) SELECT `users`.* FROM `users` WHERE User Load (5.4ms) SELECT `users`.* FROM `users` WHERE User Load (4.5ms) SELECT `users`.* FROM `users` WHERE User Load (4.4ms) SELECT `users`.* FROM `users` WHERE User Load (4.2ms) SELECT `users`.* FROM `users` WHERE User Load (4.4ms) SELECT `users`.* FROM `users` WHERE

Page 109: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Question.includes(:user).limit(10).each do |q| p “#{q.user} posted #{q.content}”end Question Load (9.8ms) SELECT `questions`.* FROM `questions` User Load (5.2ms) SELECT `users`.* FROM `users` WHERE User Load (4.1ms) SELECT `users`.* FROM `users` WHERE User Load (4.8ms) SELECT `users`.* FROM `users` WHERE User Load (7.9ms) SELECT `users`.* FROM `users` WHERE User Load (5.7ms) SELECT `users`.* FROM `users` WHERE User Load (5.4ms) SELECT `users`.* FROM `users` WHERE User Load (4.5ms) SELECT `users`.* FROM `users` WHERE User Load (4.4ms) SELECT `users`.* FROM `users` WHERE User Load (4.2ms) SELECT `users`.* FROM `users` WHERE User Load (4.4ms) SELECT `users`.* FROM `users` WHERE

Page 110: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Question.includes(:user).limit(10).each do |q| p “#{q.user} posted #{q.content}”end Question Load (6.9ms) SELECT `questions`.* FROM `questions` User Load (5.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (724, 1402, 2277, 3154, 3696, 4180, 4551, 5375, 6090, 6890)

Page 111: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

Question.includes(:user).limit(10).each do |q| p “#{q.user} posted #{q.content}”end Question Load (6.9ms) SELECT `questions`.* FROM `questions` User Load (5.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (724, 1402, 2277, 3154, 3696, 4180, 4551, 5375, 6090, 6890)

Page 112: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

# 指定リレーショナルオブジェクトを先読みQuestion.includes(:user) Question.includes(:user, :category)

# 指定リレーションオブジェクトのフィールドを先読みQuestion.includes(category: [:parent_category])Question.includes(category: [:parent_category, :answer_good_logs])

# 更に入れ子にも出来ます・・・

Page 113: Ruby on Rails on MySQL チューニング入門

Eager Loading を利用する

Rails でのチューニング

# 指定リレーショナルオブジェクトを先読みQuestion.includes(:user) Question.includes(:user, :category)

# 指定リレーションオブジェクトのフィールドを先読みQuestion.includes(category: [:parent_category])Question.includes(category: [:parent_category, :answer_good_logs])

# 更に入れ子にも出来ます・・・

Page 114: Ruby on Rails on MySQL チューニング入門

Eager Loading チューニングのフロー1. キャッシュを切る2. rack-mini-profiler で大量発行 SQL を探

す3. 対象のコントローラを探す4. Includes を追加する5. rack-mini-profiler で確認

Rails でのチューニング

Page 115: Ruby on Rails on MySQL チューニング入門

Eager Loading チューニングのフロースローログに乗らない細かい SQL も大きな負荷削減になることが結構有ります

Rails でのチューニング

Page 116: Ruby on Rails on MySQL チューニング入門

インスタンス変数によるキャッシュ

Rails でのチューニング

Page 117: Ruby on Rails on MySQL チューニング入門

インスタンス変数によるキャッシュ1接続内に複数回 SQL が発行される場合

Rails でのチューニング

Question.first.tap do |q| 10.times { "answers count is #{q.answers.count}" }end Question Load (8.0ms) SELECT `questions`.* FROM `questions` (4.2ms) SELECT COUNT(*) FROM `answers` WHERE (3.6ms) SELECT COUNT(*) FROM `answers` WHERE (5.3ms) SELECT COUNT(*) FROM `answers` WHERE (3.8ms) SELECT COUNT(*) FROM `answers` WHERE (3.8ms) SELECT COUNT(*) FROM `answers` WHERE (5.7ms) SELECT COUNT(*) FROM `answers` WHERE (3.8ms) SELECT COUNT(*) FROM `answers` WHERE (3.4ms) SELECT COUNT(*) FROM `answers` WHERE (3.8ms) SELECT COUNT(*) FROM `answers` WHERE (5.4ms) SELECT COUNT(*) FROM `answers` WHERE

Page 118: Ruby on Rails on MySQL チューニング入門

インスタンス変数によるキャッシュ1接続内に複数回 SQL が発行される場合

Rails でのチューニング

class Question < ActiveRecord::Base

・・・

def answers_count @answers_count ||= self.answers.count end

・・・

end

Page 119: Ruby on Rails on MySQL チューニング入門

インスタンス変数によるキャッシュ1接続内に複数回 SQL が発行される場合

Rails でのチューニング

Question.first.tap do |q| 10.times { "answers count is #{q.answers_count}" }end Question Load (5.1ms) SELECT `questions`.* FROM `questions` (3.6ms) SELECT COUNT(*) FROM `answers` WHERE

Page 120: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計• カウント集計

• 最後に更新のあった解答

Rails でのチューニング

Page 121: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計• カウント集計–更新時に親へカウントアップ /ダウン

• 最後に更新のあった投稿–更新時に親へ保存

Rails でのチューニング

Page 122: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Master)

name

email

Posts (Transaction)

user_id

content

…1:n

Page 123: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Master)

name

email

Posts (Transaction)

user_id

content

UserPostStatus (Transaction)

user_id

last_post_id

posted_count

1:n

Page 124: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Master)

name

email

Posts (Transaction)

user_id

content

UserPostStatus (Transaction)

user_id

last_post_id

posted_count

1:1

1:n

Page 125: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Master)

name

email

Posts (Transaction)

user_id

content

UserPostStatus (Transaction)

user_id

last_post_id

posted_count

1:1 1:1

1:n

Page 126: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Master)

name

email

Posts (Transaction)

user_id

content

UserPostStatus (Transaction)

user_id

last_post_id

posted_count

…都度カウントアップ /カウントダウン都度更新

1:1 1:1

1:n

Page 127: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様なテーブル設計の場合なの?

Rails でのチューニング

Users (Transaction)

name

email

last_post_id

posted_count

1:1 or nPosts (Transaction)

user_id

content

Page 128: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様に更新処理を行うのか?

Rails でのチューニング

class User < ActiveRecord::Base attr_accessible :last_post_id belongs_to :last_post, class_name: Post.to_s, foreign_key: :last_answer_id

def update_last_post(post = nil) new_last_post = post || Post.last_post(self.id).first self.update_column :last_post_id, new_last_post.try(:id) end

・・・

end

Page 129: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様に更新処理を行うのか?

Rails でのチューニング

class User < ActiveRecord::Base attr_accessible :last_post_id belongs_to :last_post, class_name: Post.to_s, foreign_key: :last_answer_id

def update_last_post(post = nil) new_last_post = post || Post.last_post(self.id).first self.update_column :last_post_id, new_last_post.try(:id) end

・・・

end

Page 130: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計どの様に更新処理を行うのか?

Rails でのチューニング

class Post < ActiveRecord::Base

after_save { user.update_last_post(self) }

・・・

end

Page 131: Ruby on Rails on MySQL チューニング入門

更新時コールバックでの集計注意点• 冗長性が高いものであること• 再集計が可能であること

Rails でのチューニング

Page 132: Ruby on Rails on MySQL チューニング入門

総括• データストアの知識が重要• プロファイルでボトルネックを探せるか• メンテナンス性とトレードオフにならない

ように設計• データベース設計を変更する勇気を持とう

総括

Page 133: Ruby on Rails on MySQL チューニング入門

参考文献漢のコンピュータ道http://nippondanji.blogspot.jp/2009/03/mysqlexplain.htmlhttp://nippondanji.blogspot.jp/2009/03/using-filesort.htmlhttp://nippondanji.blogspot.jp/2012/10/mysql-56.htmlMySQL Practive Wikihttp://www.mysqlpracticewiki.com/index.php/Extra_fieldSH2 の日記http://d.hatena.ne.jp/sh2/20111217十番目のムーサhttp://d.hatena.ne.jp/psappho/20111101/1320152348 MySQL SQL オプティマイザのコスト計算アルゴリズムhttp://dbstudy.info/files/20120310/mysql_costcalc.pdfMySQL Reference Manualshttp://dev.mysql.com/doc/

総括