2017.10.11

InnoDB だけじゃない!MyRocks (MySQL + RocksDB) ストレージエンジンを試してみた


こんにちは。次世代システム研究室のデータベース・NoSQL・Hadoop 関連を担当している M.K. です。

最近はHadoopネタの記事が多かったのですが、今回は久しぶりに RDB、みんなお馴染み MySQL のネタについて書きます。
ここのところ、セミナーなどで非常に気になっていた MyRocks ストレージエンジンを試してみることにしました。

MySQL 界隈では知らぬものはいない松信さんが、Facebook 社で今取り組んでいるストレージエンジンで、MySQL に NoSQL の RocksDB を組み込んだものです。

そもそも MySQL のストレージエンジンとは何ぞやですが、

ストレージエンジンは、さまざまなテーブル型に対する SQL 操作を処理する MySQL コンポーネントです。InnoDB はデフォルトでもっとも汎用のストレージエンジンであり、Oracle は、特別なユースケースを除くテーブルについては、このエンジンの使用を推奨します。
公式ドキュメントに書いてあります。

今ひとつわかりずらいですが、データ処理を特徴づけるコアのエンジンのことで、MySQL はプラガブルに選択することができます。
公式ドキュメントに書いてあるように、InnoDB がデフォルトで事実上の一択です。今も進化し続けていて、ACID のトランザクションに対応して性能面もどんどん向上しているためです。

そういうわけで、MySQL ≒ InnoDB という感覚でいたのですが、久しぶりに InnoDB の対抗馬になりうるストレージエンジンが出てきました。
それが、MyRocks というわけです。

今回の検証では、MyRocks ストレージエンジンを組み込んだ MySQL の構築とパラメータ設定、簡単なクエリテストを行ってみました。

MyRocks を組み込んだ MySQL の構築


Facebook では、自分達がパッチをあてて改良した Facebook MySQL 5.6 で MyRocks が使われていますが、検証目的で MySQL 5.7 を触っておきたかったこともあり、あえて MySQL 5.7 + MyRocks で構築しました。

Oracle 社純正の MySQL ではなく (この言い回しに隔世の感がありますが・・・)、InnoDB など MySQL をチューンナップしている Percona 社の Percona Server を使いました。

MyRocks をビルドした RPM パッケージと Percona Server 5.7 (MySQL 5.7 相当) に組み込むためのツールが用意されていることから採用しました。OS は CentOS 7 です。

構築手順は、こちらの Percona ドキュメントを参考にしました。

検証環境スペック


検証環境には、いつもの GMO アプリクラウドのサーバーを使いました。スペックは以下です。
  • OS : CentOS 7
  • 仮想 CPU : 6
  • メモリ容量 : 30GB
  • ディスク容量 : 640GB
参考までに、GMOアプリクラウドの技術を応用した Z.com クラウドもあります

Percona レポジトリと MyRocks パッケージをインストール

まず、Percona のレポジトリをインストールします。手順はこちら

mysql ユーザーとグループを、任意の UID と GID で作成。libaio も必要なのでそれもインストール。
groupadd -g 600 mysql
useradd -u 600 -g 600 mysql

yum install -y libaio

Percona のレポジトリと、MyRocks のパッケージをインストールします。
yum install -y http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm
yum install -y Percona-Server-rocksdb-57.x86_64

MyRocks をインストールするだけかと思いきや、依存関係で Percona Server 5.7 一式がインストールされ、かつ、mysqld まで立ち上がりますので要注意。

OSのセットアップ周り

transparent_hugepage 無効、 vm.swappiness = 0 とファイルオープン数のリミットを増やすことを実施しました。
※以下の設定はすべて、mysqld をroot ユーザーで起動することが前提です。

transparent_hugepage 無効

transparent_hugepage を無効にする方法は以下のようなものもありますが、
  • /etc/default/grub のGRUB_CMDLINE_LINUXの行に transparent_hugepage=never を追加を編集
  • grub2-mkconfig -o /boot/grub2/grub.cfg コマンドを実施
今回は実施しません。

Percona Server を RPM でインストールすると、mysqld を起動するための systemctl コマンドファイルが整備されますが、その中の処理で無効化させます。

mysqld 用の systemctl コマンドファイル (シンボリックリンク含む)
/usr/lib/systemd/system/mysqld.service
/etc/systemd/system/mysql.service -> /usr/lib/systemd/system/mysqld.service
/etc/systemd/system/multi-user.target.wants/mysqld.service -> /usr/lib/systemd/system/mysqld.service

このファイルの中身を見ると、mysqld を起動する前に呼ぶ、/usr/bin/mysqld_pre_systemd というスクリプトがあり、
そのスクリプトの中で、transparent_hugepage 無効にする処理を行っています。
fix_thp_setting() {
    # If no THP_SETTING variable specified in /etc/sysconfig/mysql don't do anything
    if [ ! -z "${THP_SETTING}" ]; then
      # Set whatever option is specified in environment file
      echo "${THP_SETTING}" > /sys/kernel/mm/transparent_hugepage/defrag
      echo "${THP_SETTING}" > /sys/kernel/mm/transparent_hugepage/enabled
    fi
}


無効化処理をするかしないかのトリガーは、/etc/sysconfig/mysql に THP_SETTING という変数の設定があることです。
なので、今回はその設定で無効化を行います。

echo 'THP_SETTING=never' > /etc/sysconfig/mysql

vm.swappiness = 0

vm.swappiness = 0 は /etc/sysctl.d ディレクトリに任意の名前の conf ファイルを作って設定します。

echo '
vm.swappiness = 0
' > /etc/sysctl.d/percona_server_os_setting.conf
sysctl --system

ファイルオープン数のリミットを増やす

ファイルオープン数のリミットを増やすのは、mysqld を起動するための systemctl コマンドファイルで設定させます。
/etc/systemd/system/mysql.service を開き、[Service] のセクションに、LimitNOFILE 変数で指定します。
あわせてプロセス・スレッド数も増やすため、LimitNPROC 変数も設定します。

[Service]

# Sets open_files_limit
LimitNOFILE=1006500
LimitNPROC=1006500

編集が終わったら、リロードします。
systemctl daemon-reload

MySQL のパラメータ設定

Percona Server を RPM でインストールすると、/etc/my.cnf ができ、その中で他のディレクトリにある cnf ファイルを読み込むようになってます。

/etc/my.cnf
#
# The Percona Server 5.7 configuration file.
#
#
# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
#   Please make any edits and changes to the appropriate sectional files
#   included below.
#
!includedir /etc/my.cnf.d/
!includedir /etc/percona-server.conf.d/

今回はこの構造をそのまま踏襲して、/etc/percona-server.conf.d/ に myrocks.cnf というファイルを配置しました。

myrocks.cnf の中身

Facebook が公開している、MyRocks のチューニング設定を使いました。

linkbench (Facebook が作った大規模ソーシャルネットワークのトランザクション負荷を測るベンチマーク) 向けのサンプルの方です。
と言っても非常にパラメータ数が多く、基本的には今回そのまま試しています。
rocksdb_block_cache_size は環境にあわせて、20GB としました。

また、実践を見据えて MyRocks の Crash Safe Master 設定の設定も加えたのと、キャラクタセットを utf8 にして、チューニング設定の Tuning Tips にあるとおり collation (照合順) を、utf8_bin にしました。

あと、MyRocks の検証以外のところで MySQL 5.7 にハマらないために、yoku0825 さんのこちらのスライド資料も参考にさせて頂きました。

# The MySQL server
[mysqld]
port=3306
tmpdir=/var/lib/mysql_tmp
skip-external-locking
skip-name-resolve
explicit_defaults_for_timestamp=1
#-----------------------------
#         レプリケーション関連
#-----------------------------
gtid_mode=ON  # For GTID replication
enforce-gtid-consistency=true
server-id=1
#-----------------------------
#            ログ関連
#-----------------------------
log_timestamps=SYSTEM
log-bin=mysql-bin
binlog_format=ROW  # For rocksdb
binlog_cache_size=1M
sync_binlog=0
#-----------------------------
#        バッファ/キャッシュ関連
#-----------------------------
query_cache_type=OFF
sort_buffer_size=8M
read_rnd_buffer_size=16M
read_buffer_size=2M
join_buffer_size=8M
max_allowed_packet=256M
#-----------------------------
#          テンポラリ関連
#-----------------------------
max_heap_table_size=32M
tmp_table_size=32M
#-----------------------------
#         キャラクタセット関連
#-----------------------------
character-set-server=utf8
collation-server=utf8_bin  # For rocksdb
#-----------------------------
#        ストレージエンジン関連
#-----------------------------
default-storage-engine=rocksdb
default-tmp-storage-engine=MyISAM
internal-tmp-disk-storage-engine=MyISAM
#-----------------------------
#         トランザクション関連
#-----------------------------
transaction-isolation=READ-COMMITTED  # For rocksdb
#-----------------------------
#       MyRocks (rocksdb)
#-----------------------------
rocksdb_max_open_files=-1
rocksdb_base_background_compactions=1
rocksdb_max_background_compactions=8
rocksdb_max_total_wal_size=4G
rocksdb_max_background_flushes=4
rocksdb_block_size=16384
rocksdb_block_cache_size=20G
rocksdb_table_cache_numshardbits=6

# rate limiter
rocksdb_bytes_per_sync=4194304
rocksdb_wal_bytes_per_sync=4194304
rocksdb_rate_limiter_bytes_per_sec=104857600 #100MB/s

# triggering compaction if there are many sequential deletes
rocksdb_compaction_sequential_deletes_count_sd=1
rocksdb_compaction_sequential_deletes=199999
rocksdb_compaction_sequential_deletes_window=200000

rocksdb_default_cf_options=write_buffer_size=128m;target_file_size_base=32m;max_bytes_for_level_base=512m;level0_file_num_compaction_trigger=4;level0_slowdown_writes_trigger=10;level0_stop_writes_trigger=15;max_write_buffer_number=4;compression_per_level=kLZ4Compression;bottommost_compression=kZSTD;compression_opts=-14:1:0;block_based_table_factory={cache_index_and_filter_blocks=1;filter_policy=bloomfilter:10:false;whole_key_filtering=0};level_compaction_dynamic_level_bytes=true;optimize_filters_for_hits=true;memtable_prefix_bloom_size_ratio=0.05;prefix_extractor=capped:12;compaction_pri=kMinOverlappingRatio

rocksdb_override_cf_options=cf_link_pk={prefix_extractor=capped:20};rev:cf_link_id1_type={prefix_extractor=capped:20}

# Crash Safe on unplanned mysqld restart
rocksdb-flush-log-at-trx-commit=2
rocksdb-wal-recovery-mode=1
#-----------------------------
#          MyISAM
#-----------------------------
key_buffer_size=32M
myisam_sort_buffer_size=32M

MySQL 初期化と起動


MySQL 5.7 から初期化でおなじみの mysql_install_db スクリプトではなく mysqld –initialize コマンドの実施が推奨されるようになりました。
ただ、systemctl コマンドが mysqld 起動前に上述の /usr/bin/mysqld_pre_systemd を呼んでいて、その中で mysqld –initialize が実施されるため、systemctl コマンドだけで完結します。
initialize は一度実行されると、次からは行われないようになっています。

まだこの時点では、Percona Server は MyRocks に対応していないので、mysqld 起動前に上記の myrocks.cnf で、以下のパラメータをすべてコメントアウトしておきます。
  • default-storage-engine=rocksdb
  • rocksdb ~から始まるパラメータすべて

コメントアウトが終わったら、systemctl コマンドで初期化と起動をします。
#初期化&起動
systemctl start mysql
#確認
systemctl status mysql

MySQL セキュリティ設定

MySQL 5.7 から mysql_secure_installation コマンドでセキュリティ周りをちゃんと整備するようになりました。
初期化&起動すると、MySQL の root@localhost ユーザーの初期パスワードが、MySQL のエラーログに吐かれます。
その仮パスワードをもとに、mysql_secure_installation コマンドでパスワードを変更します。
対話式なので、新しいパスワードや Yes / No を入力していきます。
必要に応じて行ってください。

#mysqlのrootユーザーの仮パスワードを取得
# grep 'A temporary password is generated for root@localhost:' MySQL エラーログ

su - mysql
mysql_secure_installation

ps-admin ツールで MyRocks を有効化

mysqld が起動したら、MyRocks を有効化します。
ps-admin --enable-rocksdb -u root -p

MyRocks パラメータ設定のエラー

しかし、MyRocks のチューニング設定MyRocks の Crash Safe Master 設定をそのまま使うと、以下のパラメータ設定で怒られました。

binlog-order-commit=1
rocksdb-enable-2pc=1

どうやら、Percona Server 5.7 版には対応してない、もしくは、廃棄されたと思われるので、今回は myrocks.cnf から外しました。
また、MyRocks を使う場合は、InnoDB と混ぜて使わないように skip-innodb がチューニング設定で勧められていますが、
MySQL 5.7 は InnoDB 必須なので、無視されます。

MyRocks パラメータを設定して再起動

先程、コメントアウトした MyRocks (rocksdb) のパラメータ設定を元に戻して、mysqld を再起動します。
これで MyRocks の構築完了です。

systemctl stop mysql
vi /etc/percona-server.conf.d/myrocks.cnf  #rocksdbパラメータ設定をすべてアンコメントします。
systemctl start mysql

MyRocks テーブル作成とデータのロード


構築が終わったので、次にデータをロードしてみます。
今回も私が検証でよく使う、Kaggle にあげられている Outbrain 社のサンプルデータを使いました。
  • documents_meta.csv
  • page_views.csv
page_views.csv は 90GB 近くあるのですが、今回はそのうちの 30GB くらいを利用しました。
documents_meta.csv の方は 90M ほどでだいぶ小さいデータです。

MyRocks テーブルの作成

サンプルデータにあわせた以下のようなテーブルを作ります。
大事な点は、PK を定義することと、セカンダリインデックスの設計です。

CREATE TABLE documents_meta (
 document_id    int PRIMARY KEY,
 source_id      int,
 publisher_id   int,
 publish_time   datetime,
 KEY(publisher_id, publish_time) comment 'rev:cf_publisher'
) ENGINE=rocksdb COLLATE=utf8_bin
;
CREATE TABLE page_views (
 uuid           varchar(20) PRIMARY KEY,
 document_id    int,
 pv_timestamp   bigint,
 platform       smallint,
 geo_location   varchar(20),
 traffic_source smallint,
 KEY(geo_location, pv_timestamp) comment 'rev:cf_geo_timestamp'
) ENGINE=rocksdb COLLATE=utf8_bin
;

Facebook のドキュメントをみると、PK と セカンダリインデックスの関係は、InnoDB の仕組みに似ています。
PK は Clusterd Index ですね。セカンダリインデックスはそのキーと PK をマッピングするもので、NoSQL ではお馴染みの Column Family として扱われるみたいです。

今回、簡単なクエリ検証をするのに、page_views テーブルの geo_location と pv_timestamp カラムを条件で絞り、pv_timestamp の降順で 10 件取得する、というありがちなクエリを想定したので、
geo_location と pv_timestamp に複合のセカンダリインデックスを張りました。

詳しくは割愛しますが、MyRocks の実体の RocksDB は Cassandra や HBase と同じ Log-Structured Merge-Tree 構造なので、キー (PK) 順に並び替えた状態でデータを保存します。

MyRocks のセカンダリインデックスには、ORDER BY DESC LIMIT 10 のような降順に10件取得するといったクエリの性能を上げる、降順に保存する機能があり、今回それを指定してみました。
KEY 句に、「rev:」で始まる Column Family 名を書いた comment 文をつけます。
詳細は MyRocks のスキーマデザインを参照してください。

MyRocks テーブルへのデータのロード

テーブルを作ったら、データをロードします。
結論を言うと、巨大なデータを MyRocks テーブルに一括でロードするのは、非常に手間と時間ががかかります。

最初はサンプルデータの page_views.csv を直接 MyRocks テーブルに LOAD DATA 文でロードしましたが、サーバーのメモリが 30GB の環境、および上述のチューニング設定で 30GB のデータを入れるには無理があり、swap が起きてしまって 5 時間経っても一向に進んでいなかったため、やり方を変えました。

  1. 別のデータベースに一時的に MyISAM ストレージエンジンの page_views テーブルを作成、まずこれにロード
  2. MyISAM の page_views テーブルを、mysqldump を使って PK 順に並びを保証してテーブル定義なしでエクスポート
  3. セカンダリインデックスを落とした MyRocks の page_views テーブルに、エクスポートしたデータをバルクロードオプションを有効にした上でインポート
  4. インポート済みの page_views テーブルにセカンダリインデックスを再作成

このやり方にしたのは、MyRocks テーブルにバルクロードするには、データが PK 順に並んでいないといけないため、collation を utf8_bin にした上で並び替えてエクスポートしたかったためです。

上記の page_views テーブルの定義から、ENGINE=MyISAM に変えてセカンダリインデックスを外した MyISAM テーブルに、LOAD DATA 文で以下のパラメータを変えて行いました。
  • key_buffer_size=2G
  • myisam_sort_buffer_size=256M
ただこれでもロードするだけで、今回の環境とやり方で 10 時間以上かかったため、ベストなやり方ではなかったと思います。

mysqldump による PK 順のエクスポートと、バルクロードオプションを有効にしたインポートは、以下のようなシェルスクリプトで実施しました。
#!/bin/sh
MYPASS=xxxxx
MYDB1=myrocks_tmp
MYDB2=myrocks_db
MYTABLE=page_views
mysqldump -p"${MYPASS}" ${MYDB1} -t ${MYTABLE} --order-by-primary --set-gtid-purged=OFF > ${MYTABLE}.dmp
mysql -p"${MYPASS}" ${MYDB2} --init-command="set sql_log_bin=0;set rocksdb_bulk_load=1" < ${MYTABLE}.dmp
結果、今回の環境でエクスポートは約 80 分 (max_allowed_packet=256Mにしてあります)、インポート (バルクロード有効) は約 130 分という処理時間となりました。
セカンダリインデックスを張ったままにしていると、この時間よりかなり遅くなることが予想できます。
インポート後に、セカンダリインデックスを ALTER TABLE ADD KEY 文で再作成しました。
ALTER TABLE page_views
 ADD KEY(geo_location, pv_timestamp) comment 'rev:cf_geo_timestamp'
;
再作成にかかった時間は、約 140 分とインポートよりかかる結果となりました。


MyRocks テーブルへのクエリ検証


最後に、クエリ性能はどうなのかというところを簡単なクエリで検証してみました。
まず、全件をカウントしてみます。
select count(*) from page_views;
想像より遅くて、20分ちょっとかかりました。MyRocks は集計向きではなさそうです。

テーブル結合した上で、page_views テーブルの geo_location と pv_timestamp カラムを条件で絞り、pv_timestamp の降順で 10 件取得する、というクエリの処理時間を簡単にみてみました。
select
 pv.uuid, pv.pv_timestamp, pv.geo_location,
 pv.document_id, dm.source_id, dm.publisher_id, dm.publish_time,
 pv.platform, pv.traffic_source
from page_views pv
inner join documents_meta dm on pv.document_id = dm.document_id
where
 pv.geo_location = 'US>MO>609' and
 pv.pv_timestamp >= unix_timestamp('2011-01-01 00:00:00') and
 pv.pv_timestamp <  unix_timestamp('2012-01-01 00:00:00') and
 dm.publish_time >= '2011-01-01 00:00:00' and
 dm.publish_time <  '2012-01-01 00:00:00'
order by pv.pv_timestamp desc limit 10
;

実行計画
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                       | rows | filtered | Extra                              |
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+------+----------+------------------------------------+
|  1 | SIMPLE      | pv    | NULL       | range  | geo_location  | geo_location | 72      | NULL                      | 5857 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | dm    | NULL       | eq_ref | PRIMARY       | PRIMARY      | 4       | myrocks_db.pv.document_id |    1 |    11.11 | Using where                        |
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+------+----------+------------------------------------+
処理時間 (計 3 回)
  1. 10 rows in set (11.26 sec)
  2. 10 rows in set (0.04 sec)
  3. 10 rows in set (0.04 sec)

検索条件の geo_location を変えて再度やってみました。
pv.geo_location = ‘US>NY>501’ だけ条件を変えました。

実行計画
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+-------+----------+------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                       | rows  | filtered | Extra                              |
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+-------+----------+------------------------------------+
|  1 | SIMPLE      | pv    | NULL       | range  | geo_location  | geo_location | 72      | NULL                      | 48839 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | dm    | NULL       | eq_ref | PRIMARY       | PRIMARY      | 4       | myrocks_db.pv.document_id |     1 |    11.11 | Using where                        |
+----+-------------+-------+------------+--------+---------------+--------------+---------+---------------------------+-------+----------+------------------------------------+ 
処理時間 (計 3 回)
  1. 10 rows in set (9.05 sec)
  2. 10 rows in set (0.04 sec)
  3. 10 rows in set (0.04 sec)

どちらも初回がかなり遅いので、データベースが暖気するまで何かしら考慮しないといけない気がしました。このあたりは課題です。

InnoDB との比較検証


結局 InnoDB に比べてどうなのか、が知りたいところなので、同じスペックのサーバーを別に立てて、MyRocks を組み込んでない Percona Server 5.7 を構築しました。

InnoDB 専用 MySQL の構築

本文の前半で書いたとおりの構築手順を踏みました。
MyRocks パッケージは不要なので、Percona Server のみインストールしています。
yum install Percona-Server-server-57.x86_64
また、MySQL のパラメータ設定は /etc/percona-server.conf.d/myrocks.cnf の中身のうち、
以下の箇所を変えて構築しました。

デフォルトストレージエンジンの変更
default-storage-engine=innodb
MyRocks (rocksdb) の箇所を以下の InnoDB のパラメータ設定に置き換え
#-----------------------------
#          InnoDB
#-----------------------------
innodb_file_per_table
innodb_data_file_path=ibdata1:2G:autoextend
innodb_autoextend_increment=32
innodb_buffer_pool_size=20G
innodb_buffer_pool_dump_at_shutdown=ON
innodb_buffer_pool_load_at_startup=OFF
innodb_log_files_in_group=2
innodb_log_file_size=256M
innodb_log_buffer_size=16M
innodb_flush_log_at_trx_commit=1
innodb_support_xa=1
innodb_flush_method=O_DIRECT
innodb_read_io_threads=2
innodb_write_io_threads=6

クエリの比較検証

InnoDB の方で全件をカウントしてみると、約 9 分でした。

テーブル結合した上で page_views テーブルの geo_location と pv_timestamp カラムを条件で絞り、pv_timestamp の降順で 10 件取得するクエリ
select
 pv.uuid, pv.pv_timestamp, pv.geo_location,
 pv.document_id, dm.source_id, dm.publisher_id, dm.publish_time,
 pv.platform, pv.traffic_source
from page_views pv
inner join documents_meta dm on pv.document_id = dm.document_id
where
 pv.geo_location = 'US>MO>609' and
 pv.pv_timestamp >= unix_timestamp('2011-01-01 00:00:00') and
 pv.pv_timestamp <  unix_timestamp('2012-01-01 00:00:00') and
 dm.publish_time >= '2011-01-01 00:00:00' and
 dm.publish_time <  '2012-01-01 00:00:00'
order by pv.pv_timestamp desc limit 10
;

実行計画
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                      | rows  | filtered | Extra                              |
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
|  1 | SIMPLE      | pv    | NULL       | range  | geo_location  | geo_location | 72      | NULL                     | 10730 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | dm    | NULL       | eq_ref | PRIMARY       | PRIMARY      | 4       | innodb_db.pv.document_id |     1 |    11.11 | Using where                        |
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
処理時間 (計 3 回)
  1. 10 rows in set (20.14 sec)
  2. 10 rows in set (0.03 sec)
  3. 10 rows in set (0.03 sec)

InnoDB のテーブルでも検索条件の geo_location を変えて再度やってみました。
pv.geo_location = ‘US>NY>501’ だけの条件変更です。

実行計画
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                      | rows  | filtered | Extra                              |
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
|  1 | SIMPLE      | pv    | NULL       | range  | geo_location  | geo_location | 72      | NULL                     | 98694 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | dm    | NULL       | eq_ref | PRIMARY       | PRIMARY      | 4       | innodb_db.pv.document_id |     1 |    11.11 | Using where                        |
+----+-------------+-------+------------+--------+---------------+--------------+---------+--------------------------+-------+----------+------------------------------------+
処理時間 (計 3 回)
  1. 10 rows in set (16.10 sec)
  2. 10 rows in set (0.02 sec)
  3. 10 rows in set (0.03 sec)

InnoDB とクエリで簡単に比較したところ、初回が遅いのも処理時間もそれほどの違いはないように見えました。

データサイズの比較

クエリの処理時間ではなく、データサイズの効率はどうかというと、
  • MyRocks – page_views : 30GB
  • InnoDB – page_views : 74GB
  • MyRocks – page_views (セカンダリインデックスなし) : 18GB
となりました。InnoDB には ROW_FORMAT=COMPRESSED というテーブル圧縮機能をもっていますが、テーブル圧縮を使わない素の InnoDB と比べると、今回使ったサンプルデータでは MyRocks は半分以下のサイズになりました。

テーブル圧縮を使った InnoDB は、ワークロードやサーバーリソース次第でメリット・デメリットがより強く出ると思われるので、MyRocks と テーブル圧縮 InnoDB の性能差やリソース消費の違いを見るためには、目的のワークロードにそった、大規模トランザクションのベンチマークが必要と思います。

なお、MyRocks のデータファイルは MySQL データディレクトリ直下やその配下のデータベースディレクトリにはなく、
実は隠しディレクトリ「.rocksdb」としてデータディレクトリ直下にあります。知らないと私みたいに悩みますのでご注意を・・。

参考までに、MyRocks の page_views テーブルからセカンダリインデックスを落としたところ、3/5 サイズになりました。
比較的サイズの大きいカラムにセカンダリインデックスを張っていたからと思いますが、セカンダリインデックスをたくさん張る場合はディスク効率の点も要注意です。


前編まとめ


今回の検証でわかったことは、MyRocks のメリットは一定以上の大規模トランザクション環境でないと享受できないということです。
メリット
  • ワークロードによるものの、Facebook 規模で実践投入できていることから、性能に対するディスク効率が良い
デメリット
  • 大量のデータをインポートするのがとても大変
  • 集計処理が強いとは言い難い

ある程度以上の規模のトランザクションや、ディスク効率の最大値を引き出さないといけない、ということがなければ、InnoDB の方が使いやすいと思いました。
個人的には、Log-Structured Merge-Tree 構造の NoSQL を、MySQL のトランザクション管理にのせられる、または MySQL レプリケーションを使える、というのは非常に興味深いところです。

いずれにしても、MyRocks は発展途上で、MySQL 5.7 以降への最適化や Percona Server や MariaDB への展開をウォッチしつつ、今後 linkbench や自作の負荷ツールでベンチマークを取って検証してみたいと思います。


最後に


次世代システム研究室では、データ分析エンジニアやアーキテクトを募集しています。ビッグデータの解析業務など次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。