Apollo Storage 2017.10.30 Monday

 今回は、弊社プラットフォーム・ソフトウェア製品である Orb DLT (Orb Distributed Ledger Technology) の構成要素の一つである Apollo Storage の概要とその高性能化の取り組みの一部についてご紹介します。

Orb DLT

 第 1 回の記事でご紹介したように、Orb DLT は非中央管理でトラザクション等を実行する Apollo と、Apollo 上で Coin 等の振る舞いを自由に定義できる Core、さらにその上のツール群である Toolbox から成るプラットフォーム・ソフトウェア製品です。Apollo はさらに、シンプルなデータ管理を提供する Apollo Storage、当該データに対して非中央型のトランザクションを実行する Apollo Transaction、加えて、分析クエリを分散実行する Apollo Analytics から成ります。本記事では、データ管理を提供する Apollo Storage について主にご紹介したいと思います。

Apollo Storage

 Apollo Storage は Multi-dimensional map 構造のストレージで、シンプルな CRUD (Create, Read, Update, Delete) インターフェースを提供しています。

 Apollo Storage で管理されるレコードは primary key によって区別され、row key (partition key) と column name によって該当の値にアクセスすることができます。もし下層のストレージ実装がパーティション毎のレコード順序を別のキー (clustering key) で管理できる場合、primary key は (row key, clustering key, column name) となり、ストレージ実装におけるレコードの並び順を活用することができます。
 Apollo Storage は、シンプルかつ柔軟なストレージ・インターフェースによって抽象化されており、複数のストレージ実装を管理できます。つまり、他のコンポーネントを一切書き換えることなく、実装を切り替えることが可能です。例えば、Apollo Storage の主要な実装の一つして、分散ストレージ Cassandra があります。他のコンポーネントは Cassandra に依存することなく、Apollo Storage のインターフェースを用いてデータの読み書きをすることが可能です。

 当然 Apollo Storage の性能は、その管理下の実装の性能に大きく依存します。すなわち、Cassandra を Apollo Storage の実装に用いた場合は、Apollo Storage の性能はもちろんのこと、それを利用する Apollo Transaction の性能にも大きな影響を与えます。よって弊社においては、Apollo Storage および Apollo Transaction の高速化のために、Cassandra の実装に対して独自の高性能化を行っています。ここでは、我々が抱えていた性能の課題とその課題に対する改良案の一つをご紹介します。

Apollo Storage の高性能化

課題

 Apollo Transaction では、独自の分散トランザクション・プロトコルを実現するために、Apollo Storage の条件付きライト・アクセス・インターフェースを多く活用しています。当該インターフェースは多くの実装で他のインターフェースと比べて負荷が高い処理であり、特にCassandra のストレージ実装においては、Paxos ベースの CAS (Compare And Set) 機能で実現されているため、トランザクション処理の性能に大きな影響を与えていました。

 例えば、Cassandra の CAS 機能におけるストレージ・アクセスに着目すると、複数ノード間での合意形成中の状態を記録するために3回、ライト・データのために 1 回、計 4 回のアクセスが発生します。一つのトランザクションを実行するには複数回の CAS を実行する必要があるため、記憶装置に対して多くのストレージ・アクセスが必要となります。多くのリクエストがある場合には、これがトランザクション性能の律速要因となることがありました。エンジニアリング・チームでは、この問題を解決すべく、Cassandra でのストレージ・アクセス回数を削減する Group Commit Log なる機能を追加しました。

Group Commit Log

 Group Commit Logとは、Cassandra が発行する二次記憶装置への小さく多量のストレージ・アクセスをまとめて行うことでその数を削減し、ライト処理のスループットを向上させることができる機能です。この機能は、現在 Cassandra コミュニティへ提案中です。

 Cassandra のライト処理では、ライト・データはまずメモリ上の Memtable に格納されます。通常は、Memtable に格納されているライト・データがある程度大きくなると、Memtable 上のライト・データは SSD や HDD などの不揮発性の二次記憶装置に格納されます。しかし、メモリ上のデータは、そのノードの予期しない電源遮断時やプロセスの強制終了時に失われてしまいます。その消失を避けるため、Cassandra は Commit Log というライト処理のログを不揮発性の二次記憶装置に別途格納します(永続化)。異常終了後にそのノードが復帰する際には、この永続化された Commit Log を用いて、異常終了時に Memtable 上にあったライト・データを復活させます。現在の Cassandra では、この Commit Log を記憶装置にいつ永続化するかというポリシは Periodic と Batch の2つあり、用途に応じて設定することができます。Orb DLT では後述するデータ消失の危険を避けるため、Batch を設定しています。

 Periodic は、定期的に Commit Log を永続化します。この定期的な永続化間隔を設定することができ、デフォルトでは10秒となっています。Periodic では、要求されたライト処理に対し、Commit Log の永続化を待たずに、Memtable への格納した時点で完了を返します。つまり、レプリカを持つすべてのノードが異常電源遮断などを起こしてしまった時、完了したはずのライト・データが消失してしまうことがあります。

 一方、Batch は、ライト要求毎に Commit Log を永続化できてからライト処理の完了を返します。つまり、完了したライト処理については、必ず Commit Log が永続化されています。そのため、データが消失することはありません。

 Apollo をはじめとして、ライト・データの消失が致命的になるソフトウェアで Cassandra を使用する場合、Batch によりデータの消失を避ける必要があります。しかし、ライト処理毎に Commit Log の永続化を行わなければならず、記憶装置への小さな多量のストレージ・アクセスが発生してしまいます。実際、1 回の CAS で 4 回 Commit Log が永続化されます。さらに、その実装を調査した結果、複数の Commit Log をまとめて永続化できるのは、ある Commit Log を永続化するタイミングに、ほぼ同時に他の Commit Log が永続化可能な状態になっているという限られたケースです。しかし、Commit Log の永続タイミングを Cassandra にリクエストを行う側から制御することは実際には不可能です。そのため、多数の CAS が Commit Log の永続化を行うと、記憶装置の IOPS 性能によってトランザクション性能が制限されてしまったり、永続化のオーバヘッドによる CPU 負荷の増加によって制限されることがあります。

 この問題に対して、Periodic のように定期的に永続化を行いつつ、かつその永続化が完了するまではライト処理の完了も返さないようにすることで、複数の Commit Log をまとめて記憶装置に永続化する Group Commit Log を提案しました。実装としては、Batch では Commit Log の永続化スレッドを待ち合わせるセマフォを永続化要求時に即時解放していたのに対し、Group ではそのセマフォを保持したままにするという僅か 1 行の差異のみでこれを実現しています。また、以下のように Cassandra 設定ファイル (cassandra.yaml) 内で永続化の間隔については設定できるようにしてあり、10 〜 数十ミリ秒程度の間隔とすることを想定しています。
 下図は提案している Group と既存の Batch の動きを図示したものです。ここでは青とオレンジの二つの処理について着目しています。青の線はライト処理中で Commit Log 永続化を待ち合わせる処理です。オレンジの線は実際に Commit Log を永続化するスレッドの処理を表しており、濃いオレンジの長方形は記憶装置への永続化を表しています。図右の Batch では、待ち合わせ処理に入った直後に永続化スレッドのセマフォを解放して即時永続化を行わせます。そのため、複数のライト処理があってもそれぞれが即時永続化を行うので、まとめて永続化する Commit Log は前の永続化中に要求されたものだけに限られます。図左の Group では、セマフォに触れず、永続化スレッドからの永続化完了のシグナルを待ちます。永続化スレッドは定期的に Commit Log をディスクに永続化するため、永続化間隔 (group_window) の間に行われるライト処理の Commit Log を一度の永続化にまとめることができます。

評価実験

 評価では、3 ノードの Cassandra クラスタを用いています。ノードは AWS EC2 m4.large インスタンスで、各ノードでは HDD 相当に設定した EBS io1 200 IOPS の 2 ボリュームを Cassandra のデータ用と Commit Log 用にそれぞれ割り当てました。測定項目としては、UPDATE リクエストを Cassandra クラスタに発行し、一定のスループットが出ている時の一度に永続化される Commit Log の数と、レイテンシを測定しました。Batch に比べて最大約 1.5 倍の数の Commit Log をまとめて永続化し、平均レイテンシを 47 % 改善させることを確認しました。

まとめ

 本記事では、Orb DLT の構成要素の一つである Apollo Storage の概要と、その高性能化の取り組みの一部である Group Commit Log についてご紹介しました。Apollo Storage は、シンプルで柔軟なインターフェースによって抽象化され、複数のストレージ実装を管理できます。また、性能向上の施策として、そのストレージ実装の一つである Cassandra の実装についても弊社で改良を試みています。この Group Commit Log によりレイテンシを約半分にする結果を得られ、本機能の追加を Cassandra コミュニティに働きかけています。多くの人が協力し合って日々改善されていくオープンソースの例に漏れず、Cassandra コミュニティでは活発な議論や開発が展開されています。今後もこの素晴らしいコミュニティの力を借りつつも、Group Commit Log の提案のように、Cassandra がより優れたソフトウェアとなるよう引き続き貢献していきたいと思っています。
Yuji Itoの最近記事