世界中のユーザーがあらゆる情報を共有しているFacebook。ユーザーの投稿や「いいね!」などのアクティビティはメインデータベースのMySQLに刻々と書き込まれていく。同社はこのMySQLおよびストレージエンジンInnoDBに性能を高めるための独自の改良を加え、日々運用している。
サイトでは常に大量の書き込みが発生しておりデータはペタバイト級、膨大な処理をまかなうために大量のサーバーでシャーディング(分散)をしている。またMySQLデータベースの前にキャッシュを置き、多くの読み込みはキャッシュでヒットしてすぐ応答を返せるようにしている。
メインデータベースのストレージにはフラッシュストレージ(SSD)を使用。SSDは高いIOPS性能を出せるものの、価格はいまだにHDDよりも高い。なにしろ台数が半端ないので、ストレージ使用量(スペース)を少しでも減らすことができれば、コスト削減に貢献する。これはフェイスブックにおいて長年優先度が高い課題となっていた。
これまではMySQLのストレージエンジンはInnoDBで運用していた。しかし、ストレージ使用に関してInnoDBならではの課題も抱えていた。例えば耐障害性を高めるために二重に書き込む処理(Doublewrite)、データのフラグメンテーション(断片化)、データ圧縮でデータサイズを縮小してもまだストレージ使用スペースに冗長さが残るなど、InnoDBではストレージ使用量が増えてしまいがちな性質があった。
RocksDBが効率よくストレージを使える理由
そこでフェイスブックではストレージ効率が高いLevelDBに目をつけた。これはGoogleが開発した階層化されたキーバリュー型NoSQLデータベースで、C++で書かれている。このLevelDBをFacebookで使えるように改良したのがRocksDB。LevelDBから見ればフォークとなる。
さらにRocksDBをMySQLのストレージエンジンとして使えるようにしたのがMyRocks(http://myrocks.io/)。ストレージ効率の高さを持ちつつ、SQLやレプリケーションなどMySQLの機能が利用できる。フェイスブックはMyRocksを2016年にオープンソースとしてGitHubで公開し、今では本番でも一部運用している。
RocksDBはHBaseやCassandraのようなLSM(Log Structured Merge)構造をしており、構成要素としては主にMemTable、WAL(Write Ahead Log)、圧縮、カラムファミリーからなる。書き込みがコミットされると、まずはWALに書き込み、次に書き込みを保持するMemTableに書き込む。両方に書き込めたら書き込み成功。アクティブなMemTableがいっぱいになるとMemTableとWALが新しく切り替わる。MemTableがメモリに一定量たまると、データファイルに書き出す(フラッシュする)。このデータファイルはさらに何段階か圧縮して容量を減らし、ストレージの使用スペースを抑えている。
WAL(ログ)やMemTableが一定量に達すると書き出すデータファイルはSST(Sorted String Table)と呼ばれ、作成時にソートと圧縮される。このSSTが一定量たまるとマージしてより大きなSSTを作る。この時も全体でソートと圧縮が行われる。
InnoDBだとデータは16KBのページ単位となる。圧縮して5KBに縮小できたとしても、実際にストレージで使用するスペースは8KBになるなど、非効率さがある。一方RocksDBのSSTファイルは32MBや64MBなど大きなサイズとなる。複数のSSTファイルをまとめて圧縮して縮小して書き込む時にはOSのセクタ単位(4KBなど)となるので、InnoDBほど冗長にならない。ここが圧縮効率がいいとされる理由だ。
またRocksDBで大きな特徴となるのが階層化だ。LevelDBの由来でもある。この階層化とは先述したように、SSTファイルをまとめた段階のようなもの。最初にMemTableからSSTを作成した時(レベル0)はあまり大きくないが、まとめる(レベル1~)たびに約10倍ほど巨大化する。
書き込み効率はいいものの、範囲検索のような読み込みでは複数のレベルにデータが分散する可能性もあり、InnoDBほど効率はよくない。読み込み時の非効率さを防ぐ対策として、ブルームフィルターがある。ブルームフィルターは探しているデータがどのレベルに存在するか見分けられるようになっている。
繰り返しになるが、RocksDBはストレージの使用効率は優れている(あまりスペースを使わなくてすむ)。ただし読み込み時は多少不利という特徴がある。