Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
8年以上前投稿 修正あり

InnoDBの透過的ページ圧縮(MySQL Server Blogより)

InnoDBではMySQL 5.1のInnoDBプラグインからページ圧縮の機能があるが、新しく透過的なページの圧縮が実装された。機能上の制約、内部の実装、ベンチマーク結果などをご紹介する。ハードとファイルシステムを適切に選ぶことで最大300%の性能向上と旧来の圧縮と同等の圧縮効果が得られる可能性がある。

原文
InnoDB Transparent Page Compression | MySQL Server Blog (English)
翻訳依頼者
D98ee74ffe0fafbdc83b23907dda3665 B5aa4f809000b9147289650532e83932
翻訳者
B5aa4f809000b9147289650532e83932 taka-h
翻訳レビュアー
D98ee74ffe0fafbdc83b23907dda3665 doublemarket
原著者への翻訳報告
未報告


免責事項

この記事はSunny Bains氏によるMySQL Server Blogの記事「InnoDB Transparent Page Compression」(2015/8/18)をユーザが翻訳したものであり、Oracle公式の文書ではありません。


鋭敏な読者の皆様はInnoDBはMySQL 5.1のInnoDBプラグインから既に圧縮機能をもっていることに気がついているだろう。我々は「ページ圧縮(Page Compression)」という用語はMySQL 5.7でリリースされる予定の新機能を、「InnoDBの圧縮(InnoDB Compression)」はそれ以前のものとして使うこととする。

最初に簡単な歴史と現在の発端に関してご説明しよう。

InnoDBの圧縮

圧縮されたテーブルは2008年にMySQL 5.1のInnoDBプラグインの一部として導入された。圧縮を追加することで、InnoDBは高速なSSDデバイスなどの近代のハードウェアをより有効に活用できるようになった。Facebookはこの技術を初期段階で採用した。彼らは大規模な解析を行い多数の改善を行い、大部分は後にアップストリームのMySQLのコードにもプッシュされた。

旧来のInnoDBの圧縮を適正に動作させる為のコードの変更は大規模で複雑である。その影響はどこにでもあるのだ。つまり、圧縮を利用する時にはそれを適正に動作させるためにはInnoDB内部の全てのモジュールを修正する必要がある。メンテナンス性および、機能を改善する時に、この複雑さが課題である。我々は内部で長期間にわたりどのように対処すべきかについて議論してきた。我々は旧来の圧縮のコードを再設計し全て書き換えたいのだが、現実的ではないのだ。このようにして、我々は旧来の圧縮のコードに関してはユーザーから報告された問題の修正のみにとどまっている。性能と圧縮の問題を解決するのにB-Tree関連でより良い方法があると考えている。例えば、LSM treeと/またはBW-Tree、あるいはその2つのバリエーションなどである。新しいインデックスのアルゴリズムを加える為には、今後の展望を変えながらも同時に完全に後方互換を保つ必要があり、その柔軟性を備えるためにページと行の形式も変更したいと思っている。InnoDBを開発し改善し続けながら、ライトアンプリフィケーションと圧縮効率の問題は古いコードベースに小手先の変更を加えるのではなく、本質的に対処されるべきであるとも考えている。

InnoDBのページ圧縮の導入

FusionIO(現在はSanDiskの一部である)が透過的ページ圧縮についてのアイデアを提案したとき、そのシンプルさがアピールであった。つまりコードベースへの変更はとても局所的であり、より重要な点としては、同一のサーバーインスタンス内で旧来の圧縮と既存のInnoDBの圧縮の両者が共存でき、既存のInnoDB圧縮を補完するということである。ユーザーはテーブル毎ではあるがユースケースに対してより適した圧縮のアルゴリズムを選択できる。

高次のレベルからも、透過的ページ圧縮はシンプルなページの変換である。

書込み: ページ → 変換  → 変換されたページのディスクへの書込み → ホールパンチ
読込み: ディスクからのページ読取り → 変換 → 元のページ

この概要を説明した段階で、暗号/復号や我々が取組んでいるようなその他のものも含め、ページへのどのような変換でも適用可能であることが直ちに明らかになる。従って新しい圧縮アルゴリズムのサポートを追加するのは他のことに比べてささいなことである。MySQL 5.7には既に複数の専用スレッドでダーティーページをフラッシュする機能がある。この現行の5.7の機能は"変換"を、ページをディスクに書込む前に専用のバックグラウンドスレッドにオフロードするのに適している。これによって圧縮およびディスクへの書込みの後の"ホールパンチ"が並列化される。対照的に旧来のInnoDBの圧縮では、圧縮/展開/再圧縮の操作が(ほとんど)クエリースレッドで実施される。これがどのように動作するかを説明するには、読者の皆さんがInnoDBの内部を良く知っていると仮定したとしても、ブログ投稿で小さいシリーズをくまなければならないぐらい時間がかかる。

新しい透過的ページ圧縮の機能を利用するには、OSおよびファイルシステムがスパースファイルとホールパンチに対応している必要がある。

Linux

いくつかのポピュラーなLinuxのファイルシステムは既にホールパンチをサポートしている。例えば、XFSではLinux 2.6.38から、ext4ではLinux 3.0から、tmpfs(/dev/shm)はLinux 3.5から、BtrfsはLinux 3.7からである。

Windows

この機能はWindows上でもサポートされているものの、"そのままの状態"で現実的でないかもしれない。問題はNTFSのクラスタリングの設計のされ方にある。

Windowsでは、スパースファイルの容量確保の単位としてブロックサイズが使われない。NTFS上のスパースファイルはNTFSの圧縮に基づいている。ホールパンチは"圧縮単位"に基づいて実施され、この圧縮単位がクラスタサイズによる(下記の表を参照)。これはデフォルトでクラスタサイズが8K以上の場合ホールパンチできないことを意味している。ここに小さなクラスタサイズについてブレークダウンする。

クラスタサイズ     圧縮単位
512バイト 8Kバイト
1Kバイト 16Kバイト
2Kバイト 32Kバイト
4Kバイト 64Kバイト

デフォルトのNTFSのクラスタサイズは4Kバイトで、圧縮単位のサイズは64Kバイトであることを意味する。したがって、ユーザーが小さなクラスタサイズでファイルシステムを作成し、大きなInnoDBのページサイズを利用しない限り、ほとんど"そのままの状態で"えられる透過的圧縮の恩恵はない。

機能の利用

現在のリリースでは2つの圧縮アルゴリズム、ZlibLZ4をサポートしている。我々は.frmファイルに保存されるテーブルレベルの属性を新たに導入した。新しい属性は、

COMPRESSION := ( "zlib" | "lz4" | "none")

である。新しいテーブルを作成する時に透過的ページ圧縮を有効化したければ、次のように書けば良いだろう。

CREATE TABLE T (C INT) COMPRESSION="lz4";

後で圧縮を無効化したくなった場合には次のようにする。

ALTER TABLE T COMPRESSION="none";

上記のALTER TABLEはメタデータの変更しか伴わないことに注意して欲しい。これはInnoDB内部では圧縮はページレベルの属性である為である。つまりテーブルスペースには、サポートされている圧縮アルゴリズムが混在しうることを暗に示している。各ページの変換を強制したい場合は、次のクエリを発行する。

OPTIMIZE TABLE T;

チューニング

忘れずにinnodb_page_cleanersを適切な値に設定して欲しい。テストの中では32を使った。読込みの為の設定はそんなに簡単ではない。InnoDBは同期および非同期で読込みができる。同期の読込みはクエリースレッドにより実施される。非同期の読込みは読込み中に先行して実施される。事前読込みの不利な点は仮にそのページが利用されなくても展開が行われることである。非同期の読込みと展開はバックグラウンドIO読込みスレッドで実施される為、現実に過剰な問題とならないだろう。同期の読込みスレッドはページを利用し、そうでない場合はまず最初には同期読込みをしないだろう。現在の同期読込みを非同期読込みに変換する際の1つの問題は、InnoDBのIO完了ロジックである。あまりにも多くの非同期の読込みリクエストがあれば、バッファプールおよびページのラッチに多くの競合を引き起こしうる。

モニタリング

ホールパンチングではls -lで表示されるファイルサイズは論理的なファイルサイズで実際にブロックデバイス上に確保されるサイズではない。これはスパースファイルの一般的な問題である。ユーザーは、information schemaのINNODB_SYS_TABLESPACESテーブルにクエリを発行し、論理および実際に確保されたサイズをえることが出来る。

information schemaビューには次の列が追加されている。FS_BLOCK_SIZE、FILE_SIZE、ALLOCATE_SIZE、そしてCOMPRESSIONである。

  • FS_BLOCK_SIZEはファイルシステムのブロックサイズである。
  • FILE_SIZEはファイルの論理サイズであり、ls -lで確認できるものである。
  • ALLOCATED_SIZEはファイルシステムがあるブロックデバイス上で実際に確保するサイズである。
  • COMPRESSIONは現在の圧縮アルゴリズムの設定であり、もしあれば設定される。

注: 前に述べたようにCOMPRESSIONの値は現在のテーブルスペースの設定であり、現在のテーブルスペースの全てのページがその形式であることを保証するものではない。

ここにいくつか例を示す。

mysql> select * from information_schema.INNODB_SYS_TABLESPACES WHERE name like 'linkdb%';
+-------+------------------------+------+-------------+----------------------+-----------+---------------+------------+---------------+-------------+----------------+-------------+
| SPACE | NAME                   | FLAG | FILE_FORMAT | ROW_FORMAT           | PAGE_SIZE | ZIP_PAGE_SIZE | SPACE_TYPE | FS_BLOCK_SIZE | FILE_SIZE   | ALLOCATED_SIZE | COMPRESSION |
+-------+------------------------+------+-------------+----------------------+-----------+---------------+------------+---------------+-------------+----------------+-------------+
|    23 | linkdb/linktable#P#p0  |    0 | Antelope    | Compact or Redundant |     16384 |             0 | Single     |           512 |  4861198336 |     2376154112 | LZ4         |
...
mysql> select name, ((file_size-allocated_size)*100)/file_size as compressed_pct from information_schema.INNODB_SYS_TABLESPACES WHERE name like 'linkdb%';
+------------------------+----------------+
| name                   | compressed_pct |
+------------------------+----------------+
| linkdb/linktable#P#p0  |        51.1323 |
| linkdb/linktable#P#p1  |        51.1794 |
| linkdb/linktable#P#p2  |        51.5254 |
| linkdb/linktable#P#p3  |        50.9341 |
| linkdb/linktable#P#p4  |        51.6542 |
| linkdb/linktable#P#p5  |        51.2027 |
| linkdb/linktable#P#p6  |        51.3837 |
| linkdb/linktable#P#p7  |        51.6309 |
| linkdb/linktable#P#p8  |        51.8193 |
| linkdb/linktable#P#p9  |        50.6776 |
| linkdb/linktable#P#p10 |        51.2959 |
| linkdb/linktable#P#p11 |        51.7169 |
| linkdb/linktable#P#p12 |        51.0571 |
| linkdb/linktable#P#p13 |        51.4743 |
| linkdb/linktable#P#p14 |        51.4895 |
| linkdb/linktable#P#p15 |        51.2749 |
| linkdb/counttable      |        50.1664 |
| linkdb/nodetable       |        31.2724 |
+------------------------+----------------+
18 rows in set (0.00 sec)

制限事項

完全なものなどありえない。潜在的な問題はいつもつきもので、事前にそれを知っておくのが最善である。

利用

現在のところ共通のシステムテーブルスペース(例えば、systemテーブルスペースとUNDOテーブルスペース)も一般テーブルスペースも圧縮できないし、空間(R-Tree)インデックスに属するページはサイレントに無視する。最初の2つはInnoDBの観点からは不自然な制限である。InnoDBの内部では、共有テーブルスペースか、UNDOログであるかは違いがない。これはベージレベルの属性である。問題は、圧縮テーブルの属性を.frmファイルに保存しており、.frmファイルの構造はInnoDBのテーブルスペースについては知りようがないため発生する。空間インデックス(R-Tree)の制限は、両方の機能がヘッダーファイルの同じ8バイトを利用するため発生する。最初の制限はMySQLの伝統的な問題であり、修正が困難である。後者はもしかするとR-Treeインデックスの圧縮ページの形式を変更することにより修正可能かもしれないが、われわれは2つのフォーマットを同じ機能に導入しないことを決断した。これらの制限は新しいデータディクショナリおよび関連する取組みによってなくなるであろうものだ。

ディスク容量の節約

多くのファイルシステムでのブロックサイズの粒度はデフォルトで4Kである。これは例えInnoDBのデータブロックが16Kから1Kに圧縮したとしても、ホールパンチして15Kの空のスペースをブロックに返すことが出来ず、せいぜい12Kだけとなることを意味する。これとは逆に、ずっと小さな512バイト以下をデフォルトとしているファイルシステムでは、非常に良く機能する(例えば、DirectFSやNVMFS)。ハードウェアでは512バイトのセクタからより大きなセクタ、例えば4Kなどへ移行する傾向があり、これは書込みがセクタサイズと同サイズで行われれば、大きなセクタサイズを利用すると通常は書込みが高速化する為である。FusionIOのデバイスはフォーマットする際にこのサイズを設定できる機能を持つ。もしかすると他のベンダも追従し、同じ柔軟性を持つようになるかもしれない。そうすればキャパシティープランニングする際に、サイズとスピードのトレードオフを考慮して決断することができる。

ファイルを他のシステムにコピーする

ホールパンチをサポートしているホストからファイルをコピーし、そのファイルが透過的ページ圧縮機能で圧縮されている場合、コピー先のホストでファイルが"膨張し"穴をうめるかもしれない。そのようなファイルをコピーする時は特別な注意をしなければならない。(rsyncは一般にスパースファイルのコピーをサポートするが、scpはサポートしない)

ALTER TABLE/OPTIMIZE TABLE

テーブルスペースの圧縮アルゴリズムを変更する為、または透過的ページ圧縮を無効化するためにALTER TABLE文を発行すると、これはメタデータの変更にしかならない。実際のページは次に書込まれた時に修正される。全てのページの変更を強制させる為には、OPTIMIZE TABLEが利用できる。

ファイルシステムのフラグメンテーション

ホールパンチがファイルシステムの空きリストにブロックを戻すことにより、ファイルシステムにフラグメンテーションが発生しうる。これは2つの予期せぬ結果を発生させる。

  1. シークエンシャルスキャンが結果的にランダムIOになりうる。これはとりわけハードディスクで問題となる。
  2. ファイルシステムの空きリスト管理のオーバーヘッドを増加させるかもしれない。我々のXFSでの(短い)テストでは大きな問題はみあたらなかったが、IOの激しいテストで数ヶ月問題ないことを確認すべきである。

実装の詳細

我々はInnoDBのページヘッダにおける8バイトに、再度別の目的を持たせた。これは各テーブルスペースのファイルの各ファイルがもつものだ。対象のフィールドは"FLUSH_LSN"(下記のC言語の構造体としてのページヘッダの定義を参照のこと)である。このフィールドはMySQL 5.7のR-Treeの実装が追加される前までは、システムテーブル(ID: 0)の最初のページ(ID: 0)のみで意味があった。他の全てのページでは利用されていなかった。

typedef uint64_t lsn_t;

struct Page _PACKED_ {

    struct Header _PACKED_ {
       uint32_t        checksum;
       uint32_t        offset;
       uint32_t        prev;
       uint32_t        next;
       lsn_t           lsn;
       uint16_t        page_type;

       /* Originally only valid for space 0 and page 0. In 5.7 it is now
       used by the R-Tree and the transparent page compression to store
       their relevant meta-data */
       lsn_t           flush_lsn;
       
       uint32_t        space;
    };

    /* PAGE_SIZE on of : 1K, 2K, 4K, 8K, 16K, 32K and 64K */
    byte               data[PAGE_SIZE - (sizeof(Header) + sizeof(Footer)];

    struct Footer _PACKED_ {
       uint32_t        checksum;

       /* Low order bits from Header::lsn */
       uint32_t        lsn_low;
    };
};

flush_lsnは下記のmeta_tとして解釈される。ページをディスクに書込む前にページを圧縮し、後でディスクからページを読込む時に元に戻す為にmeta_t構造体に様々なフィールドを設定する。

/** Compressed page meta-data */
struct meta_t _PACKED_ {
    /** Version number */
    uint8_t         m_version;

    /** Algorithm type */ 
    uint8_t         m_algorithm;

    /** Original page type */
    uint16_t        m_original_type;

    /** Original page size, before compression */
    uint16_t        m_original_size;

    /** Size after compression */
    uint16_t        m_compressed_size;
};

読書の皆さんは型がuint16_tとなっていることにお気づきだろう。これによりページサイズが64Kバイトに制限される。これは現在のInnoDBのページの構造では一般的な制限事項で、この機能で新たに発生した制限ではない。このような問題があるため、長期的な方針を再考し、より抜本的な変更により修正したいと考えている。

性能

最初のテストではハードウェアはFusionIO、ファイルシステムはNVMFSを利用した。先週、新しい圧縮を利用したテストを初めて実行した時は、控えめにいってもほとんど面白くないものだった。約1年半、私は性能テストを全くやっていなかったが、その時から多くの変化が起きた。結果の数値はあまりよくなく、圧縮は旧来の圧縮とおおむね同じで、Linkbenchの秒間リクエスト数が旧来の圧縮とほぼ変わらず、ほんのわずかによいくらいで、何もあえていうことがなかった。テストに使ったマシンは4ソケットの新しいマシンで、この問題はこのハードウェア特有のNVMFSドライバにおけるNUMAの問題の為に発生するのかもしれないと考えている。

Linux Perfを使って、いくらかプロファイリングをおこなったが、何も解明されず最もCPUを消費していたのはLinkbenchであった。JavaのGCの設定も変えてみたが、変化は起こらなかった。そこで最後のバージョンで発生した可能性のあるラッチのオーバーヘッドを見始めた。AIOミューテックス保護下でホールパンチの呼出しが発生していることに気付いた。コードをミューテックスの外部に移すと、わずかに改善がみられた。少し考え込んだ後、問題を小さく分解することにトライした。この機能では大したことはしておらず、ページを圧縮し、ページをディスクに書込み、未使用ディスクブロックを解放する為にホールパンチを行うだけである。次に、ホールパンチを無効化したところ秒間リクエスト数が500%に向上した。ホールパンチの呼出しをしすぎていたのだろうか?結果的にそうであることが分かった。ホールパンチは圧縮した時の長さが元のディスク上の長さより小さい時のみでよいのだ。この修正(現在の5.7.8 RCには入っていない)により、大分結果の数値が良くなった。

私の見解では圧縮/展開はそこまで高コストではなく15%程度のオーバーヘッドである。NVMFS上のホールパンチは結果に大きなインパクトがあるようで、NVMFS上でおよそ25%の影響である。

ホストのスペックは、

RAM:                   1 TB 

Disk1:                 FusionIO ioMemory SX300-1600 + NVMFS

Disk2:                 2 x Intel SSDSA2BZ10 in a RAID configuration + EXT4

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                80
On-line CPU(s) list:   0-79
Thread(s) per core:    2
Core(s) per socket:    10
Socket(s):             4
NUMA node(s):          4
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 47
Stepping:              2
CPU MHz:               2394.007
BogoMIPS:              4787.84
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              30720K
NUMA node0 CPU(s):     0-9,40-49
NUMA node1 CPU(s):     10-19,50-59
NUMA node2 CPU(s):     20-29,60-69
NUMA node3 CPU(s):     30-39,70-79

下記がテストで利用したmy.cnfの重要な設定である。これは高度にチューニングした設定だというつもりのものではなく、ただ利用したものである。3回のテスト全てで同じである限りはそこまで重要ではないと考えている。

loose_innodb_buffer_pool_size = 50G
loose_innodb_buffer_pool_instances = 16
loose_innodb_log_file_size = 4G
loose_innodb_log_buffer_size = 128M
loose_innodb_flush_log_at_trx_commit = 2
loose_innodb_max_dirty_pages_pct = 50
loose_innodb_lru_scan_depth = 2500
loose_innodb_page_cleaners = 32
loose_innodb_checksums = 0
loose_innodb_flush_neighbors = 0
loose_innodb_read_io_threads = 8
innodb_write_io_threads = 8
loose_innodb_thread_concurrency = 0
loose_innodb_io_capacity = 80000
loose_innodb_io_capacity_max=100000

3つのテストを実行した。

  1. 圧縮なし。ページサイズはデフォルトの16K
  2. 旧来の圧縮。ページサイズは8K
  3. 新しい圧縮。ページサイズはデフォルトの16Kで圧縮アルゴリズムの指定はCOMPRESSION="lz4"

負荷およびテストで利用したLinkbenchコマンドは次の通り。

linkbench -D dbid=linkdb -D maxid1=100000001 -c config/MyConfig.properties -l

linkbench -D requesters=64 -Dmaxid1=100000001 -c config/MyConfig.properties -D requests=500000000 -D maxtime=3600 -r

LinkBenchのロード時間

新しい圧縮を利用した時のロード時間は、圧縮を使っていない時とほとんど同じで、一方で旧来の圧縮は相対的に低速であった。

最初のテストはNVMFSでのテストである。

NVMFSでのLinkbenchのロード時間

次のテストは少し遅いIntel SSD上のEXT4でのテストである。

EXT4でのLinkbenchのロード時間

LinkBenchのリクエスト数

旧来の圧縮が再度一番悪い結果となった。新しい圧縮に比べ3倍低速である。新しい圧縮のコードはまだ最適化し始めてすらいないので、もう少し絞り出して高速化することが出来る。

最初のテストはNVMFSでのテストである。

NVMFSでのLinkbenchのリクエスト数

次のテストは少し遅いIntel SSD上のEXT4でのテストである。

EXT4でのLinkbenchのリクエスト数

私はこの結果にとても驚いており、これはもっと解析が必要である。新しい透過的圧縮は実際に圧縮を行わないときより性能が向上した。これは想定していなかった :-) もしかすると、未圧縮のテーブルに比べてディスクへの書込みが減少しているため良いスコアとなっているのかもしれない。まだ確かには分かっていない。

ディスク容量の節約

性能は良くなった訳であるが、ディスク容量は削減できているのか?結果は旧来の圧縮の方がわずかに良いが、1.5%程度の差に留まる。

これはNVMFSである。

NVMFSでのディスク容量

Intel SSD上のEXT4である。

EXT4でのディスク容量

旧来の圧縮は2番目のテストではより容量削減効率が良い。これはEXT4ファイルシステムでのブロックサイズがデフォルトの4Kに設定されていた為と思われる。 次は、ブロックサイズを小さくすると秒間リクエスト数がよくなるかどうか調べてみたいと思う。

結論

この機能はユーザーにより多くの選択肢を提供する。適したハードウェアとファイルシステムの組合わせにより、300%まで性能が加速し、同時に旧来の圧縮と同レベルの容量を圧縮できる。

この機能についてのお考えを知らせて欲しい。フィードバックを受取るのを歓迎している!この新機能で問題を見つけたら、ここのコメントで知らせていただき、bugs.mysql.comでバグレポートをオープンするか、サポートチケットをオープンして欲しい。

MySQLを使ってくれてありがとう!

次の記事
FacebookのSimon Martin氏への準同期レプリケーションについてのインタビュー(The Percona Performance Blogより)
前の記事
MySQL 5.7の新機能完全リスト

Feed small 記事フィード

新着記事Twitterアカウント