グラフDBMSとは
扱うデータモデル(表、グラフ、JSONなど)が決まると、そのデータモデルに応じてDBMSの前後の階層(=クエリ言語および実装)の最適なデザインを選択することができます。そこで、特定のデータモデルに特化して機能を絞ったDBMSはNoSQLと呼ばれるようになり、他方、複数のデータモデルと実装を組み合わせているものはマルチモデルDBMSと呼ばれるようになりました。RDBMSの多くは既に成熟していてユースケースも広範なため、SQLの進化と併せて、自然とマルチモデルDBMSに進化している傾向があります。
NoSQLとマルチモデルの違いはさておき、グラフに対するクエリ言語と実装の最適なデザインとはそれぞれどのようなものでしょうか。
グラフに適したクエリ言語とは?
まず、クエリ言語について考えてみます。グラフモデルでは、どのエンティティ同士がどの関係性によって接続されているかが、クエリが実行される前にデータベースに保存されています。その一方で、表モデルの場合はどの表同士をどの列の値を使って結合するかをクエリで制御しますので、クエリの実行前にはどう結合されるかはわかりません。外部参照制約は表同士が後々結合されることを示唆していますが、結合のパターンが複数になることもあり、はじめから結合方法を決めているわけではありません。
このようなデータモデルの違いから新しいクエリ言語が考え出されることになります。グラフモデルの場合は、特定のエンティティであるアリスについて、何かしらの関係のある他のエンティティ(ここではポチ)が既に接続されているので、それらを簡単に取得できるクエリがあれば便利です。そこで以下のような「アスキーアート」を用いたパターンマッチングが生まれました。
SELECT n2.name FROM MATCH (n1) -> (n2) WHERE n1.name = “アリス”
このクエリはOracle Graphがサポートする「PGQL」の文法で書かれています。SQLに似ていますが、MATCHキーワードのあとにパターンマッチングが続いているのが特徴です。
この表現方法は複数ホップを辿るパスを取得する、といったクエリを書く場合にも直感的で便利です。複数ホップを辿る場合でも、パスに含まれるエンティティについて「何かしらの関係のあるなにか」のような曖昧さを許したまま検索できることはグラフモデルに適したクエリの特徴です。それによって、アリスとボブの「何かしらの関係性」を検索することもできることになります。
SELECT n2.name AS n2, n3.name AS n3 FROM MATCH (n1) - (n2) - (n3) - (n4) WHERE n1.name = “アリス” AND n4.name = “ボブ”
もしもポチとモコが友達であれば、このクエリは結果を返します。
パターンマッチングの記法はグラフDBMSのNeo4jが開発した「Cypher」というクエリ言語によって普及したものです。現在では、Cypherの標準化を目指したopenCypherやGQL、既にSQLのISO標準として提案されているSQL/PGQ、OracleのPGQLなどのクエリ言語でパターンマッチング記法が使用されています。
グラフに適した実装とは?
それでは次に、DBMSの実装について考えてみます。グラフDBMSでは、上記のようなデータモデルとクエリ言語から、特定のノードからグラフを「辿る」といった操作が実行されることが想定されます。そこで、グラフ特化型のDBMSでは「辿る」処理に最適な実装が作られ、マルチモデルDBMSでは同様の実装を既存の仕組みに統合しました。
歴史的には、まず、ストレージ上のデザインとして、各ノードを格納する際にそのノードに接続されているエッジ(エッジが格納されているストレージ上の位置)のリストを一緒に格納しておくことで、エッジを辿る処理の性能を向上させる実装が登場しました。このデザインでは、接続されているエッジを辿るだけであれば、グラフのノードの数が多くなっても読み出しデータブロック数が変わらないというメリットがあります。これに対して、RDBMSでは同様の処理で表の結合が必要なため、表や索引のサイズが大きくなるにつれて読み出しブロック数が多くなる場合があります。もっとも、RDBMSには効率的な表の結合方法やシーケンシャルにデータを読み出せるといったメリットもあるため、集計などを含む実際のクエリでは性能がよく、グラフDBMSの性能は一長一短と考えられてきました。
近年では、10年前と比較して利用できるメモリ容量が大きくなったため、グラフ構造をメモリに展開するグラフDBMSが増えてきました。この場合にはCSR(Compressed Sparse Row)やCSC(Compressed Sparse Column)といった行列の圧縮方式を用いてグラフ構造を格納しておくことで、グラフを辿る処理を効率化しています。そして、グラフDBMSがデータ分析のためのプラットフォームとして使われるようになると、一方向に辿るだけでなく、二点間の経路を計算する、重要度の高いノードを抽出する、関係性を予測する、といった機能が求められるようになりました。これらのアルゴリズムを効率的に実行するために、現在ではグラフ構造をメモリに展開する方式が一般的になっています。
Neo4jはストレージ上にグラフ構造を格納するデザインを採用していますが、Graph Data Scienceライブラリを使うことでメモリ上にCSRで展開することができます。Oracle GraphのストレージはRDBMSでメモリはCSRです。その他、TigerGraph、MemgraphなどがCSRを、RedisGraphがCSCを採用しています。
万能の組み合わせはないという落とし穴
ここまでの議論から、グラフDBMSは、そのデータモデルだけでなく、クエリ言語、実装、といった全ての階層において他のDBMSと異なる独自性があることがわかるかと思います。グラフ特化型のDBMSではこれらの特徴はお互いに「最適な」ものとして組み合わされますが、その一方で、グラフDBMSの特徴のみを持ったDBMSは万能のツールではなくなるということに注意する必要があります。特化型のDBMSである限り、この注意書きは当然のようではあるのですが、実際のプロジェクトにおいてどれか一つの階層の制限を見落としてグラフDBMSを採用してしまっていたという事例を、筆者も目にしてきています。
一つの例では、ウェブ・アプリケーションの開発者がグラフの柔軟なデータモデルに魅力を感じてグラフDBMSを用いて開発を完了しましたが、データが増えるにつれてクエリのレスポンス性能が著しく劣化しました。グラフDBMSを使っていれば辿る処理の性能は劣化しにくいはずですが、このアプリケーションにはいくつかの集計処理が必要でした。索引の利用やモデルの変更も試みましたが、索引更新による性能の劣化や次数の高いノードの出現などの問題を解決できず、集計処理については別のDBMSを使うことになりました。この事例は、処理のワークロードやスケールに対してグラフのための「実装」が適していなかった例と言えます。
もう一つの例では、金融機関のデータ分析担当者がグラフのパターンマッチングを学習して分析に活用するという野心的なプロジェクトでしたが、グラフを作成するためのデータ準備のコストが掛かりすぎたためにアーキテクチャの見直しを迫られました。分析対象のデータはもともとRDBMSで管理されている日々のトランザクションだったため、これを逐次変換してグラフDBMSに同期するという運用には見積もり以上の工数がかかり、SI企業の支援なしに継続できないことが判明したのです。この事例は、グラフという「データモデル」の追加導入が難しかった(=高コストすぎた)例と言えそうです。
