GraphRAG 入門 #1 グラフ DB って何?を理解して Amazon Neptune Analytics を動かしてみた【Amazon Neptune】
はじめに
こんにちは!AI 事業本部のこーすけです。
最近グラフ DB を扱う機会があったのでまとめてみます。今まであまり深堀ったことが無かったので、今回きちんと基礎から勉強し直すことにしました。
本記事は GraphRAG 入門シリーズの 1 本目です。このシリーズでは、グラフ構造を検索に活用する RAG(GraphRAG)を、AWS 上で実際に動かしながら検証していきます。シリーズの予定は次の通りです。
- #1(本記事): グラフ DB の基礎と Amazon Neptune Analytics のハンズオン
- #2: GraphRAG と KGQA という用語の整理
- #3: Bedrock Knowledge Bases のマネージド GraphRAG とベクトル RAG の精度比較
- #4: Neptune Database に Bulk Loader でナレッジグラフを構築
- #5: byokg-rag で構築済みのナレッジグラフに自然言語で質問
その土台として、今回は グラフ DB とは何か を身近な例で理解し、AWS のフルマネージドなグラフ DB サービスである Amazon Neptune の概要を紹介します。最後に、実際に Neptune Analytics へダミーの映画データを投入してクエリしてみるところまで軽く体験してみます。Neptune や GraphRAG をしっかり触っていくのは #3 以降です。
グラフ DB とは
まず「グラフ」という言葉から整理します。ここでいうグラフは、棒グラフや折れ線グラフのようなチャートのことではなく、数学のグラフ理論に登場する、点と点を線でつないだ構造のことを指します。
身近な例でいうと、
- 人物相関図(人と人を「友人」「同僚」といった線でつなぐ)
- 電車の路線図(駅と駅を路線でつなぐ)
- SNS のフォロー関係(ユーザー同士を「フォロー/フォロワー」でつなぐ)
これらはどれもグラフ構造です。「モノ」と「モノ同士のつながり」をそのまま表現できるのがグラフの特徴で、この構造をデータベースとして扱えるようにしたのがグラフ DB です。
RDB(リレーショナルデータベース)は、データを 表(テーブル) で管理する一方、グラフ DB は、データを ノード(点) と エッジ(線) で管理するデータベースです。
まず、グラフ DB に登場する用語を整理します。
- ノード: 人・モノ・出来事など、実世界のオブジェクトを表す
- エッジ: ノード同士の関係を格納する
- プロパティ: ノードやエッジが持つ属性(名前、日付、数量など)
たとえば「山田翔太さんが映画『星のかけら』に出演している」という事実は、グラフでは次のように表せます。

この「山田翔太」「星のかけら」にあたる点が ノード、「出演」にあたる線が エッジ です。
また、ノードに 「Actor」 のようなラベルや 「name: '山田翔太'」 のようなプロパティを、エッジに「出演」のようなラベルを持たせられるデータモデルをプロパティグラフと呼びます。グラフ DB のデータモデルには他に RDF という形式もありますが、本記事ではプロパティグラフを前提に話を進めます。
参考まで、こちらの動画が非常にわかりやすく、理解の助けとなりました。
具体例でグラフ DB の利点を見る
グラフ DB の利点をイメージしやすいように、多対多の構造を持つデータとして「映画と俳優」という架空のデータで比べてみます。
架空のデータ
| 映画 | ジャンル | 出演俳優 |
|---|---|---|
| 星のかけら | SF | 山田翔太, 佐藤美咲, 高橋健一 |
| 真夜中のワルツ | ドラマ | 佐藤美咲, 田中麗奈 |
| 笑う刑事 | コメディ | 高橋健一, 伊藤大輔, 渡辺さくら |
| 銀河の果てまで | SF | 山田翔太, 中村隼人 |
| 雨上がりの街で | ドラマ | 田中麗奈, 小林あかり, 伊藤大輔 |
これを RDB で持つなら、Movie テーブル・Actor テーブル・その 2 つを紐づける中間テーブル ActedIn の 3 つに分けます。
Movie
| movie_id | title | genre |
|---|---|---|
| 1 | 星のかけら | SF |
| 2 | 真夜中のワルツ | ドラマ |
| 3 | 笑う刑事 | コメディ |
| 4 | 銀河の果てまで | SF |
| 5 | 雨上がりの街で | ドラマ |
Actor
| actor_id | name |
|---|---|
| 1 | 山田翔太 |
| 2 | 佐藤美咲 |
| 3 | 高橋健一 |
| 4 | 田中麗奈 |
| 5 | 伊藤大輔 |
| 6 | 渡辺さくら |
| 7 | 中村隼人 |
| 8 | 小林あかり |
ActedIn(俳優と映画を紐づける中間テーブル)
| actor_id | movie_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 2 | 2 |
| 4 | 2 |
| 3 | 3 |
| 5 | 3 |
| 6 | 3 |
| 1 | 4 |
| 7 | 4 |
| 4 | 5 |
| 5 | 5 |
| 8 | 5 |
ActedIn を見ると、movie_id=1(星のかけら)に actor_id=1,2,3 の 3 人が紐づいていて、これが「1 つの映画に複数の俳優が出演する」という多対多の関係を表現しています。
これから見ていく共演俳優の検索で ActedIn を 2 回 JOIN する理由も、この中間テーブルの構造から来ています。
実際に目的となる情報を得るためのクエリを見ていきましょう。
「山田翔太さんの出演作」が知りたい場合、
SELECT m.title
FROM Actor a
JOIN ActedIn ai ON a.actor_id = ai.actor_id
JOIN Movie m ON ai.movie_id = m.movie_id
WHERE a.name = '山田翔太';
となります。
では「山田翔太さんと共演したことがある俳優は?」だとどうでしょうか。
同じ ActedIn テーブルを 2 回使う 自己結合(self-join) が必要になります。
SELECT DISTINCT a2.name
FROM Actor a1
JOIN ActedIn ai1 ON a1.actor_id = ai1.actor_id
JOIN ActedIn ai2 ON ai1.movie_id = ai2.movie_id
JOIN Actor a2 ON ai2.actor_id = a2.actor_id
WHERE a1.name = '山田翔太' AND a2.actor_id <> a1.actor_id;
さらに、「その共演俳優が出演した、別ジャンルの映画は?」まで踏み込むと、JOIN が 5 つに増えます。
SELECT DISTINCT a2.name, m2.title, m2.genre
FROM Actor a1
JOIN ActedIn ai1 ON a1.actor_id = ai1.actor_id
JOIN Movie m1 ON ai1.movie_id = m1.movie_id
JOIN ActedIn ai2 ON ai1.movie_id = ai2.movie_id
JOIN Actor a2 ON ai2.actor_id = a2.actor_id AND a2.actor_id <> a1.actor_id
JOIN ActedIn ai3 ON a2.actor_id = ai3.actor_id
JOIN Movie m2 ON ai3.movie_id = m2.movie_id AND m2.genre <> m1.genre
WHERE a1.name = '山田翔太';
関係を 1 段たどるごとに JOIN が増えていくのが分かるかと思います。
このようにたどる関係(ホップ数と呼びます)が増えるほど多段の JOIN 操作が発生するため、SQL はどんどん複雑になり、実行速度も落ちていきます。
一方グラフ DB は、ノード自身が隣接するノードへの参照を直接持っています。「山田翔太」のノードから出ているエッジをそのまま辿ればよいので、関係をたどるコストはデータ全体の量にほとんど影響されず、ホップ数を増やしても探索が高速なままです。データの持ち方そのものが関係の探索に向いている、というのがグラフ DB の利点です。
ただし、グラフ DB が常に RDB より優れているわけではありません。
- 全件の集計や大量データの一括更新、単純な一覧取得 → RDB のほうが得意
- 「A と B はどうつながっているか」「A から n ホップ先に何があるか」といった関係の探索が主役の処理 → グラフ DB が得意
このような使い分けになるかと思います。今回の映画データのように「つながりをたどること」が問いの中心になるケースでは、グラフ DB のほうが向いています。
Amazon Neptune とは
Amazon Neptune は、AWS が提供するフルマネージドのグラフデータベースサービスです。サーバーの管理やパッチ適用、バックアップといった面倒な運用作業を AWS におまかせしながら、グラフ DB を使えます。
Neptune には 2 つのサービスがあります。
| Neptune Database | Neptune Analytics | |
|---|---|---|
| 得意なこと | 日々の読み書き(トランザクション処理) | 大量データの分析・アルゴリズム処理 |
| イメージ | Web アプリ・業務システムの裏側で常時稼働する DB | 手元のグラフ全体を高速に分析するエンジン |
| 用途の例 | ユーザーの関係管理、レコメンド、権限管理 | 最短経路探索、コミュニティ検出、ナレッジグラフ構築 |
クエリ言語は主に 3 種類に対応しています。
- Gremlin(Apache TinkerPop): 手続き的にグラフをたどる記法
- openCypher: 本記事で使った
MATCHのような、宣言的で読みやすい記法 - SPARQL: RDF(Resource Description Framework)形式のデータ向け
Gremlin と openCypher は、ここまで本記事で扱ってきた「プロパティグラフ」のデータモデルを扱い、SPARQL は「RDF」という別のデータモデルを扱います。同じ Neptune でもどちらのモデルで作るかで対応言語が変わってくるので、最初に押さえておきたいポイントです。
代表的なユースケースとしては、EC サイトのレコメンド、不正検知、ソーシャルグラフ、ナレッジグラフ、GraphRAG などが挙げられます。
実際に Amazon Neptune Analytics で動かしてみる
ここまで使ってきた映画・俳優データを実際に Neptune Analytics に投入して、クエリを実行するところまで軽く体験してみます。
Neptune には Database 版と Analytics 版がありますが、今回は検証のための数十ノード程度の小さいデータを試したいので、コンソールからすぐにグラフを作れる Neptune Analytics を使います。
ステップ 1: Neptune Analytics グラフを作成する
-
Neptune コンソールを開き、左メニューの Analytics → Graphs から グラフを作成 をクリックします

-
以下の項目を指定して、グラフを作成します
- 設定: グラフ名を入力します(例:
movie-actor-demo) - データソース: 空のグラフを作成 を選択。メモリ最適化 Neptune キャパシティーユニット(m-NCU) は最小の 16 m-NCU を選択すれば OK です
- 可用性設定: 検証用途なのでレプリカは不要です。カスタム数のレプリカを使用 を選び、レプリカの数を 0 にします。
- ネットワークとセキュリティ:
- パブリック接続を有効化 はチェックしません(インターネット経由のアクセスは無効のまま、IAM 認証必須のプライベート運用にします)
- プライベートエンドポイントをセットアップ にチェックし、ノートブックを置く VPC・サブネット・VPC セキュリティグループ(デフォルト VPC の
defaultセキュリティグループで OK)を選択します。
- ベクトル検索設定: 今回は埋め込みベクトルを使わないので ベクトルディメンションなし を選択します。指定する際は、投入予定の埋め込みモデルの出力次元数に合わせて事前に指定する必要があります(例: Amazon Titan Text Embeddings V2 を使うのであればデフォルトで 1024 次元を出力するのでその場合は 1024 を指定します)。
- 詳細設定: 削除保護の有効化 はデフォルトのままで OK です。


作成完了まで 5 分ほどかかりました。

ステップ 2: ノートブックを作成してグラフに接続する
作成したグラフに対してクエリを実行するため、Neptune ノートブック(Workbench)を作成します。ノートブックの実体は SageMaker がホストする Jupyter で、SageMaker の通常利用料金が別途かかります。
-
Neptune コンソールの左メニューから Notebooks → ノートブックを作成 を選択します

-
以下の項目を指定して、ノートブックを作成します
- Neptune サービス: Analytics を選択
- グラフ: ステップ 1 で作成したグラフ(例:
g-xxxxxxxx (movie-actor-demo))をプルダウンから選択します。 - ノートブックインスタンスタイプ: 今回は検証用のため ml.t2.medium を選択しました
- ノートブック名:
aws-neptune-に続けて任意の名前を入力します(例:aws-neptune-movie-actor-demo) - IAM ロール名: 初回は IAM ロールを作成 を選択します(ロール名は自動で
AWSNeptuneNotebookRole-<入力名>になります) - ライフサイクル設定: Neptune のデフォルト設定を使用する のままで OK です
- ネットワーク設定: VPC・サブネットはステップ 1 で作成したグラフと同じものが自動的に選択されます。セキュリティグループはステップ 1 と同じ
defaultを選択し、インターネットアクセスは Amazon SageMaker を介した直接アクセス にします。今回の VPC には NAT ゲートウェイを用意していませんが、「Amazon SageMaker を介した直接アクセス」を選ぶと SageMaker が管理する ENI が用意されるので、ノートブック上でのpip install等によるライブラリの追加インストールや Git 操作 を行うことができます。 - ノートブックを作成 をクリックします


5 分ほどで作成が完了したので、早速「Jupyter lab」を開いてみます。

ノートブックが立ち上がるので、Python3 でカーネルを立ち上げます。

ステップ 3: 映画データを投入する
データを入れる前に、openCypher の基本的な作法を軽く押さえておきます。
- ノードは
()、リレーションシップ(エッジ)は-[]-で表し、矢印->/<-で向きを示します - ラベルは
:に続けて書きます(例::Actor、:Movie) - プロパティは
{key: value}の形でノードやリレーションシップに持たせます ()の中のaやmのような文字はその場限りの変数名で、WHERE句やRETURN句から使い回して参照します- 既存のパターンを検索するときは
MATCH、新規作成はCREATE、結果を返すときはRETURNを使います MATCH・CREATE・RETURNなどのキーワードは大文字で書くのが慣習ですが、必須ではありません- 複数件のデータをまとめて投入したいときは
UNWINDを使います。リスト([{...}, {...}, ...])を 1 件ずつの行に展開し、AS rowで受けた変数をそのまま後続のCREATEに渡せるので、同じ形のCREATE文を件数分書かずに済みます。この後のデータ投入でも使っています
まずは何も投入していない状態で、グラフを覗いてみます。
openCypher を使うときは、セルの開始に 「%%oc」 のマジックコマンドを記載する必要があります。
%%oc
MATCH (n)
RETURN count(n) AS node_count

グラフが空であることを確認できたら、俳優・映画・出演関係のデータを投入します。3 つのセルに分けて実行します。
%%oc
UNWIND [
{id: 1, name: '山田翔太'},
{id: 2, name: '佐藤美咲'},
{id: 3, name: '高橋健一'},
{id: 4, name: '田中麗奈'},
{id: 5, name: '伊藤大輔'},
{id: 6, name: '渡辺さくら'},
{id: 7, name: '中村隼人'},
{id: 8, name: '小林あかり'}
] AS row
CREATE (:Actor {actor_id: row.id, name: row.name})
%%oc
UNWIND [
{id: 1, title: '星のかけら', genre: 'SF'},
{id: 2, title: '真夜中のワルツ', genre: 'ドラマ'},
{id: 3, title: '笑う刑事', genre: 'コメディ'},
{id: 4, title: '銀河の果てまで', genre: 'SF'},
{id: 5, title: '雨上がりの街で', genre: 'ドラマ'}
] AS row
CREATE (:Movie {movie_id: row.id, title: row.title, genre: row.genre})
%%oc
UNWIND [
{actor: 1, movie: 1}, {actor: 2, movie: 1}, {actor: 3, movie: 1},
{actor: 2, movie: 2}, {actor: 4, movie: 2},
{actor: 3, movie: 3}, {actor: 5, movie: 3}, {actor: 6, movie: 3},
{actor: 1, movie: 4}, {actor: 7, movie: 4},
{actor: 4, movie: 5}, {actor: 5, movie: 5}, {actor: 8, movie: 5}
] AS row
MATCH (a:Actor {actor_id: row.actor}), (m:Movie {movie_id: row.movie})
CREATE (a)-[:ACTED_IN]->(m)
ノードが計 13 個(俳優 8 + 映画 5)、エッジ(ACTED_IN)が延べ出演数の計 13 本、格納できたことを確認します。


ステップ 4: クエリを実行して結果を確認する
「グラフ DB とは」の章で紹介した 3 つの問いを、実際にこのグラフへ投げてみます。
Q1. 「山田翔太さんの出演作は?」
%%oc
// 1ホップ: 山田翔太さんの出演作
MATCH (a:Actor {name: '山田翔太'})-[:ACTED_IN]->(m:Movie)
RETURN m.title, m.genre
このクエリの回答には「星のかけら」「銀河の果てまで」という結果が得られました。

Q2. 「山田翔太さんと共演したことがある俳優は?」
%%oc
// 2ホップ: 共演したことがある俳優
MATCH (a:Actor {name: '山田翔太'})-[:ACTED_IN]->(:Movie)<-[:ACTED_IN]-(co:Actor)
RETURN DISTINCT co.name
このクエリの回答には「高橋健一」、「佐藤美咲」、「中村隼人」という結果が得られました。

Q3. 「その共演俳優が出演した、別ジャンルの映画は?」
%%oc
// 3ホップ: 共演俳優が出演した別ジャンルの映画
MATCH (a:Actor {name: '山田翔太'})-[:ACTED_IN]->(m1:Movie)<-[:ACTED_IN]-(co:Actor)-[:ACTED_IN]->(m2:Movie)
WHERE m2.genre <> m1.genre
RETURN DISTINCT co.name, m2.title, m2.genre
このクエリの回答には「真夜中のワルツ」、「笑う刑事」という結果が得られました。

元のデータを再掲します。
確認すると取得結果があっていることが確認できました!
| 映画 | ジャンル | 出演俳優 |
|---|---|---|
| 星のかけら | SF | 山田翔太, 佐藤美咲, 高橋健一 |
| 真夜中のワルツ | ドラマ | 佐藤美咲, 田中麗奈 |
| 笑う刑事 | コメディ | 高橋健一, 伊藤大輔, 渡辺さくら |
| 銀河の果てまで | SF | 山田翔太, 中村隼人 |
| 雨上がりの街で | ドラマ | 田中麗奈, 小林あかり, 伊藤大輔 |
ステップ 5: グラフを可視化してみる
最後に、Workbench に備わっているビジュアライズ機能でグラフそのものを見てみます。文字の結果だけだと分かりにくいノード間の関係のつながりが、グラフで表示することでとても直感的になります。
まずはグラフの全体像を見てみます。
%%oc -d {"Actor":"name","Movie":"title"}
MATCH (a:Actor)-[r:ACTED_IN]->(m:Movie)
RETURN a, r, m
少し小さくて見づらいですが、8 人の俳優(水色の丸)と 5 つの映画(黄色の丸)が ACTED_IN のエッジで結ばれたネットワークが描画されました。

次に、ステップ 4 の 3 ホップ目のクエリを同じように実行し、Graph タブに切り替えてみます。
%%oc -d {"Actor":"name","Movie":"title"}
MATCH p=(a:Actor {name: '山田翔太'})-[:ACTED_IN]->(m1:Movie)<-[:ACTED_IN]-(co:Actor)-[:ACTED_IN]->(m2:Movie)
WHERE m2.genre <> m1.genre
RETURN p
山田翔太さんを起点に矢印をたどって、共演俳優を経由し、別ジャンルの映画までつながっていく経路がそのまま線として見えます。関係をたどるというグラフ DB の感覚が、ビジュアルとしても掴めました。

後片付け
Neptune Analytics のグラフは、クエリを実行していなくても存在しているだけで m-NCU 単位の時間課金が発生します。ノートブックも同様に SageMaker インスタンスの稼働課金がかかります。検証が終わったら忘れずにグラフは削除し、ノートブックは停止または削除しましょう。
なお、ステップ 1 で 削除保護の有効化 をデフォルト(有効)のままにしているため、グラフを削除する際は、先にグラフの設定を変更して削除保護を無効化する必要があります。
料金体系はこちらのブログに詳しく記載されているので参考にしてください。
おわりに
今回はグラフ DB の基本から Amazon Neptune Analytics を軽く触ってみるところまでを扱いました。
次回(#2)は、このシリーズの中心テーマである GraphRAG と、その周辺用語(KGQA)を整理します。手を動かす検証は #3 以降で再開します。
最後まで読んでいただきありがとうございました!
参考文献
- Creating a new Neptune Analytics graph using the AWS Management Console(AWS) — グラフ作成画面の各設定項目(データソース、m-NCU、ネットワーク、ベクトル検索設定など)の解説
- Vector indexing in Neptune Analytics(AWS) — ベクトル検索インデックスは作成時にしか設定できない点、次元数の仕様
- Using magics in Amazon Neptune notebooks(AWS) —
%%ocなど各種マジックコマンドの一覧 - Using Amazon Neptune with graph notebooks(AWS) — ノートブック(Workbench)の概要
- aws/graph-notebook(GitHub) —
-d/--display-propertyによるノード表示名のカスタマイズ例(Air-Routes-openCypher.ipynb 等) - Connect a Notebook Instance in a VPC to External Resources(AWS) — SageMaker ノートブックが NAT ゲートウェイなしでインターネットに出られる仕組み(2 本のネットワークインターフェース)
- Understanding Amazon SageMaker notebook instance networking configurations and advanced routing options(AWS Blog)
- Managed Graph Database – Getting Started with Amazon Neptune(AWS) — Neptune Database / Analytics の全体像
- Announcing openCypher for Amazon Neptune(AWS Blog) — Gremlin と openCypher の関係





