MySQL InnoDB Pluginのデータ圧縮機能

InnoDB Pluginの面白い機能の一つに、データ圧縮機能があります。今回はその仕組みと効果について見ていきたいと思います。まずはグラフをご覧ください。

これはWikipedia日本語版のデータベースをダウンロードし、記事本文の格納されているtextテーブルをMySQL 5.1+InnoDB Plugin 1.0の環境にロードしたものです。

  • 元テキスト:今回利用したデータは2009/06/21版のものです(jawiki-20090621-pages-articles.xml.bz2)。元テキストはここからXml2sqlを用いてタブ区切りテキストを取り出したものを用いています。このファイルには1,167,411件の記事が格納されており、容量は3,436MBとなっています。
  • 元テキスト gzip:元テキストをgzipコマンドで圧縮したものです。
  • MyISAM:記事をMyISAMのテーブルにロードしたものです。MYDファイルとMYIファイルを合算しています。MyISAMは元テキストに対してオーバーヘッドがほとんどないことが分かります。
  • MyISAM myisampack:MyISAMのテーブルをmyisampackというMySQL付属のコマンドで圧縮したものです。myisampackをかけるとテーブルが読み取り専用になり、更新ができなくなります。
  • InnoDB:記事を通常のInnoDBテーブルにロードしたものです。元テキストに比べ約1.5倍のファイルサイズになっています。
  • InnoDB Compressed:InnoDBのデータ圧縮機能を用いたものです。通常のInnoDBに比べおよそ半分のサイズになっています。

InnoDBのデータ圧縮機能がよく効いていることが分かると思います。

前提条件

InnoDBのデータ圧縮機能は、以下の環境で利用することができます。

素のMySQL 5.1やそれより前のバージョンでは利用することができません。また、MySQL 5.4では利用可能になることがアナウンス(PDF)されていますが、確認したところMySQL 5.4.1にはまだ実装されていませんでした。そこで今回も先日のエントリと同様、CentOS 5.3上のMySQL Community Server 5.1.35+InnoDB Plugin 1.0.3で作業を行っています。

パラメータ設定

今回利用したmy.cnfの抜粋です。

character_set_server = utf8
collation_server = utf8_bin
default_storage_engine = InnoDB
innodb_file_format = Barracuda
innodb_file_per_table = 1
innodb_autoextend_increment = 1

InnoDB Pluginのデータ圧縮機能を利用するには、innodb_file_formatとinnodb_file_per_tableという二つのパラメータを設定する必要があります。
innodb_file_formatはInnoDB Pluginで新規に追加されたパラメータで、InnoDBのファイルフォーマットを指定するものです。値としてAntelope(レイヨウ)またはBarracuda(カマス)を指定することができます。Antelopeが従来のフォーマット、Barracudaが圧縮機能をサポートした新しいフォーマットです。どうやらアルファベット順にAntelopeがバージョン1、Barracudaがバージョン2ということのようで、ソースを見るとバージョン26のZebraまでがすでに定義されていることが確認できます。
innodb_file_per_tableは従来からあるオプションで、テーブルごとにデータファイルを分割する機能です。InnoDB Pluginではこの分割されたデータファイルごとに、AntelopeとBarracudaを使い分けることができるようになっています。
そのほかcharacter_set_serverは、Wikipedia日本語版のデータがUTF-8ですのであわせてutf8にしています。また、innodb_autoextend_incrementでInnoDBがデータファイルの拡張を行うサイズを指定しています。本来はもっと大きな値を指定するべきものですが、今回はデータサイズを極力正確に見るためにあえて1MBとしています。

データ圧縮の手順

データ圧縮はテーブル単位に指定します。CREATE TABLEまたはALTER TABLE文で以下のように定義します。

CREATE TABLE `text` (
  `old_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `old_text` mediumblob NOT NULL,
  `old_flags` tinyblob NOT NULL,
  PRIMARY KEY (`old_id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8

ROW_FORMATにCOMPRESSEDを指定することで、Barracudaフォーマットの圧縮形式になります。InnoDB Pluginにおいて、ROW_FORMATは現在以下の四種類が指定できます。

  • REDUNDANT:MySQL 4.1までのフォーマットです。
  • COMPACT:MySQL 5.0以降のフォーマットです。UTF-8文字列の最適化などによって、REDUNDANTよりもデータサイズが小さくなっています。
  • DYNAMIC:InnoDB PluginのBarracudaフォーマットを用いますが、データ圧縮は行わない形式です。
  • COMPRESSED:InnoDB PluginのBarracudaフォーマットを用い、データ圧縮を行う形式です。

次のKEY_BLOCK_SIZEは、圧縮後におけるInnoDBのページサイズを指定するものです。デフォルトは8KBで、1、2、4、8、16KBから選ぶことができます。InnoDBはもともとページサイズが16KB固定になっていてこのページ単位にディスクI/Oなどのデータ管理を行っているのですが、Barracudaフォーマットの圧縮形式ではページサイズがテーブルごとに可変になっています。

データ圧縮の仕組み

データ圧縮の仕組みについて見ていきましょう。InnoDB Pluginのデータ圧縮はzlibライブラリを用いて実装されています。つまりZIPやgzipと同じDeflateアルゴリズムによる圧縮が行われているのです(Deflateアルゴリズムについてはしらぎくさいとに詳細な解説記事があります)。これは非常に珍しい実装です。何が珍しいのかというと、このような普通のデータ圧縮をデータベース側の機能制限なしに実現した製品を、私は今まで見たことがありません。他の製品におけるデータ圧縮機能は、そのほとんどが以下のように何かしらの制限を抱えています。

  • Oracle Advanced Compression:一つのデータ・ブロック内で重複した列値を省くという実装になっています。データ・ウェアハウスに用いられるような非正規化されたテーブルにおいて、同じ列値が頻出するという前提にもとづいた機能です。(参考資料(PDF))
  • MySQL myisampack:ハフマン符号化による圧縮を行っていますが、テーブルが読み取り専用になるという制限があります。
  • MySQL ARCHIVEストレージエンジン:zlibによる圧縮を行っていますが、SELECTとINSERTしかできないという制限があります。

InnoDB Pluginのデータ圧縮機能にはこのような制限はなく、圧縮されたテーブルに対して普通にSELECT/INSERT/UPDATE/DELETEを行うことが可能です。
データ圧縮はInnoDBのページ単位に行われます。InnoDBは各ページ16KBの容量があり、これを圧縮してKEY_BLOCK_SIZEの大きさの圧縮ページに格納します。KEY_BLOCK_SIZEはデフォルトで8KBとなっており、これは元のページサイズに対して半分となります。

ぱっと見て疑問が沸いてくると思います。「半分より小さく圧縮できても無駄ってこと?」「半分以下に圧縮できなかったらどうなるの?」というものです。
一つ目の疑問については、答えはYESです。データを半分より小さく圧縮できても圧縮ページに隙間ができてしまうだけで、データファイルの容量としては半分より小さくはなりません。つまり、KEY_BLOCK_SIZEを指定した時点で最大の圧縮率が決まってしまうということになります。このことから、例えばデータ特性上サイズを25%以下に圧縮できると期待できる場合は、KEY_BLOCK_SIZEには8KBではなく4KBを指定するべきです。

ただし、例外があります。LOBデータに関しては扱いが異なるため、KEY_BLOCK_SIZEが8KBでも半分より小さくできる場合があります。
InnoDB PluginのBarracudaフォーマットでは、LOBデータはすべてオーバーフローページと呼ばれる元のレコードとは別のページに格納されます。そしてそれらのデータに対する圧縮は、InnoDBのページ単位ではなくLOBデータ単位で行われます。元々16KB×2ページを使用していたLOBデータが常に8KB×2ページになるというわけではなく、圧縮が効きさえすれば8KB×1ページになることがあるのです。

Wikipediaのtextテーブルは本文がMEDIUMBLOB型のため、こうした仕組みによって半分よりやや小さいサイズまで圧縮されています。
二つ目の疑問、圧縮しても半分まで小さくできなかったときにどうなるかですが、この場合InnoDBは元のページを2つに分割して圧縮をやり直します。

そのためテーブルにあまり圧縮の効かないデータを格納してしまうと、この分割&再圧縮が頻繁に起こります。そのようなデータは元データに比べてほとんど小さくならないですし、性能も大幅に落ちてしまうことになります。圧縮がうまくKEY_BLOCK_SIZEに収まったのかどうかは、information_schemaのINNODB_CMPテーブルで確認できます。

mysql> select * from information_schema.INNODB_CMP;
+-----------+--------------+-----------------+---------------+----------------+-----------------+
| page_size | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
+-----------+--------------+-----------------+---------------+----------------+-----------------+
|      1024 |            0 |               0 |             0 |              0 |               0 |
|      2048 |            0 |               0 |             0 |              0 |               0 |
|      4096 |            0 |               0 |             0 |              0 |               0 |
|      8192 |       139654 |           77562 |           253 |          31046 |              21 |
|     16384 |            0 |               0 |             0 |              0 |               0 |
+-----------+--------------+-----------------+---------------+----------------+-----------------+
5 rows in set (0.00 sec)

INNODB_CMPテーブルには、page_sizeごとに圧縮を行った回数(compress_ops)、圧縮が無事KEY_BLOCK_SIZEに収まった回数(compress_ops_ok)が示されています。この例では13万回中6万回以上も圧縮に失敗しており、かなり成績が悪いということが分かります。圧縮の失敗が多いテーブルに対しては、KEY_BLOCK_SIZEを大きくするとか、圧縮をやめるといった対処が必要になります。

ここまでのまとめ

ここまでInnoDB Pluginのデータ圧縮機能について、その仕組みと圧縮効果を見てきました。zlibがデータを半分以下のサイズに圧縮してくれることを期待してKEY_BLOCK_SIZEがデフォルト8KBになっており、実際にWikipedia日本語版のデータがおよそ半分にまで圧縮されたことを確認しました。
ちょっと長文になってしまったので今回はここまでにして、次回は圧縮テーブルに対する性能を見ていきたいと思います。ZIPやgzipを使ったことがあればすぐに分かりますが、この手の圧縮・展開アルゴリズムは決して処理が速いわけではありません。いったいどこまで性能が落ちるのか、実際に試してみようと思います。

2009/07/05追記

性能試験を行いました。The Art of Work:MySQL InnoDB Pluginのデータ圧縮機能 性能編をぜひご覧ください。