Upgrade to Pro — share decks privately, control downloads, hide ads and more …

InnoDB Table Compression

InnoDB Table Compression

InnoDB Table Compression のお話です

Avatar for Takanori Sejima

Takanori Sejima PRO

February 20, 2016
Tweet

More Decks by Takanori Sejima

Other Decks in Technology

Transcript

  1. 今日のお題 - (2008年あたり)InnoDB Plugin のころ、 InnoDB に圧縮機能が追加されました - Facebook など一部の大企業が酷使しました

    - MySQL 5.7 になって、 InnoDB Page Compression というものが追加されました - なぜ Page Compression というものが追加さ れたのか、むかしの圧縮機能はどういうもの だったのか、振り返ってみましょう
  2. あとは - BLOB などの可変長カラムが実はインラインで 格納されるという、この話でしょうね - Externally Stored Fields in

    InnoDB - 公式ドキュメントにも書いてあります - 14.9.3 DYNAMIC および COMPRESSED 行フォーマット - 14.7.5 InnoDB テーブルでの圧縮の動作 - 文字列がインラインで格納されてるページ、そこ そこ圧縮率良くなるんじゃないかなぁ
  3. uncompressed page(展開済ページ) - compressed data が展開され modification log が適用された状態のページ -

    InnoDB Table Compression 使うときに出てく る特別な概念 - レコードのデータを読んだり書いたりするときに 使う - MySQL5.6以降では padding されたりするんだ けど詳しくは後述
  4. ややこしい話だけど - buffer pool 内の page はふつう innodb_page_size で指定したサイズになって るけど

    - 圧縮された page は、 KEY_BLOCK_SIZE で 指定されたサイズの page になる - SHOW ENGINE INNODB STATUS などでみると、 innodb_buffer_pool_size/innodb_page_size より page の数がはるかに多くなったりする
  5. MySQL5.6では - page cleaner thread が lru_scan_depth にも とづいて free

    page 確保するときに、 unzip_LRU 参照して展開済みページ減らしたり する - buf_do_LRU_batch() あたりから読むとわかる - buf_LRU_evict_from_unzip_LRU() が true のときは、 優先して展開済みページを捨てる
  6. 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多いと展開済みページは容赦なく捨てる
  7. ちなみに unzip_LRU listの長さは - SHOW ENGINE INNODB STATUS\G でわか ります

    - このへん - InnoDB から I/O バウンドだとみなされていれ ば、 unzip_LRU len は LRU len の 10% しか ないでしょう
  8. まずここまででわかること - ストレージが遅いという前提の設計だが - ストレージが変わろうとも、I/Oのコストは均一な想定。 HDD / SATA SSD /

    PCI-e SSD どれを使っても、 unzip より I/O の方が50倍遅いという前提になってる - ワークロードによっては unzip されまくる可能性 あり - 最近のSSDかなり速いのでちょっとビミョウ
  9. では、 zip/unzip するのは誰なのか - ほとんどがSQL実行するスレッド。クライアント からの connection 受け付けてSQL実行する thread や、slave

    の SQL thread - SQLなど実行して page を参照/更新する thread たち - page cleaner thread はすでに圧縮されたページを disk に flushするのであって、 zip/unzip をするわけではない - ということは、ワークロードによっては、 SQL thread が unzip しまくる可能性
  10. 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
  11. こういうtableをつくります mysql> show create table test\G *************************** 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)
  12. 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');
  13. ここからちょくちょくステップ進めていくと 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 "9\302\033\371", m_start = 112, m_external = false, m_end = 140, m_nonempty = 1, n_blobs = 0, ssize = 4}
  14. 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)
  15. MySQL5.6でmergeされたpatch - InnoDB Table Compression のヘビーユーザ である Facebook からの patch

    が、 5.6 で merge されました - InnoDBチームの昔のblogにまとめられていま す - InnoDB Compression Improvements in MySQL 5.6 - しかし
  16. innodb_log_compressed_pages - REDO log への書き込みを減らせるので - これは純粋に嬉しいですね - REDO log

    への書き込みが減ると、 MySQL5.6 以降では write combining 効きやすくなります - write combining については、こちらのスライド を参照してください - 5.6 以前の InnoDB Flushing
  17. Adaptive Padding - もう、ここまで来ると何がいいたいのか最初わか んなかったですが - ありがとう Nizam さん。貴方の資料はわかりや すかったです

    - 具体的には、次のパラメータが関係します - innodb_compression_failure_threshold_p ct - innodb_compression_pad_pct_max
  18. recompression の改善 - MySQL5.5 以前は、 Padding されてなかった ので、 compression failure

    からの recompression 発生しやすかった。 - (長い文字列のように、圧縮効率よいものだと起きにくい かもしれませんが)、 modification log 適用して再圧縮 したとき、圧縮済みのページにデータが収まらない可能 性があります。 - そうなると、ページを分割して、それぞれ圧縮し直しに
  19. そこで Adaptive Padding - modification log が一杯になる前に圧縮し始め て、「compression failure からの

    recompression」の頻度を下げるのが狙い - innodb_compression_pad_pct_max で展開 済みページ内の空き領域の最大値を決める - 空き領域使い切ったら予め page を分割して、 compression failure を避ける
  20. 幅広く使いたいなら - innodb_compression_level = 1 - compression を軽く - transaction

    の実行時間などが compression の影 響を受けにくくなる - KEY_BLOCK_SIZE=8 - compression failure 避ける - 次の二つはとりあえずデフォルトでいいかな? - innodb_compression_pad_pct_max - innodb_compression_failure_threshold_pct
  21. とにかく圧縮したいなら - innodb_compression_level = 9 - KEY_BLOCK_SIZE は 4 以下

    - 圧縮しまくる方向 == modification log を最小にする方 向で - KEY_BLOCK_SIZE を 4 以下に下げるときって、とに かく圧縮率がよいデータを入れるときくらいじゃないかな - 次の二つはデフォルトでもいいかも - innodb_compression_pad_pct_max - innodb_compression_failure_threshold_pct
  22. - とりあえず INNODB_CMP で次の比率 (compressの成功率)を確認するのが良いと思 います - COMPRESS_OPS_OK / COMPRESS_OPS

    - 更新頻度高いと、使っているうちに compression failure 発生して、compress の成 功率が変わったりするでしょうから、ときどきは 確認しても良いでしょう Monitoring については
  23. といったことを踏まえると - Mark Callaghan 氏が、なぜ innodb_compression_level = 1 や Padding

    の設定いれつつ、次のような記事を書いたのか が、読み取れるようになると思います。彼らは性 能改善したかったのでしょう。 - InnoDB compression for read-only workloads - InnoDB compression for OLTP - Making InnoDB compression adaptive
  24. patch を理解するには背景を知るとよい - MySQL 5.6 で merge された InnoDB Table

    Compression の patch は - 初見の人が Facebook のことを考えずに理解 するのは難しい(と思う) - よくわからん patch が入ったら、その人たちの blog 読むなりして、その背景を理解するのが近 道
  25. 参照性能はそこそこだけど - 何はともあれ、 zlib での圧縮/展開が重い - unzip しなくていい状態はけっこう速い - あと、mutex

    の競合が増える - 展開済みページを割り当てるとき、 buffer pool の instance 単位でしか存在しない mutex が競合 - innodb_buffer_pool_instances を増やすのが有効 なのは このあたりのはず - ただ、(5.6以降だと改善してるのか)read only なと きは、 unzip の方が性能に与える影響大きいかも
  26. sysbench 0.5 で MySQL5.6 試すと - 次のように prepare して -

    https://gist.github. com/sejima/dabfb85eea99459f78dd - 次のようにalterして - https://gist.github. com/sejima/64eb1cc7d7fd0dd9e683 - これで 2.4GB くらいなので、 buffer pool に は楽勝で収まります
  27. - 次のように SHOW ENGINE INNODB STATUS しながら - https://gist.github. com/sejima/7e7d3d31341ee957d35a

    - 次のように oltp.lua を実行すると - https://gist.github. com/sejima/d22d8814b46ae29b6131
  28. 何よりもつらいのは - index->lock を取得してから圧縮するケース - B+Tree が更新されるとき、(5.6以前は)ツリー全体が 排他ロックされるのだが、排他ロック取得してからページ が圧縮されるケースがある。 -

    というわけで、 B+Tree 更新するようなクエリが飛んで来 ると、B+Tree経由でのそのテーブルへのアクセスがブ ロックされる(超ざっくりいえばテーブルロック) - 少ない table を多くの Thread から更新するとつらい
  29. B+Tree の更新とは - page 内のレコードの件数がある程度増えたり 減ったりすると、 page の分割やマージが発生 する -

    これは compression failure が発生しない状況でもさけ られない。極論すると、 16KB の page にMB単位の データを INSERT できるはずがない。 - page の分割やマージが発生したタイミングで、 index->lock つかんだまま圧縮されてしまう
  30. mutex 競合しやすいと core 使いにくい - 少ない mutex を取り合うと、 CPUのcoreが増 えたとき使い切るのが難しくなる

    - mutex を取り合ってるとき、 spin loop が回っ て、 spin lock で CPU 浪費してる可能性もある - InnoDB の spin lock について興味のある方は、詳しく はこの記事読んでください - InnoDB の mutex の話(入門編)
  31. 閑話休題 - ベンチマークには要注意 - 実行する Thread の数と Table の数が等しかっ た場合、

    index->lock の競合が発生しない場合 もある - 実際、 index->lockの競合が発生するケースとしない ケースで、 InnoDB Table Compression の性能はぜん ぜん違う - 実際のワークロードを想定して評価しよう
  32. 5.5 と比べ、 5.6 でだいぶ良くなったけど - 5.6 のデフォルトパラメータでもけっこういけるけ ど、パラメータチューニングすると、 spin round

    や os wait などを減らせる。 - innodb_compression_level 下げて - Adaptive Padding に期待する - innodb_buffer_pool_instances 増やすのも試していい かも
  33. あと、5.6では - lru_scan_depth という概念ができて、なるべく free page 確保されるようになった - 5.5 は

    free page 使い切る設計なので、 single page flush が 5.6 より発生しやすい - single page flush と compression failure のコンボとか どう考えても辛そう - single page flush などについては こちら を参 考に
  34. 個人的に思うのは - InnoDB Table Compresion で CPU をうまく使 い切れるか?と言われると、ちと難しい。CPUス ケーラビリティがよくない

    - index->lock を排他ロックしつつ圧縮はしんどい。更新 が増えると mutex が競合せざるを得ないのでは - CPU活用したいなら、テーブル分割がオススメ - spin round や os wait を Monitoring しても良 いかも
  35. 思うに - InnoDB Table Compression は、とても良く出 来ていると思いますし、テキストデータが多い大 規模SNSの Facebook にはマッチしたんでしょ

    うが - もう7年以上前にリリースされた機能みたいです し、将来のハードウェアの進化を考えると、リ ファクタリングしたほうが良いのかな?という時 期が差し迫ってきているんでしょう
  36. そして出てきた - 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.