InnoDB Table Compression5. 今日のお題
- (2008年あたり)InnoDB Plugin のころ、
InnoDB に圧縮機能が追加されました
- Facebook など一部の大企業が酷使しました
- MySQL 5.7 になって、 InnoDB Page
Compression というものが追加されました
- なぜ Page Compression というものが追加さ
れたのか、むかしの圧縮機能はどういうもの
だったのか、振り返ってみましょう
10. 次に英語圏では
- やっぱり Nizam さんの資料が重要かなと思い
ます
- Getting InnoDB Compression Ready for Faceb
Scale
- INNODB COMPRESSION: PRESENT AND
FUTURE
16. uncompressed page(展開済ページ)
- compressed data が展開され modification log
が適用された状態のページ
- InnoDB Table Compression 使うときに出てく
る特別な概念
- レコードのデータを読んだり書いたりするときに
使う
- MySQL5.6以降では padding されたりするんだ
けど詳しくは後述
17. ややこしい話だけど
- buffer pool 内の page はふつう
innodb_page_size で指定したサイズになって
るけど
- 圧縮された page は、 KEY_BLOCK_SIZE で
指定されたサイズの page になる
- SHOW ENGINE INNODB STATUS などでみると、
innodb_buffer_pool_size/innodb_page_size より
page の数がはるかに多くなったりする
18. unzip_LRU
- buffer pool のページはLRUリストで管理されて
いるのだが、展開済みページを管理するLRUリ
スト(unzip_LRU)というものもある
- 圧縮された*.ibdも圧縮されてない*.ibdも、それらのペー
ジは通常のLRUリストにマッピングされる。展開済み
ページとそのLRUリストが特別扱いである
- buffer pool から、展開済みページをどれくらい
確保するかというのは、動的に決定される
19. MySQL5.6では
- page cleaner thread が lru_scan_depth にも
とづいて free page 確保するときに、
unzip_LRU 参照して展開済みページ減らしたり
する
- buf_do_LRU_batch() あたりから読むとわかる
- buf_LRU_evict_from_unzip_LRU() が true のときは、
優先して展開済みページを捨てる
20. buf_LRU_evict_from_unzip_LRU()
- これが true になる条件は
- unzip_LRU のリストが空ではなく
- unzip_LRU のリストが (buffer pool 全体の)LRU list
の 1/10 以上で
- つまり、展開済みページは少なくとも LRU list の
10%は確保される
- (unzip するより I/O 発生する方が50倍遅い前提みたい
なので)、 unzip の頻度 =< IOの頻度*50 を満たすとき
- IO多いと展開済みページは容赦なく捨てる
21. ちなみに unzip_LRU listの長さは
- SHOW ENGINE INNODB STATUSG でわか
ります
- このへん
- InnoDB から I/O バウンドだとみなされていれ
ば、 unzip_LRU len は LRU len の 10% しか
ないでしょう
23. では、 zip/unzip するのは誰なのか
- ほとんどがSQL実行するスレッド。クライアント
からの connection 受け付けてSQL実行する
thread や、slave の SQL thread
- SQLなど実行して page を参照/更新する thread たち
- page cleaner thread はすでに圧縮されたページを disk
に flushするのであって、 zip/unzip をするわけではない
- ということは、ワークロードによっては、 SQL
thread が unzip しまくる可能性
25. 次に、 zip されるタイミング
- INSERTなど更新系クエリが実行される度に圧
縮されるわけではない
- Nizam さんのスライド がわかりやすいんです
が、圧縮されてるページは、圧縮されたデータと
modification log とで構成されている
28. gdb は function 定義できるので
- こちらの記事を参考にして定義しておきます
- Memory dump formatted like xxd from gdb
-
(gdb) define xxd
>dump binary memory dump.bin $arg0
$arg0+$arg1
>shell xxd dump.bin
>end
29. こういうtableをつくります
mysql> show create table testG
*************************** 1. row
***************************
Table: test
Create Table: CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`val` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8
1 row in set (0.00 sec)
30. page_zip_write_rec() あたりで break
- このへんが modification log への書き込みにな
るので
- 適当に break point 張りつつ
- 次のような INSERT 流すと
- insert into test values (NULL, 'takanori');
- insert into test values (NULL, 'sejima');
- insert into test values (NULL, 'test');
31. ここからちょくちょくステップ進めていくと
Breakpoint 1, page_zip_write_rec (page_zip=0x7f610ec5bdd0,
rec=0x7f6116af00bd "200", index=0x7f5cdc13eef8,
offsets=0x7f5cdc007e88, create=1) at /usr/local/mysql-
5.6.28/storage/innobase/page/page0zip.cc:3585
3585 ut_ad(PAGE_ZIP_MATCH(rec, page_zip));
(gdb) p *page_zip
$3 = {data = 0x7f6116aee000 "9302033371", m_start =
112, m_external = false, m_end = 140, m_nonempty = 1,
n_blobs = 0, ssize = 4}
32. log の領域に書き込まれるとこうなる
(gdb) xxd page_zip->data 160
0000000: 39c2 1bf9 0000 0003 ffff ffff ffff ffff 9...............
0000010: 0000 0000 0018 9b17 45bf 0000 0000 0000 ........E.......
0000020: 0000 0000 0002 0002 00d2 8005 0000 0000 ................
0000030: 00bd 0002 0002 0003 0000 0000 0000 0000 ................
0000040: 0000 0000 0000 0000 0012 0000 0002 0000 ................
0000050: 0002 00f2 0000 0002 0000 0002 0032 6805 .............2h.
0000060: e294 ae63 0400 0000 ffff 0300 0176 00a4 ...c.........v..
0000070: 0200 0880 0000 0174 616b 616e 6f72 6904 .......takanori.
0000080: 0006 8000 0002 7365 6a69 6d61 0600 0480 ......sejima....
0000090: 0000 0374 6573 7400 0000 0000 0000 0000 ...test.........
(gdb)
34. MySQL5.6でmergeされたpatch
- InnoDB Table Compression のヘビーユーザ
である Facebook からの patch が、 5.6 で
merge されました
- InnoDBチームの昔のblogにまとめられていま
す
- InnoDB Compression Improvements in MySQL 5.6
- しかし
37. innodb_log_compressed_pages
- REDO log への書き込みを減らせるので
- これは純粋に嬉しいですね
- REDO log への書き込みが減ると、 MySQL5.6
以降では write combining 効きやすくなります
- write combining については、こちらのスライド
を参照してください
- 5.6 以前の InnoDB Flushing
43. - ただ、 「modification log たくさん詰め込めない
けど圧縮軽い」と、「modification log たくさん詰
め込めるけど圧縮重い」だと、後者の方が更新
処理遅くなる可能性があります。
- 詳しくは後ほど
44. recompression の改善
- MySQL5.5 以前は、 Padding されてなかった
ので、 compression failure からの
recompression 発生しやすかった。
- (長い文字列のように、圧縮効率よいものだと起きにくい
かもしれませんが)、 modification log 適用して再圧縮
したとき、圧縮済みのページにデータが収まらない可能
性があります。
- そうなると、ページを分割して、それぞれ圧縮し直しに
45. そこで Adaptive Padding
- modification log が一杯になる前に圧縮し始め
て、「compression failure からの
recompression」の頻度を下げるのが狙い
- innodb_compression_pad_pct_max で展開
済みページ内の空き領域の最大値を決める
- 空き領域使い切ったら予め page を分割して、
compression failure を避ける
49. 幅広く使いたいなら
- innodb_compression_level = 1
- compression を軽く
- transaction の実行時間などが compression の影
響を受けにくくなる
- KEY_BLOCK_SIZE=8
- compression failure 避ける
- 次の二つはとりあえずデフォルトでいいかな?
- innodb_compression_pad_pct_max
- innodb_compression_failure_threshold_pct
50. とにかく圧縮したいなら
- innodb_compression_level = 9
- KEY_BLOCK_SIZE は 4 以下
- 圧縮しまくる方向 == modification log を最小にする方
向で
- KEY_BLOCK_SIZE を 4 以下に下げるときって、とに
かく圧縮率がよいデータを入れるときくらいじゃないかな
- 次の二つはデフォルトでもいいかも
- innodb_compression_pad_pct_max
- innodb_compression_failure_threshold_pct
51. - とりあえず INNODB_CMP で次の比率
(compressの成功率)を確認するのが良いと思
います
- COMPRESS_OPS_OK / COMPRESS_OPS
- 更新頻度高いと、使っているうちに
compression failure 発生して、compress の成
功率が変わったりするでしょうから、ときどきは
確認しても良いでしょう
Monitoring については
52. といったことを踏まえると
- Mark Callaghan 氏が、なぜ
innodb_compression_level = 1 や Padding
の設定いれつつ、次のような記事を書いたのか
が、読み取れるようになると思います。彼らは性
能改善したかったのでしょう。
- InnoDB compression for read-only workloads
- InnoDB compression for OLTP
- Making InnoDB compression adaptive
53. patch を理解するには背景を知るとよい
- MySQL 5.6 で merge された InnoDB Table
Compression の patch は
- 初見の人が Facebook のことを考えずに理解
するのは難しい(と思う)
- よくわからん patch が入ったら、その人たちの
blog 読むなりして、その背景を理解するのが近
道
54. InnoDB Table Compression の弱点
- わりと複雑。初見でヒント無しでパラメータ
チューニングまで理解するのは難しい
- コード追いかけるのもだいぶめんどくさい
- buffer pool 上に圧縮済みページと展開済み
ページとを持つので、メモリをけっこう使う
55. 参照性能はそこそこだけど
- 何はともあれ、 zlib での圧縮/展開が重い
- unzip しなくていい状態はけっこう速い
- あと、mutex の競合が増える
- 展開済みページを割り当てるとき、 buffer pool の
instance 単位でしか存在しない mutex が競合
- innodb_buffer_pool_instances を増やすのが有効
なのは このあたりのはず
- ただ、(5.6以降だと改善してるのか)read only なと
きは、 unzip の方が性能に与える影響大きいかも
58. sysbench 0.5 で MySQL5.6 試すと
- 次のように prepare して
- https://gist.github.
com/sejima/dabfb85eea99459f78dd
- 次のようにalterして
- https://gist.github.
com/sejima/64eb1cc7d7fd0dd9e683
- これで 2.4GB くらいなので、 buffer pool に
は楽勝で収まります
59. - 次のように SHOW ENGINE INNODB
STATUS しながら
- https://gist.github.
com/sejima/7e7d3d31341ee957d35a
- 次のように oltp.lua を実行すると
- https://gist.github.
com/sejima/d22d8814b46ae29b6131
63. 何よりもつらいのは
- index->lock を取得してから圧縮するケース
- B+Tree が更新されるとき、(5.6以前は)ツリー全体が
排他ロックされるのだが、排他ロック取得してからページ
が圧縮されるケースがある。
- というわけで、 B+Tree 更新するようなクエリが飛んで来
ると、B+Tree経由でのそのテーブルへのアクセスがブ
ロックされる(超ざっくりいえばテーブルロック)
- 少ない table を多くの Thread から更新するとつらい
64. B+Tree の更新とは
- page 内のレコードの件数がある程度増えたり
減ったりすると、 page の分割やマージが発生
する
- これは compression failure が発生しない状況でもさけ
られない。極論すると、 16KB の page にMB単位の
データを INSERT できるはずがない。
- page の分割やマージが発生したタイミングで、
index->lock つかんだまま圧縮されてしまう
65. mutex 競合しやすいと core 使いにくい
- 少ない mutex を取り合うと、 CPUのcoreが増
えたとき使い切るのが難しくなる
- mutex を取り合ってるとき、 spin loop が回っ
て、 spin lock で CPU 浪費してる可能性もある
- InnoDB の spin lock について興味のある方は、詳しく
はこの記事読んでください
- InnoDB の mutex の話(入門編)
66. 閑話休題
- ベンチマークには要注意
- 実行する Thread の数と Table の数が等しかっ
た場合、 index->lock の競合が発生しない場合
もある
- 実際、 index->lockの競合が発生するケースとしない
ケースで、 InnoDB Table Compression の性能はぜん
ぜん違う
- 実際のワークロードを想定して評価しよう
67. 5.5 と比べ、 5.6 でだいぶ良くなったけど
- 5.6 のデフォルトパラメータでもけっこういけるけ
ど、パラメータチューニングすると、 spin round
や os wait などを減らせる。
- innodb_compression_level 下げて
- Adaptive Padding に期待する
- innodb_buffer_pool_instances 増やすのも試していい
かも
68. あと、5.6では
- lru_scan_depth という概念ができて、なるべく
free page 確保されるようになった
- 5.5 は free page 使い切る設計なので、 single page
flush が 5.6 より発生しやすい
- single page flush と compression failure のコンボとか
どう考えても辛そう
- single page flush などについては こちら を参
考に
69. 個人的に思うのは
- InnoDB Table Compresion で CPU をうまく使
い切れるか?と言われると、ちと難しい。CPUス
ケーラビリティがよくない
- index->lock を排他ロックしつつ圧縮はしんどい。更新
が増えると mutex が競合せざるを得ないのでは
- CPU活用したいなら、テーブル分割がオススメ
- spin round や os wait を Monitoring しても良
いかも
72. 思うに
- InnoDB Table Compression は、とても良く出
来ていると思いますし、テキストデータが多い大
規模SNSの Facebook にはマッチしたんでしょ
うが
- もう7年以上前にリリースされた機能みたいです
し、将来のハードウェアの進化を考えると、リ
ファクタリングしたほうが良いのかな?という時
期が差し迫ってきているんでしょう
73. そして出てきた
- Worklog 7696
- Allow transparent page level compression in the IO
layer. The InnoDB row level compression a.k.a
InnoDB compressed tables is not fast enough and
secondly the implementation is more complicated
than it should be. The transparent page level
compression complements the old scheme it doesn't
aim to replace it.