グラフデータベース入門: Amazon NeptuneとGremlinを使ったトラバーサル
はじめに
先日、「Graph Database 入門 ~Amazon Neptune によるデータの「つながり」探索~」 というタイトルの勉強会を開催しました(イベントリンク)。このブログ記事では、そのプレゼンテーションの内容を共有し、グラフデータベース を理解し、Amazon Neptune の機能を探るお手伝いをしたいと思います。
この記事で使用されたサンプルコードは、GitHub リポジトリ で見つけることができます。
前提条件
対象読者
この投稿は以下の読者を対象としています。
- グラフデータベース や Amazon Neptune に興味がある。
- リレーショナルデータベース や AWS の基本的な理解がある。
目的
- グラフデータベース と Amazon Neptune の概要を提供。
- Apache TinkerPop Gremlin を使用したグラフのトラバーサルを実演。
グラフデータベースとは
キーコンセプト
グラフデータベースは、エンティティ間のリレーションシップの保存とクエリの最適化に特化しています。リレーショナルデータベースや NoSQL データベースとは異なり、グラフデータベースは関係が複雑でパフォーマンスが重要なシナリオで優れています。
グラフデータベースでは:
- 頂点 (ノード) はエンティティを表します。
- エッジ は頂点間の関係を定義します。
- 頂点とエッジの両方にプロパティ(キーと値のペア)を持たせることができます。
- 頂点とエッジにラベルが付けられたグラフは プロパティグラフ と呼ばれます。
グラフデータベースの用途
ソーシャルネットワーク
グラフデータベースは、Facebook や LinkedIn のようなプラットフォームで見られるように、人々の間の関係をモデル化します。
ナレッジグラフ
これらのグラフは、データに文脈的な意味を追加し、例えば「リンゴ」という果物と「Apple Inc.」という会社を区別します。Introducing the Knowledge Graph: things, not strings で、ナレッジグラフという用語が広く採用されました。
アイデンティティグラフ
アイデンティティグラフは、複数のユーザー識別子をプラットフォーム間でリンクし、包括的なユーザー行動分析を可能にします。有名なアプリケーションの例として、Customer 360 が挙げられ、すべての接点にわたる顧客の統一ビューを提供します。
不正検出
不正グループは正当な取引と違法行為を混在させることが多く、検出が困難です。グラフデータベースは、関係を視覚化し、共通属性を特定することで、不正検出に優れています。これにはクレジットカード番号、電話番号、社会保障番号などが含まれます。
実世界の例:パナマ文書
パナマ文書の調査では、グラフデータベースを使用して漏洩した文書を効率的に分析しました。詳細は Forbes の記事 をご覧ください。
データベースの比較:グラフ DB、RDB、NoSQL
特徴 | グラフ DB | RDB | NoSQL |
---|---|---|---|
データ構造 | グラフ | テーブル | 多様 |
スキーマ | ゆるい | 厳密 | ゆるい |
用途 | リレーションシップ | エンティティストレージ | 多様 |
クエリ方法 | トラバーサル | ジョイン / サブクエリ | 多様 |
パフォーマンス | 非常に高い | 平均 1 | 非常に高い |
クエリ言語 | Gremlin SPARQL openCypher 2 Cypher GQL | SQL | - |
- リレーショナルデータベース (RDB) のパフォーマンスは、Amazon Redshift のようなカラム型データベースを使用することで向上します。
- Amazon Neptune は、エンジンリリース 1.1.1.0 以降、openCypher を本番環境でサポート しています。ただし、Cypher および GQL は現在サポートされていません。
Amazon Neptune の紹介
Amazon Neptune は、完全マネージド型のグラフデータベースサービスであり、数十億のリレーションシップをミリ秒単位でクエリできます。高可用性、耐久性、低レイテンシーを提供します。
主な特徴
- クラスターのスケーラビリティ:最大 15 のリードレプリカ。
- 自動スケーリング:ストレージは最大 64 TiB まで自動拡張。
- データの冗長性:3 つのアベイラビリティゾーンにまたがる 6 コピーを保持。
Neptune におけるトランザクション
Neptune は、リードオンリークエリとミューテーションクエリをそれぞれ異なるトランザクション分離レベルで管理します。詳細はこちらのドキュメント をご参照ください。
リードオンリークエリ
リードオンリークエリでは、Neptune は スナップショット分離を使用して マルチバージョンコンカレンシーコントロール (MVCC) を実現します。これにより、ダーティリード、非再現リード、およびファントムリードがスナップショットを用いて防止されます。
ミューテーションクエリ
ミューテーションクエリでは、Neptune は READ COMMITTED 分離を使用してダーティリードを防ぎます。さらに、Neptune は読み取られるレコードセットをロックし、非再現リードやファントムリードを回避します。
Gremlin を使用したクエリ
Amazon Neptune は、オープンソースのグラフトラバーサル言語である Gremlin によるクエリをサポートしています。また、Graph Notebook を使用して、クエリや可視化を行うことも可能です。
トラバーサルの例
サンプルグラフデータ
この例では、頂点に age プロパティ を持ち、エッジに weight プロパティ を持つグラフを使用します。
上記のサンプルデータをロードするには、以下のコマンドを使用してください。%%gremlin
は、Neptune Workbench 専用の Jupyter Notebook マジックコマンドです。
%%gremlin
// 既存データの削除
g.V().drop()
%%gremlin
// 頂点の追加
g.addV('person').property(id, 'A').property('age', 30)
.addV('person').property(id, 'B').property('age', 25)
.addV('person').property(id, 'C').property('age', 35)
.addV('person').property(id, 'D').property('age', 20)
.addV('person').property(id, 'E').property('age', 18)
.addV('person').property(id, 'F').property('age', 25)
.addV('person').property(id, 'G').property('age', 10)
.addV('person').property(id, 'H').property('age', 15)
%%gremlin
// エッジの追加
g.V('A').addE('know').to(g.V('B')).property('weight', 1.0)
.V('A').addE('know').to(g.V('C')).property('weight', 0.9)
.V('A').addE('know').to(g.V('H')).property('weight', 0.5)
.V('B').addE('know').to(g.V('D')).property('weight', 0.8)
.V('B').addE('know').to(g.V('E')).property('weight', 0.4)
.V('C').addE('know').to(g.V('F')).property('weight', 0.5)
.V('C').addE('know').to(g.V('G')).property('weight', 0.6)
.V('D').addE('know').to(g.V('E')).property('weight', 0.7)
.V('H').addE('know').to(g.V('E')).property('weight', 1.0)
.V('H').addE('know').to(g.V('G')).property('weight', 1.0)
例1: 全ての頂点を取得
%%gremlin
// 頂点の抽出
g.V()
.project('id', 'properties') // 投影
.by(id()).by(valueMap()) // valueMap は頂点のプロパティを返します。
結果:
Row | データ |
---|---|
1 | {'id': 'A', 'properties': {'age': [30]}} |
2 | {'id': 'B', 'properties': {'age': [25]}} |
3 | {'id': 'C', 'properties': {'age': [35]}} |
4 | {'id': 'D', 'properties': {'age': [20]}} |
5 | {'id': 'E', 'properties': {'age': [18]}} |
6 | {'id': 'F', 'properties': {'age': [25]}} |
7 | {'id': 'G', 'properties': {'age': [10]}} |
8 | {'id': 'H', 'properties': {'age': [15]}} |
例2: 接続のトラバーサル
25 歳以上で ‘A’ に接続された 2 階層以内の人物を取得します。
%%gremlin
// A から 2階層以内で接続されている 25 歳以上の人物を抽出
g.V('A')
.repeat(outE().inV()).times(2).emit() // 隣接する頂点のトラバーサルを 2 回繰り返す
.has('age', gte(25)) // 25 歳以上
.project('id', 'age')
.by(id()).by(values('age'))
結果:
Row | データ |
---|---|
1 | {'id': 'B', 'age': 25} |
2 | {'id': 'C', 'age': 35} |
3 | {'id': 'F', 'age': 25} |
例3: 重みでフィルタリング
重みの積が 0.5 を超える人物を見つけます。
%%gremlin
// A から開始し、重みの積が 0.5 を超える頂点を抽出
g.withSack(1.0f).V('A') // 一時的なデータを格納するための Sack を使用
.repeat(outE().sack(mult).by('weight').inV().simplePath()).emit()
.where(sack().is(gt(0.5))) // Sack 値が 0.5 を超える
.dedup() // 重複を削除
.project('id', 'weight')
.by(id).by(sack())
結果:
Row | データ |
---|---|
1 | {'id': 'B', 'weight': 1.0} |
2 | {'id': 'C', 'weight': 0.9} |
3 | {'id': 'D', 'weight': 0.8} |
4 | {'id': 'G', 'weight': 0.54} |
5 | {'id': 'E', 'weight': 0.5599…} |
グラフの可視化
Neptune Workbench は、グラフをインタラクティブに可視化するツールを提供しています。詳細は公式ドキュメントをご参照ください:Neptune Visualization。
以下のコマンドを実行して、グラフを可視化します。
%%gremlin -d T.id -de weight
// -d は表示する頂点プロパティを指定
// -de は表示するエッジプロパティを指定
// 例3のトラバーサルを実行
g.withSack(1.0f).V('A') // Sack を使用して一時データを保存
.repeat(outE().sack(mult).by('weight').inV().simplePath()).emit() // 重み付きエッジのトラバース
.where(sack().is(gt(0.5))) // Sack 値が 0.5 を超えるパスをフィルタリング
.dedup() // 重複パスを削除
.path() // パスデータを抽出
.by(elementMap()) // 頂点およびエッジのプロパティを表示
出力例:
Row | データ |
---|---|
1 | path[{<T.id: 1>: 'A', <T.label: 4>: 'person', 'age': 30}, {<T.id: 1>: '8ebe47fa-901b-c6d3-a11f-0a9bf0ce8aa2', <T.label: 4>: 'know', <Direction.IN: 2>: {<T.id: 1>: 'B', <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 'A', <T.label: 4>: 'person'}, 'weight': 1.0}, {<T.id: 1>: 'B', <T.label: 4>: 'person', 'age': 25}] |
2 | path[{<T.id: 1>: 'A', <T.label: 4>: 'person', 'age': 30}, {<T.id: 1>: '7abe47fa-901c-c394-4bed-6dce7defa3f9', <T.label: 4>: 'know', <Direction.IN: 2>: {<T.id: 1>: 'C', <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 'A', <T.label: 4>: 'person'}, 'weight': 0.9}, {<T.id: 1>: 'C', <T.label: 4>: 'person', 'age': 35}] |
実行後、Neptune Workbench の Graph
タブに移動して結果を可視化します。このインターフェースは、ドラッグやズームイン・ズームアウト操作をサポートしており、直感的にグラフを探索できます。
まとめ
グラフデータベースは、リレーションシップの処理において大きな利点を提供し、Amazon Neptune は AWS 上でそのようなデータベースをシームレスに展開できます。Gremlin を使用することで、複雑なデータリレーションシップを効率的にトラバースおよび分析できます。
グラフデータベースを探求するなら、Amazon Neptune は素晴らしいスタート地点です。
Happy Coding! 🚀
Appendix 1: リレーショナルデータベース (RDB) におけるツリー構造の表現
ツリー構造は、リレーショナルデータベース (RDB) で様々なアプローチを使用してモデル化できます。一部のシナリオでは、グラフデータベースを使用する必要がない場合もあります。ただし、Naive Tree モデル は多くのケースで非最適であることが多く、“SQL Antipatterns” で紹介されています。
モデル | 説明 | 長所 | 短所 |
---|---|---|---|
Naive Tree | t1.id = t2.parent_id |
|
|
Path Enumeration | path LIKE '1/2%' |
|
|
Nested Sets | Left > 1 AND Right < 6 |
|
|
Closure Table | ツリー用の別テーブル |
|
|
各アプローチにはそれぞれの長所と制限があり、アプリケーションの具体的な要件に依存して選択が決まります。
Appendix 2: トランザクション
ダーティリード (Dirty Read)
Tx1 が行を更新し、Tx2 がその行を読み、Tx1 が失敗またはロールバックする場合、Tx2 は反映されていないデータを読むことになります。
非再現リード (Non-repeatable Read)
Tx1 が行を読み、Tx2 がその行を更新または削除してコミットし、Tx1 が再度その行を読む場合、Tx1 は前回とは異なるコミット済みデータを読むことになります。
ファントムリード (Phantom Read)
Tx1 がレコードを読み、Tx2 がいくつかの行を挿入または削除してコミットし、Tx1 が再度その行を読む場合、Tx1 は前回とは異なる行を読むことになります。
分離レベル
レベル | ダーティリード | 非再現リード | ファントムリード |
---|---|---|---|
READ UNCOMMITTED | 生じる | 生じる | 生じる |
READ COMMITTED | 生じない | 生じる | 生じる |
REPEATABLE READ | 生じない | 生じない | 生じる |
SERIALIZABLE | 生じない | 生じない | 生じない |
Appendix 3: リレーショナルデータベース (RDB) を使用したクエリ
RDB にデータを格納する場合、リレーションシップをクエリすることはますます複雑になる可能性があります。SQL クエリはしばしば複雑になり、行と列の構造内で相互に接続されたデータを表現することは直感的ではありません。