Slide 1

Slide 1 text

理屈で考える、 データベースのチューニング 第1回 Oracle DBのさらなるパフォーマンスチューニングに必要な基礎知識 2020年09月30日 ラクーンホールディングス 羽山 純 1

Slide 2

Slide 2 text

はじめに ● DBの内部構造を知りましょう ● 処理速度・処理時間を具体的な数値でイメージしましょう ○ その数値は多少間違ってても問題ありません ● 各処理を分析すると問題の原因を把握できます ● 分析のためには内部構造の知識が生きてきます ● 問題の原因が分かれば解決策を考えられます ○ やみくもに解決策を考えるのは風邪で胃薬を飲むようなもの ○ DBの中でなにが起きているのか理解しましょう ● 計測結果を疑う、みかけの数値に騙されないこと ○ パフォーマンス測定では内部構造を知らないと騙されやすい ○ 2回目以降の実行は速いなど問題の再現性が低いことがある 2

Slide 3

Slide 3 text

アジェンダ ● DBの性能を知る ● 内部構造を知る ● データブロックを理解する ● ROWIDとは何か ● MySQLとの比較 ● レコードサイズとパフォーマンス ● レコードの分散 ● 10年後を考える 3

Slide 4

Slide 4 text

DBの性能を知る 4

Slide 5

Slide 5 text

ストレージの性能を知る ● DB性能で重要なのがストレージのランダムI/O性能 ● ここでは 4,000 IOPS(I/O per Second)として考える (※ 性能としては少し遅めの水準) ● 1分間に行えるI/Oが24万回、これが絶対的な限界値 ● 具体的な数値でイメージすることが大切 4,000回/秒 (= 24万回/分) 5

Slide 6

Slide 6 text

4,000 IOPS とはどの程度の性能か ● AWS RDS で Multi-AZ 4,000 IOPS プロビジョンドIOPS ストレージを割り当てると月10万円以上かかる ➢ 価格から分かるように低すぎる性能ではない ● ストレージI/O 1回を8KBとする(≒ランダムアクセス) 1秒間 ⇒ 8KB × 4,000回 ≒ 32MB/秒 1分間 ⇒ 32MB × 60秒 = 1,920MB ≒ 2GB/分 プロビジョンド IOPS ストレージ 4,000 IOPS Multi-AZ アジアパシフィック(東京) Multi-AZ プロビジョンド IOPS 料金 ⇒ 毎月 0.24USD/IOPS 4,000 プロビジョンドIOPS x 0.24 USD = 960.00 USD/月 2020年9月現在 6

Slide 7

Slide 7 text

4,000 IOPS / 8KBブロックの読み込み速度 1秒間 8KB × 4,000回 ≒ 32MB 1分間 32MB × 60秒 ≒ 2GB 7

Slide 8

Slide 8 text

ストレージ性能と最適化 ● 2GBのテーブルを読み込むには1分かかる、意外と遅い ● 実際の読み込みでは様々な最適化がある ○ DBバッファキャッシュ ○ マルチブロックリード(※後ほど説明) ● DBバッファキャッシュはメモリ上に確保されるキャッシュ ○ メモリ読み込みはストレージよりも圧倒的に速い 読み込み済のデータを メモリに保持する ストレージ DBバッファキャッシュ 8

Slide 9

Slide 9 text

DBバッファキャッシュ ● OLTP(オンライントランザクション)で利用されるDBでは DBバッファキャッシュのヒット率99%以上が普通 ● ヒット率が高いならストレージ性能は重要ではない? ⇒ NO、たった1%のストレージ読込がボトルネックになる 読込データ量の割合 ストレージ読込 1% DBバッファ キャッシュ 99% 9

Slide 10

Slide 10 text

DBバッファキャッシュ ● 読み込み速度が「メモリ : ストレージ = 1,000 : 1」とする メモリ読込で1秒の処理 ⇒ 以下の条件でかかる時間は? ○ キャッシュヒット率 100% (⇒ メモリ 100%) ⇒ 1秒 ○ キャッシュヒット率 0% (⇒ ストレージ 100%) ⇒ 1秒 × 1,000倍 = 1,000秒 ○ キャッシュヒット率 99% (⇒ メモリ99% ストレージ1%) ⇒ 0.99秒 + (0.01秒 × 1,000倍) = 10.99秒 ≒ 11秒 難しいなぁ・・・ 10

Slide 11

Slide 11 text

読み込み速度比 ⇒ メモリ : ストレージ = 1,000 : 1 キャッシュヒット率 ⇒ 99% 読込時間と所要時間の関係 読込データ量の割合 ストレージ 1% DBバッファ キャッシュ 99% 所要時間の割合 ストレージ 91% DBバッファキャッシュ 9% 11

Slide 12

Slide 12 text

● 汎用目的のDBは「I/Oバウンド」な状態が多い ● CPUは休みながらストレージ読み込みを待って処理する ● I/Oバウンドならば「ストレージ性能」≒「DBの処理性能」 I/Oバウンド ストレージは遅い CPUは速いがデータがこない ストレージを待ちながら処理 ストレージ性能 ≒ 処理速度 12

Slide 13

Slide 13 text

CPUバウンド ● 「CPUバウンド」になりやすいDBは ○ データがすべてメモリに収まる(メモリ多い、データ少ない) ○ 利用用途が限定的でキャッシュヒット率が高い ● 結果としてパフォーマンス的な課題がさほどない メモリは速い CPUの速度に負けないスピードで データが来る 13

Slide 14

Slide 14 text

CPU性能について ● 大抵のDBはI/Oバウンド、今回はI/OバウンドなDBを扱う ● I/Oバウンドの場合、 CPU性能はDBパフォーマンスにさほど影響しない CPU性能が良くてもヒマで寝てるだけ 14

Slide 15

Slide 15 text

内部構造を知る 15

Slide 16

Slide 16 text

なぜDBの内部構造の理解が必要か? ● 内部構造を知らなくてもSQLを知ればデータを取り出せる ● アプリケーションはSQLを介して、DBの内部構造と疎結合 ● 開発者はSQLを介して、DBの内部構造と疎結合 ● 開発にsqlite、本番でMySQLなどDB実装の切り替えも可能 だけれども 16

Slide 17

Slide 17 text

● パフォーマンスの領域では内部構造の理解が不可欠 ● DBは魔法の箱ではない、無理な処理をすれば時間がかかる ● 例えば、順不同に並ぶ英和辞典で単語を探す ○ 単語を見つけるまで最初のページから全て探す必要がある ○ 荒唐無稽に聞こえるがDBでは意外とやりがち ➢ 実はテーブルフルスキャンしてるSQL ○ 見出し語のアルファベット順に並んでれば一瞬で探せる ➢ DBでもインデックスがあれば速くなる ○ DBの立場になって、その処理に無理がないか考える なぜDBの内部構造の理解が必要か? 英和辞典 17 順不同に並ぶ英和辞書

Slide 18

Slide 18 text

なぜDBの内部構造の理解が必要か? ● 人は無意識に道具の内部構造を考えて最適な利用をする ○ DBではこの仕事をオプティマイザが担当 ● フランスの旅行ガイドブックから「ラメールプラール」とい うオムレツ屋を探す 1. モンサンミシェル⇒レストランとたどって探す 2. 巻末索引から探す 3. 最初から最後まで全ページ探す 1. 2. 索引 巻末索引で探す 地域を絞って探す 18

Slide 19

Slide 19 text

● 旅行ガイドブックの構造はDBの構造に近い ○ 本編・観光情報(⇒テーブルのデータ) ○ 本編の掲載順番は決まりがない(⇒レコードの保存順) ○ 名称から探すために巻末索引がある(⇒インデックス) ○ 巻末索引には本編への参照がある(⇒ROWID ※後述) なぜDBの内部構造の理解が必要か? 索引 巻末に索引がある 観光情報は順不同 19

Slide 20

Slide 20 text

なぜDBの内部構造の理解が必要か? ● 図書館で本を探す場合 ○ 蔵書のエリア配置・並び順を把握していて最短でたどり着く ○ 情報端末で蔵書検索をする ● たどり着き方は複数あり、どれが最適かは状況により変わる ● DBは最適な方法を選択(=オプティマイザ)してくれる ○ 開発者はDBの立場になって「どう処理するか?」を考える ● DBとして最適は全体の最適ではない、開発者の手助けが必要 ○ そもそも必要のない処理である可能性 ○ さらに効率的な絞り込み条件を追加できる ○ 運用を変更したら効率的な処理にできる ○ バッチ処理だから処理速度が遅くてもよい ● DBの内部構造を知らないと開発者はうまく手助けできない 20

Slide 21

Slide 21 text

データブロックを理解する 21

Slide 22

Slide 22 text

テーブルのデータは、ストレージの表領域のセグメントの エクステントのデータブロックに保存される ⇒知らなくてOK Oracle DBのデータ構造 表領域 セグメント 表領域 セグメント セグメント セグメント セグメント セグメント セグメント セグメント エクステント エクステント エクステント エクステント エクステント エクステント データブロック ストレージ 22

Slide 23

Slide 23 text

● 覚えてもらいたいのは「データブロック」だけ ● データブロックのサイズは選択できる ○ 2KB, 4KB, 8KB, 16KB, 32KBなど ● テーブルやインデックスの実データが入っている ● データブロックがOracleにおけるI/Oの最小単位 ● DBバッファキャッシュへのキャッシュもデータブロック単位 ● 今回は8KBとする データブロック データブロック レコードデータ 空き領域 23

Slide 24

Slide 24 text

● テーブルの各レコードはデータブロックに保存される ● データブロックはCREATE TABLE時に割り当てられ、空き データブロックがなくなると、再度割り当てられる ● 1つのデータブロックには複数レコード入る データブロック データブロック ID Name Price Updated 1 AA 100 2012-10-04 2 BB 53 2014-12-01 3 CC 500 2018-06-12 4 DD 12 2019-03-28 5 EE 212 2020-05-03 ... ... ... ... 24

Slide 25

Slide 25 text

● データブロックのうち大部分はレコードデータに利用 ● カラム型や保存するデータによって消費サイズが異なる ● VARCHAR2はバイト列分を消費、数値型も可変長 ● 数値カラムを9個持つテーブルで1行がおおよそ36byteなら ○ 8KBのデータブロック1つには200レコード以上入る ○ 1回のI/Oで200レコードも取得できる データブロック データブロック 25 数値カラムを9個ほど持つ テーブルなら1ブロックに 200レコード程度入る

Slide 26

Slide 26 text

● 1ブロックに200レコード入るテーブルを考える ● 200万レコード取得する場合、200万 ÷ 200 = 1万ブロック ● 4,000 IOPS なので、10,000 ブロック ÷ 4,000 IOPS = 2.5秒 ● (うまくいけば)2.5秒で200万レコード取得できそう ● 具体的な数値で考えることが重要、おおよそで問題なし ○ ただしマルチブロックリードならもっと速い データブロック データブロック 26 1ブロックに200 レコード入るとする

Slide 27

Slide 27 text

データブロックに関する問題 Q: 注文でレコードが挿入される「注文テーブル」がある。 「2019年5月の注文」と「全期間の電化製品ジャンルの注文」 が共に約200万レコードだったとすると、それぞれの条件で 200万レコード取得にかかる時間に差はあるか? (※「注文日」及び「ジャンル」にはインデックスがあるものとする) 1. 「2019年5月の注文」の方が早く取得可能 2. 「全期間の電化製品ジャンルの注文」の方が早く取得可能 3. 両方ともほとんど差はない 27

Slide 28

Slide 28 text

データブロックに関する問題 A: 「2019年5月の注文」の方が早く取得可能 これはなぜか? ● レコード挿入は空きデータブロックに対して連続で行われる ● その結果、同一タイミングで挿入されたレコードは同一の データブロックに含まれやすい ● 「注文日」と「レコード挿入タイミング」には相関があり 同一ブロック内に条件に当てはまるデータが連続しやすい ○ データブロック1つに200レコード含むなら、その大半が条件に 当てはまるレコードとなりやすい ○ 200万 ÷ 200 = 1万ブロック の取得で済む ○ 分散していると200万ブロック取得が必要 28

Slide 29

Slide 29 text

データブロックの分散 ● ジャンルで絞り込む場合は、テーブル全体にバラバラに レコードがちらばっている ● 1データブロック取得しても、必要なレコードは数行しか含ま れない、8KBのブロックのうち大半が必要のないレコード 注文日での絞り込み 必要なレコードが1つのブロック内に 連続している 200レコード利用 200レコード利用 200レコード利用 ジャンルでの絞り込み 必要なレコードが様々なブロックに 分散している それぞれ数レコード利用 29

Slide 30

Slide 30 text

● 使用可能ブロックは挿入可能、使用済みブロックは挿入不可 ● 空き領域が残り少なくなると使用済みブロックに変化する ● DELETEなど、十分な空き領域ができると使用可能へ戻る 同一ブロックにデータが集まる理由 データブロック データブロック 空き領域が十分ある場合は 新しいレコードを入れる 空き領域が少ない場合は 新しいレコードを入れない 空き領域 空き領域 30

Slide 31

Slide 31 text

使用可能/使用済みの状態遷移 使用可能 31 使用可能 使用済み INSERT 使用済み INSERT UPDATE DELETE 使用済み DELETE INSERT 使用可能 INSERT 空き領域が一定未満に なるまでは使用可能 INSERT INSERT INSERT INSERT INSERT 十分空きができるま では使用済みのまま ① ② ③ ④ ⑤ ⑥

Slide 32

Slide 32 text

データブロックサイズの考察 ● データが分散している場合 ○ 各データブロックに必要なレコードが数行しか含まれない ○ 沢山のデータブロックを取得する必要がある ○ それならば、ブロックサイズを小さくした方が有利? ➢ 大抵の場合はNO、なぜならば ● 一般的なDBストレージ(SAS/iSCSI/FCなど)はブロックサ イズを変えてもIOPSはさほど変化しない ○ 4KB ⇒ 2KB IOPSにさほど影響なし ○ 8KB ⇒ 16KB IOPSが1~2割程度の減少(あくまで一例) 32

Slide 33

Slide 33 text

ブロックサイズとIOPSとスループット ● 厳密な数値は考えず、おおまかに理解する ● ブロックサイズが 8KB ⇒ 16KB に変化した場合、 IOPSは2割減少すると考える 4,000 IOPS ⇒ 3,200 IOPS ● スループットはブロックサイズが大きい方が有利 16KB 8KB 4,000 IOPS ブロックサイズ IOPS 3,200 IOPS 32MB/秒 51MB/秒 スループット 33 2倍 0.8倍 1.6倍

Slide 34

Slide 34 text

大きいブロックサイズの考察 ● ブロックサイズが大きいと IOPS は若干減少する ● 1レコードだけ取得する場合は不利 ○ 巨大ブロックを読み込んでも、利用するのは1レコードだけ ○ INDEX UNIQUE SCAN ⇒ TABLE ACCESS BY INDEX ROWID たった1レコードのために ブロック全体を取得する データブロック 大半のデータは 読み込んでも使わない 34

Slide 35

Slide 35 text

大きいブロックサイズの考察 ● ブロック内で連続するレコードを取得する場合は有利 ○ 日付範囲などは必要なレコードがブロック内で連続しやすい ○ 同時期に挿入されたレコードは同一ブロック内に固まりやすい ○ ブロック読込回数が減り、スループットが高くなる データブロック 取得したブロック内のデータは ほとんどが必要なレコード 35

Slide 36

Slide 36 text

● DBバッファキャッシュの無駄が多い ○ キャッシュするのはデータブロック単位 ○ 1つが大きいためキャッシュできるデータブロック数が少ない 大きいブロックサイズの考察 データブロック データブロック DBバッファキャッシュ 1ブロックが大きいため 入るブロック数が少ない 36

Slide 37

Slide 37 text

小さいブロックサイズの考察 ● ブロックサイズが小さいと IOPS は多少増加する ● 1レコードだけ取得する場合は有利 ○ 読み込んだけど利用しないデータが最小 ○ INDEX UNIQUE SCAN ⇒ TABLE ACCESS BY INDEX ROWID データブロック ブロックが小さいため 無駄が少ない 37

Slide 38

Slide 38 text

小さいブロックサイズの考察 ● ブロック内で連続するレコードを取得する場合は不利 ○ 日付範囲などは必要なレコードがブロック内で連続しやすい ○ 同時期に挿入されたレコードは同一ブロック内に固まりやすい ○ ブロック読込回数が増え、スループットが低くなる データブロック ブロックが小さいため 沢山のブロック読込が必要 38

Slide 39

Slide 39 text

● フルスキャンの場合だけ、スループットを高くできる ○ マルチブロックリード機能で複数ブロックを同時取得 ○ TABLE ACCESS FULL, INDEX FAST FULL SCAN ○ しかしフルスキャンが許されるのはバッチ処理くらい 小さいブロックサイズの考察 複数ブロックを1回のI/Oで 取得するので速い データブロック ざくっと一気に マルチブロックリード 狙ったブロックを1つずつ 通常リード 39

Slide 40

Slide 40 text

● DBバッファキャッシュの無駄が少ない ○ キャッシュするのはデータブロック単位 ○ 小さいため沢山のデータブロックをキャッシュ可能 小さいブロックサイズの考察 DBバッファキャッシュ データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック データブロック 1ブロックが小さいため 沢山のブロックが入る 40

Slide 41

Slide 41 text

ブロックサイズはなにが正解か? ● ブロックサイズは表領域単位で変更できる、 可能ならばテーブルごとにチューニングするのがベスト ● しかし現実的には難易度が高い、実施コストが高くなりがち ● 開発者としてはDBのブロックサイズを把握して、 それに合わせたチューニングを行うのがベター ○ 構造的にパフォーマンスがでないからバッチ処理にする ○ 1ページで取得するアイテム数を減らして速度をだす ス○ーピー「配られたカードで勝負するしかないのさ」 孫子「彼を知り己を知れば百戦殆うからず」 41

Slide 42

Slide 42 text

ROWIDとは何か 42

Slide 43

Slide 43 text

ROWIDとは ● ROWIDはDBストレージ内の住所のようなもの ● データブロックとブロック内の位置などを特定可能 ● ROWIDが分かれば1回のI/Oで必要なレコードを取得できる 必要なデータブロックの 場所を特定できる 膨大な数のデータブロック 43

Slide 44

Slide 44 text

ROWIDの構造 ● 「AAAVREAAEAAAACXAAA」 という形式 ○ 6桁: AAAVRE ⇒ データオブジェクト番号 ○ 3桁: AAE ⇒ 相対ファイル番号 ○ 6桁: AAAACX ⇒ データブロック番号 ○ 3桁: AAA ⇒ 行番号 ● dbms_rowidパッケージで解析できる select rowid, dbms_rowid.rowid_object(rowid) as data_object_id, dbms_rowid.rowid_relative_fno(rowid) as relative_fno, dbms_rowid.rowid_block_number(rowid) as block_number, dbms_rowid.rowid_row_number(rowid) as row_number, scott.emp.empno, scott.emp.ename from scott.emp ; 44

Slide 45

Slide 45 text

● scott.emp テーブルのROWIDを解析してみる ● 各レコードはすべてファイル番号4、データブロック番号151 に入っていることが分かる ● このテーブルはフルスキャンに必要なI/O回数が1回と分かる ROWIDの解析例 ROWID DATA_OBJECT_ID RELATIVE_FNO BLOCK_NUMBER ROW_NUMBER EMPNO ENAME AAAVREAAEAAAACXAAA 87108 4 151 0 7369 SMITH AAAVREAAEAAAACXAAB 87108 4 151 1 7499 ALLEN AAAVREAAEAAAACXAAC 87108 4 151 2 7521 WARD AAAVREAAEAAAACXAAD 87108 4 151 3 7566 JONES AAAVREAAEAAAACXAAE 87108 4 151 4 7654 MARTIN AAAVREAAEAAAACXAAF 87108 4 151 5 7698 BLAKE AAAVREAAEAAAACXAAG 87108 4 151 6 7782 CLARK AAAVREAAEAAAACXAAH 87108 4 151 7 7788 SCOTT AAAVREAAEAAAACXAAI 87108 4 151 8 7839 KING AAAVREAAEAAAACXAAJ 87108 4 151 9 7844 TURNER AAAVREAAEAAAACXAAK 87108 4 151 10 7876 ADAMS AAAVREAAEAAAACXAAL 87108 4 151 11 7900 JAMES AAAVREAAEAAAACXAAM 87108 4 151 12 7902 FORD AAAVREAAEAAAACXAAN 87108 4 151 13 7934 MILLER 45

Slide 46

Slide 46 text

ROWIDはどこで使われるか ● テーブルはヒープ表と呼ばれる ● レコードは任意のブロックに格納される ● 各レコードはROWIDによって格納位置を識別できる ● では、ROWIDはなにに使われるのか? 各レコードはROWIDを持つ データブロック ID Name Price Updated 1 AA 100 2012-10-04 2 BB 53 2014-12-01 3 CC 500 2018-06-12 4 DD 12 2019-03-28 5 EE 212 2020-05-03 ... ... ... ... 46

Slide 47

Slide 47 text

● ROWIDの主な利用用途はインデックス ● B+tree構造で「インデックス対象のデータ値」と 「レコードへのROWID(参照)」を持つ ROWIDはどこで使われるか 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ ブランチブロック リーフブロック データ値とROWIDを持つ 47

Slide 48

Slide 48 text

● 例えば ID=232 のレコードを探す場合を考える ○ ブランチブロックを2つたどる ⇒ リーフブロックでROWIDを 取得 ⇒ ROWIDでテーブルのレコードを取得 ROWIDはどこで使われるか 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ ブランチブロック リーフブロック このROWIDでテーブル からレコードを取得 48

Slide 49

Slide 49 text

プライマリキーでレコード取得 ● プライマリキーでレコードを取得する例 ○ インデックスのブランチノード取得 × 2回 ○ インデックスのリーフノード取得(ROWIDを取得) ○ ROWIDからテーブルのレコード取得 ➢ 計4回程度I/Oのラウンドトリップ、遅くないけど速くもない ● テーブルからデータを直接取得する場合 ○ マルチブロックリード機能、高いスループットで取得可能 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ 3 CC 500 2018-06-12 インデックス走査 レコード取得 49

Slide 50

Slide 50 text

● それぞれ10,000レコード取得に必要なコストを考える ● インデックス走査とレコード取得が10,000レコード分必要 ● ブランチノードはキャッシュのヒット率 が高い テーブル直接とインデックス経由のレコード取得 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ 3 CC 500 2018-06-12 インデックス走査 レコード取得 10,000回 インデックス経由で10,000レコード取得する場合 50

Slide 51

Slide 51 text

● テーブルの場合はマルチブロックリード機能で 複数ブロック・複数レコードをまとめて取得可能 ● ただしテーブル内の全行取得が必要 ● どちらが速いかは状況次第 ○ 全体で5万行程度なら?ブロックの分散状況は? ○ 1レコードが巨大なら?逆に小さいなら? ● 時間経過でレコードが増加するならインデックス利用が必須 ○ マルチブロックリードが速くてもいつかは破綻する テーブル直接とインデックス経由のレコード取得 テーブルから直接10,000レコード取得する場合 ID Name Price Updated 1 AA 100 2012-10-04 2 BB 53 2014-12-01 3 CC 500 2018-06-12 4 DD 12 2019-03-28 5 EE 212 2020-05-03 ... ... ... ... 1回のI/Oで複数行取得できる 51

Slide 52

Slide 52 text

MySQL(InnoDB)との比較 52

Slide 53

Slide 53 text

なぜ他のDBを知る必要があるのか ● プロダクトとして長期間生き残り利用されているDBは それだけの間エンジニア達によって磨かれている ● 各DBの構造は違いがあり速度特性も異なる、ただそれは特徴 の違いであり、どちらかが圧倒的に優れているわけではない ● いかなるDBも銀の弾丸で高速化されているわけではない、 速いのには理由があるし、遅いのにも必ず理由がある ● 各DBの構造の違いを知ることは、チューニングにおける限界 点を理解する助けになる ● 卓越したエンジニア達が磨いてきたプロダクトには「存在す る理由」がある 53

Slide 54

Slide 54 text

● InnoDBのテーブルはクラスタインデックス構成 ● テーブル全体がB+treeインデックス構造になっていて、 レコードのデータをリーフページに持つ MySQL(InnoDB)との比較 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,AA,100,2012 3,BB,120,2019 … 10,CC,99,2020 11,DD,210,2009 16,EE,180,2010 … 25,FF,510,1980 231,KK,615,2015 232,LL,880,2002 … 250,PP,980,1995 ・・・・・・・・・ 非リーフページ リーフページ インデックス構造で レコードデータも持つ 54

Slide 55

Slide 55 text

● プライマリキーでレコード取得する場合はインデックス走査 だけでレコードも取得できて有利 ● Oracleの場合はROWIDからレコードを取得する一手間が必要 MySQL(InnoDB)との比較 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,AA,100,2012 3,BB,120,2019 … 10,CC,99,2020 11,DD,210,2009 16,EE,180,2010 … 25,FF,510,1980 231,KK,615,2015 232,LL,880,2002 … 250,PP,980,1995 ・・・・・・・・・ 非リーフページ リーフページ インデックス走査だけで レコードデータも取得可能 55

Slide 56

Slide 56 text

● InnoDBにはROWIDにあたるものがない ● セカンダリインデックス(プライマリキー以外のインデック ス)は、レコードのポインタとしてプライマリキー値を持つ ● セカンダリインデックスはインデックス走査が2回必要、 ROWIDでダイレクトに取得できるOracleよりも遅い ● プライマリキーのデータサイズが大きいと無駄が多くなる InnoDBにおけるセカンダリインデックス 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ セカンダリインデックス走査 (プライマリキー値の取得) 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,ACXAAA 3,ADXAAC … 10,ADXABG 11,ADXAAD 16,AEAAAA … 25,AEBAAB 231,AGXAAC 232,AHAAAB … 250,AHBAAK ・・・・・・・・・ クラスタインデックス走査 (レコードの取得) 56

Slide 57

Slide 57 text

● クラスタインデックスのため、テーブルフルスキャンは プライマリキー順に取得される ⇒ 開発者に優しい ● Oracleと比べてチューニングの余地は制限される MySQL(InnoDB)との比較 0..40 41..80 81..250 0..10 11..25 26..40 41..51 52..80 81..121 122..230 231..250 0,AA,100,2012 3,BB,120,2019 … 10,CC,99,2020 11,DD,210,2009 16,EE,180,2010 … 25,FF,510,1980 231,KK,615,2015 232,LL,880,2002 … 250,PP,980,1995 ・・・・・・・・・ 先頭から順に取得 57

Slide 58

Slide 58 text

レコードサイズとパフォーマンス 58

Slide 59

Slide 59 text

1レコードのサイズ ● レコードのデータはデータブロックに格納される ● ブロックサイズは2~32KB、数値や日付データなら十分大き いが、文字列を入れると意外と小さい ● 商品説明カラムに4000byte割り当てた items テーブルでは、 8KBのブロックに2レコード程度しか入らない データブロック 59 巨大な1レコード 巨大な1レコード

Slide 60

Slide 60 text

1レコードのサイズ ● 1レコードのサイズを考慮してパフォーマンスを考える ● レコードサイズが大きいと大量レコード取得で遅い ● ROWIDで1レコード取得するコストはさほど変わらない ○ ブロックが分散した10,000レコード取得は? ○ ブロック内に連続した10,000レコードの取得は? 巨大な1レコード 巨大な1レコード 1レコード 大きい 1レコード 小さい 60

Slide 61

Slide 61 text

1レコードのサイズを設計 ● 絞り込み/並び替えのカラムを小さいテーブルに切り出す ● レコードサイズを小さく設計、テーブルサイズが小さいと 様々なシーンでパフォーマンスを出しやすい 巨大な1レコード 巨大な1レコード 1レコード 大きい 1レコード 小さい 読み込むレコードが多い ⇒ 読み込むブロック数が多い ⇒ 遅い 必要なカラムを 小さいテーブルに 切り出す 61 読み込むレコードが多い ⇒ 読み込むブロック数は少ない ⇒ 速い

Slide 62

Slide 62 text

レコードの分散 62

Slide 63

Slide 63 text

● 題材としてフォロー型のSNSを考える ● フォロー関係は N:N となる ● あるユーザーのフォロワーの一覧を取得するコストは? データの分散状況とパフォーマンス 63 FOLLOWEE_ID FOLLOWER_ID CREATED_ON ... ... ... 256 284 2007-09-05 255 544 2007-09-05 440 729 2007-09-05 591 932 2007-09-05 139 926 2007-09-05 289 460 2007-09-05 350 64 2007-09-05 68 397 2007-09-05 902 581 2007-09-05 666 45 2007-09-05 978 260 2007-09-05 696 208 2007-09-05 ... ... ... フォローテーブル フォローした人 フォローされた人

Slide 64

Slide 64 text

● フォロー関係は時間と共に徐々に増える ● 1ユーザーで絞ると各レコードのブロックは分散している ● フォロワーが10万人、10万レコードが7~8万ブロックに分散 ○ 8万ブロック ⇒ 4,000 IOPS なら20秒かかる データの分散状況とパフォーマンス フォローテーブル (FOLLOWEE_ID = 350) FOLLOWEE_ID FOLLOWER_ID CREATED_ON ... ... ... 350 468 2004-08-22 350 174 2004-08-22 ... ... ... 350 537 2008-04-08 ... ... ... 350 860 2018-08-24 ... ... ... 350 64 2007-09-05 ... ... ... 350 326 2019-06-05 ... ... ... 350 841 2005-05-01 ... ... ... 64

Slide 65

Slide 65 text

● 分散したレコードをDELETEしてINSERTするだけでも ブロックはまとまる ● ブロックの分散が解消するとI/O回数は激減する データの分散とパフォーマンス フォローテーブル FOLLOWEE_ID FOLLOWER_ID CREATED_ON ... ... ... 350 284 2004-08-22 350 544 2017-03-27 ... ... ... 350 932 2008-04-08 ... ... ... 350 460 2018-08-24 ... ... ... 350 64 2007-09-05 ... ... ... 350 45 2019-06-05 ... ... ... 350 208 2005-05-01 ... ... ... FOLLOWEE_ID FOLLOWER_ID CREATED_ON ... ... ... 350 284 2004-08-22 350 544 2017-03-27 350 932 2008-04-08 350 460 2018-08-24 350 64 2007-09-05 350 45 2019-06-05 350 208 2005-05-01 ... ... ... フォローテーブル DELETE/INSERT 10万レコード 8万ブロック ⇒ 20秒 ※1.25レコード/ブロック 10万レコード 400ブロック ⇒ 0.1秒 ※250レコード/ブロック 65

Slide 66

Slide 66 text

10年後を考える 66

Slide 67

Slide 67 text

DB設計のライフサイクル ● システムのライフサイクルは部品ごとに異なる ● アプリケーションプログラムは数年でリプレースを検討する ● DB設計はそれより長い間使われる、ライフサイクルが長い ● 今作ったテーブルが10年後も使われる想定での設計が必要 FOLLOWEE_ID FOLLOWER_ID CREATED_ON ... ... ... 256 284 2007-09-05 255 544 2007-09-05 440 729 2007-09-05 591 932 2007-09-05 139 926 2007-09-05 289 460 2007-09-05 350 64 2007-09-05 68 397 2007-09-05 902 581 2007-09-05 666 45 2007-09-05 978 260 2007-09-05 696 208 2007-09-05 ... ... ... FOLLOWEE_ID FOLLOWER_ID CREATED_ON 25 28 2000-09-05 25 54 2000-09-05 44 72 2000-09-05 59 93 2000-09-05 13 92 2000-09-05 28 46 2000-09-05 ... ... ... 数千行だったテーブル 数億行になってもパフォー マンスを維持できるか? 10年後 67

Slide 68

Slide 68 text

DB設計のライフサイクル ● 大規模テーブルのフルスキャンは原則として行わない ○ レコード数と処理速度を比例関係にしない ○ レコード数nに対して、計算量が O(n) ならいずれ破綻する 68 レコード数 n 計算量 =処理速度 O(n2) O(n) O(log n) O(1)

Slide 69

Slide 69 text

DB設計のライフサイクル ● 処理対象のレコードを限定できる設計を導入 ○ 履歴レコードは参照範囲を過去1年に限定など ■ 範囲内のレコードはDBバッファキャッシュに含む前提 ■ ブロック分散に気をつける ○ 大規模なソートを排除、order by狙いのインデックスを利用 ■ ソートには対象の全レコードが必要(=フルスキャン) ■ CPU・メモリだけを消費するソートは許容できる ○ 生きてるレコードと死んだレコードを区別する ■ 死んだレコードを処理速度に影響させない などなど 69

Slide 70

Slide 70 text

まとめ 70

Slide 71

Slide 71 text

まとめ 今回はOracle データベースの内部構造に着目して 「遅い理由」 「速い理由」 を考えるための基礎を学んでみました どんな事象にも必ず理由があります 仕組みを知り、バックグラウンドを把握して それではじめてベストな方法を見つけだすことができます 71