GraphRAG 入門 #1 グラフ DB って何?を理解して Amazon Neptune Analytics を動かしてみた【Amazon Neptune】

GraphRAG 入門 #1 グラフ DB って何?を理解して Amazon Neptune Analytics を動かしてみた【Amazon Neptune】

GraphRAG 入門シリーズの 1 本目です。このシリーズでは、グラフ構造を検索に活用する RAG(GraphRAG)を AWS 上で実際に動かしながら検証していきます。その土台として、ノード・エッジ・プロパティといった基礎用語を身近な例と図で整理し、映画と俳優の多対多データを題材に、RDB の多段 JOIN とグラフ DB(openCypher)のクエリを比較してグラフ DB の利点と使い分けを解説します。後半は Amazon Neptune Analytics でグラフを作成し、ノートブック(Workbench)からのデータ投入、1〜3 ホップのクエリ実行、グラフの可視化までをハンズオン形式で体験します。
2026.07.20

はじめに

こんにちは!AI 事業本部のこーすけです。

最近グラフ DB を扱う機会があったのでまとめてみます。今まであまり深堀ったことが無かったので、今回きちんと基礎から勉強し直すことにしました。

本記事は GraphRAG 入門シリーズの 1 本目です。このシリーズでは、グラフ構造を検索に活用する RAG(GraphRAG)を、AWS 上で実際に動かしながら検証していきます。シリーズの予定は次の通りです。

  • #1(本記事): グラフ DB の基礎と Amazon Neptune Analytics のハンズオン
  • #2: GraphRAG と KGQA という用語の整理

https://dev.classmethod.jp/articles/graphrag-intro-02-graphrag-vs-kgqa/

  • #3: Bedrock Knowledge Bases のマネージド GraphRAG とベクトル RAG の精度比較

https://dev.classmethod.jp/articles/graphrag-intro-03-bedrock-kb-graphrag-vs-vector-rag/

  • #4: Neptune Database に Bulk Loader でナレッジグラフを構築

https://dev.classmethod.jp/articles/graphrag-intro-04-neptune-bulk-loader-knowledge-graph/

  • #5: byokg-rag で構築済みのナレッジグラフに自然言語で質問

https://dev.classmethod.jp/articles/graphrag-intro-05-byokg-rag-neptune-database/

その土台として、今回は グラフ DB とは何か を身近な例で理解し、AWS のフルマネージドなグラフ DB サービスである Amazon Neptune の概要を紹介します。最後に、実際に Neptune Analytics へダミーの映画データを投入してクエリしてみるところまで軽く体験してみます。Neptune や GraphRAG をしっかり触っていくのは #3 以降です。

グラフ DB とは

まず「グラフ」という言葉から整理します。ここでいうグラフは、棒グラフや折れ線グラフのようなチャートのことではなく、数学のグラフ理論に登場する、点と点を線でつないだ構造のことを指します。

身近な例でいうと、

  • 人物相関図(人と人を「友人」「同僚」といった線でつなぐ)
  • 電車の路線図(駅と駅を路線でつなぐ)
  • SNS のフォロー関係(ユーザー同士を「フォロー/フォロワー」でつなぐ)

これらはどれもグラフ構造です。「モノ」と「モノ同士のつながり」をそのまま表現できるのがグラフの特徴で、この構造をデータベースとして扱えるようにしたのがグラフ DB です。

RDB(リレーショナルデータベース)は、データを 表(テーブル) で管理する一方、グラフ DB は、データを ノード(点)エッジ(線) で管理するデータベースです。

まず、グラフ DB に登場する用語を整理します。

  • ノード: 人・モノ・出来事など、実世界のオブジェクトを表す
  • エッジ: ノード同士の関係を格納する
  • プロパティ: ノードやエッジが持つ属性(名前、日付、数量など)

たとえば「山田翔太さんが映画『星のかけら』に出演している」という事実は、グラフでは次のように表せます。

スクリーンショット 2026-07-16 173415

この「山田翔太」「星のかけら」にあたる点が ノード、「出演」にあたる線が エッジ です。
また、ノードに 「Actor」 のようなラベルや 「name: '山田翔太'」 のようなプロパティを、エッジに「出演」のようなラベルを持たせられるデータモデルをプロパティグラフと呼びます。グラフ DB のデータモデルには他に RDF という形式もありますが、本記事ではプロパティグラフを前提に話を進めます。

参考まで、こちらの動画が非常にわかりやすく、理解の助けとなりました。
https://www.youtube.com/watch?v=aaEJN_InxvE

具体例でグラフ 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 グラフを作成する

  1. Neptune コンソールを開き、左メニューの AnalyticsGraphs から グラフを作成 をクリックします
    スクリーンショット 2026-07-16 143756

  2. 以下の項目を指定して、グラフを作成します

  • 設定: グラフ名を入力します(例: 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 です。

スクリーンショット 2026-07-16 145604
スクリーンショット 2026-07-16 145827

作成完了まで 5 分ほどかかりました。
スクリーンショット 2026-07-16 150843

ステップ 2: ノートブックを作成してグラフに接続する

作成したグラフに対してクエリを実行するため、Neptune ノートブック(Workbench)を作成します。ノートブックの実体は SageMaker がホストする Jupyter で、SageMaker の通常利用料金が別途かかります。

  1. Neptune コンソールの左メニューから Notebooksノートブックを作成 を選択します
    スクリーンショット 2026-07-16 151735

  2. 以下の項目を指定して、ノートブックを作成します

  • 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 操作 を行うことができます。
  • ノートブックを作成 をクリックします

スクリーンショット 2026-07-16 151959
スクリーンショット 2026-07-16 152006

5 分ほどで作成が完了したので、早速「Jupyter lab」を開いてみます。
スクリーンショット 2026-07-16 152738

ノートブックが立ち上がるので、Python3 でカーネルを立ち上げます。
スクリーンショット 2026-07-16 153558

ステップ 3: 映画データを投入する

データを入れる前に、openCypher の基本的な作法を軽く押さえておきます。

  • ノードは ()、リレーションシップ(エッジ)は -[]- で表し、矢印 -> / <- で向きを示します
  • ラベルは : に続けて書きます(例: :Actor:Movie
  • プロパティは {key: value} の形でノードやリレーションシップに持たせます
  • () の中の am のような文字はその場限りの変数名で、WHERE 句や RETURN 句から使い回して参照します
  • 既存のパターンを検索するときは MATCH、新規作成は CREATE、結果を返すときは RETURN を使います
  • MATCHCREATERETURN などのキーワードは大文字で書くのが慣習ですが、必須ではありません
  • 複数件のデータをまとめて投入したいときは UNWIND を使います。リスト([{...}, {...}, ...])を 1 件ずつの行に展開し、AS row で受けた変数をそのまま後続の CREATE に渡せるので、同じ形の CREATE 文を件数分書かずに済みます。この後のデータ投入でも使っています

まずは何も投入していない状態で、グラフを覗いてみます。
openCypher を使うときは、セルの開始に 「%%oc」 のマジックコマンドを記載する必要があります。

%%oc
MATCH (n)
RETURN count(n) AS node_count

スクリーンショット 2026-07-16 155554

グラフが空であることを確認できたら、俳優・映画・出演関係のデータを投入します。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 本、格納できたことを確認します。
スクリーンショット 2026-07-16 160218
スクリーンショット 2026-07-16 160230

ステップ 4: クエリを実行して結果を確認する

「グラフ DB とは」の章で紹介した 3 つの問いを、実際にこのグラフへ投げてみます。

Q1. 「山田翔太さんの出演作は?」

%%oc
// 1ホップ: 山田翔太さんの出演作
MATCH (a:Actor {name: '山田翔太'})-[:ACTED_IN]->(m:Movie)
RETURN m.title, m.genre

このクエリの回答には「星のかけら」「銀河の果てまで」という結果が得られました。
スクリーンショット 2026-07-16 160521

Q2. 「山田翔太さんと共演したことがある俳優は?」

%%oc
// 2ホップ: 共演したことがある俳優
MATCH (a:Actor {name: '山田翔太'})-[:ACTED_IN]->(:Movie)<-[:ACTED_IN]-(co:Actor)
RETURN DISTINCT co.name

このクエリの回答には「高橋健一」、「佐藤美咲」、「中村隼人」という結果が得られました。

スクリーンショット 2026-07-16 160544

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

このクエリの回答には「真夜中のワルツ」、「笑う刑事」という結果が得られました。
スクリーンショット 2026-07-16 160626

元のデータを再掲します。
確認すると取得結果があっていることが確認できました!

映画 ジャンル 出演俳優
星のかけら 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 のエッジで結ばれたネットワークが描画されました。
スクリーンショット 2026-07-16 162421

次に、ステップ 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 の感覚が、ビジュアルとしても掴めました。

スクリーンショット 2026-07-16 162243

後片付け

Neptune Analytics のグラフは、クエリを実行していなくても存在しているだけで m-NCU 単位の時間課金が発生します。ノートブックも同様に SageMaker インスタンスの稼働課金がかかります。検証が終わったら忘れずにグラフは削除し、ノートブックは停止または削除しましょう。
なお、ステップ 1 で 削除保護の有効化 をデフォルト(有効)のままにしているため、グラフを削除する際は、先にグラフの設定を変更して削除保護を無効化する必要があります。

料金体系はこちらのブログに詳しく記載されているので参考にしてください。
https://dev.classmethod.jp/articles/amazon-neptune-analytics-pricing/

おわりに

今回はグラフ DB の基本から Amazon Neptune Analytics を軽く触ってみるところまでを扱いました。
次回(#2)は、このシリーズの中心テーマである GraphRAG と、その周辺用語(KGQA)を整理します。手を動かす検証は #3 以降で再開します。
最後まで読んでいただきありがとうございました!

参考文献

この記事をシェアする

関連記事