はじめに
前編では Neptune クラスターの構築、OWL オントロジーの設計、データ投入まで行いました。
後編では、セッションマネージャー経由で EC2 から直接 SPARQL を叩ける環境 を構築し、様々なクエリ例を紹介します。
構成図
マネジメントコンソール
│ Session Manager
▼
┌──────────────────────────────────────────────────┐
│ VPC (us-east-1) │
│ │
│ ┌─────────────────┐ ┌───────────────────┐ │
│ │ EC2 (t3.micro) │─8182→│ Neptune (SPARQL) │ │
│ │ sparql.sh │ │ 472 triples │ │
│ └─────────────────┘ └───────────────────┘ │
│ │
└──────────────────────────────────────────────────┘
Step 7: EC2 インスタンスの構築
IAM ロール(SSM用)
aws iam create-role --role-name NeptuneEC2SSMRole \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]
}'
aws iam attach-role-policy --role-name NeptuneEC2SSMRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name NeptuneEC2SSMProfile
aws iam add-role-to-instance-profile \
--instance-profile-name NeptuneEC2SSMProfile \
--role-name NeptuneEC2SSMRole
EC2 起動
# 最新 Amazon Linux 2023 AMI を取得
AMI_ID=$(aws ec2 describe-images --owners amazon \
--filters "Name=name,Values=al2023-ami-2023.*-x86_64" "Name=state,Values=available" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text --region us-east-1)
# Neptune と同じ VPC/SG に起動
aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type t3.micro \
--subnet-id <SAME_SUBNET_AS_NEPTUNE> \
--security-group-ids <SG_ID> \
--iam-instance-profile Name=NeptuneEC2SSMProfile \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=neptune-sparql-client}]' \
--metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
--region us-east-1
SSH キーは不要です。セッションマネージャー経由でアクセスするため、インバウンドのポート22を開ける必要もありません。
SSM 接続確認
aws ec2 wait instance-running --instance-ids <INSTANCE_ID> --region us-east-1
# SSM に登録されるまで待機(通常1分以内)
aws ssm describe-instance-information \
--filters "Key=InstanceIds,Values=<INSTANCE_ID>" \
--query 'InstanceInformationList[0].PingStatus' --output text \
--region us-east-1
# => "Online" になれば OK
Step 8: SPARQL スクリプトの配置
EC2 上に sparql.sh を配置して、ワンライナーで SPARQL が実行できるようにします。
スクリプト内容
sparql.sh
#!/bin/bash
# Neptune SPARQL Query Tool
NEPTUNE="https://<YOUR_NEPTUNE_ENDPOINT>:8182/sparql"
if [ -z "$1" ]; then
echo "=== Neptune SPARQL Query Tool ==="
echo ""
echo "Usage: ./sparql.sh '<SPARQL query>'"
echo ""
echo "--- Sample Queries ---"
echo ""
echo '# Count all triples'
echo "./sparql.sh 'SELECT (COUNT(*) as ?count) WHERE { ?s ?p ?o }'"
echo ""
echo '# List all customers by LTV'
echo "./sparql.sh 'PREFIX : <http://example.org/ecommerce#> SELECT ?name ?tier ?ltv WHERE { ?c a :Customer ; :firstName ?fname ; :lastName ?lname ; :hasMembership ?t ; :lifetimeValue ?ltv . BIND(CONCAT(?fname, \" \", ?lname) AS ?name) BIND(STRAFTER(STR(?t), \"#\") AS ?tier) } ORDER BY DESC(?ltv)'"
exit 1
fi
curl -s -X POST "$NEPTUNE" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: application/sparql-results+json" \
--data-urlencode "query=$1" | python3 -m json.tool
EC2 への配置(SSM 経由)
# ローカルで base64 エンコード
SCRIPT_B64=$(base64 < sparql.sh | tr -d '\n')
# SSM で配置
cat > ssm-deploy.json << EOF
{"commands": ["echo '${SCRIPT_B64}' | base64 -d > /home/ssm-user/sparql.sh", "chmod +x /home/ssm-user/sparql.sh"]}
EOF
aws ssm send-command \
--instance-ids <INSTANCE_ID> \
--document-name "AWS-RunShellScript" \
--parameters file://ssm-deploy.json \
--region us-east-1
Step 9: セッションマネージャーで SPARQL を実行
接続方法
- AWS マネジメントコンソール → EC2 → インスタンス
neptune-sparql-clientを選択 - 接続 → Session Manager → 接続
- シェルが開いたら:
cd /home/ssm-user
クエリ例
基本: 全トリプル数
./sparql.sh 'SELECT (COUNT(*) as ?count) WHERE { ?s ?p ?o }'
顧客一覧(LTV降順)
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> SELECT ?name ?tier ?ltv WHERE { ?c a :Customer ; :firstName ?fname ; :lastName ?lname ; :hasMembership ?t ; :lifetimeValue ?ltv . BIND(CONCAT(?fname, " ", ?lname) AS ?name) BIND(STRAFTER(STR(?t), "#") AS ?tier) } ORDER BY DESC(?ltv)'
結果:
{
"results": {
"bindings": [
{"name": {"value": "Frank Martinez"}, "tier": {"value": "Platinum"}, "ltv": {"value": "4500.0"}},
{"name": {"value": "Bob Johnson"}, "tier": {"value": "Platinum"}, "ltv": {"value": "3200.75"}},
{"name": {"value": "Alice Williams"}, "tier": {"value": "Gold"}, "ltv": {"value": "1800.0"}},
...
]
}
}
Gold メンバー
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> SELECT ?name ?email ?ltv WHERE { ?c a :Customer ; :hasMembership :Gold ; :firstName ?fname ; :lastName ?lname ; :email ?email ; :lifetimeValue ?ltv . BIND(CONCAT(?fname, " ", ?lname) AS ?name) } ORDER BY DESC(?ltv)'
カテゴリ別売上(OWL クラス階層を活用)
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT ?category (SUM(?lineTotal) AS ?revenue) WHERE { ?item a :OrderItem ; :containsProduct ?prod ; :lineTotal ?lineTotal . ?prod rdf:type ?type ; :category ?category . FILTER(?type IN (:ElectronicsProduct, :FurnitureProduct, :BookProduct)) } GROUP BY ?category ORDER BY DESC(?revenue)'
結果:
| category | revenue |
|---|---|
| Furniture | $941.96 |
| Electronics | $589.93 |
| Books | $190.00 |
注文詳細(3ホップのグラフ走査)
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> SELECT ?customer ?product ?amount ?region WHERE { ?order :orderedBy ?cust ; :totalAmount ?amount ; :inRegion ?reg ; :hasOrderItem ?item . ?cust :firstName ?fname ; :lastName ?lname . ?item :containsProduct ?prod . ?prod :productName ?product . BIND(CONCAT(?fname, " ", ?lname) AS ?customer) BIND(STRAFTER(STR(?reg), "#") AS ?region) } ORDER BY DESC(?amount)'
メンバーシップティア別平均 LTV
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> PREFIX owl: <http://www.w3.org/2002/07/owl#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?tierName (AVG(?ltv) AS ?avgLTV) (COUNT(?c) AS ?memberCount) WHERE { ?c a :Customer ; :hasMembership ?tier ; :lifetimeValue ?ltv . ?tier a owl:NamedIndividual, :MembershipTier ; rdfs:label ?tierName . } GROUP BY ?tierName ORDER BY DESC(?avgLTV)'
結果:
| tier | avgLTV | members |
|---|---|---|
| Platinum | $3,850 | 2 |
| Gold | $1,383 | 3 |
| Silver | $820 | 2 |
| Bronze | $235 | 2 |
クラス階層の確認
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?class ?superclass WHERE { ?class rdfs:subClassOf ?superclass . FILTER(isIRI(?superclass)) } ORDER BY ?superclass'
リピーター検出
./sparql.sh 'PREFIX : <http://example.org/ecommerce#> SELECT ?customer (COUNT(?order) AS ?orderCount) (SUM(?total) AS ?totalSpent) WHERE { ?order :orderedBy ?cust ; :totalAmount ?total . ?cust :firstName ?fname ; :lastName ?lname . BIND(CONCAT(?fname, " ", ?lname) AS ?customer) } GROUP BY ?customer ?cust HAVING (COUNT(?order) > 1)'
オントロジー全体抽出
./sparql.sh 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o'
クリーンアップ
# EC2
aws ec2 terminate-instances --instance-ids <INSTANCE_ID> --region us-east-1
# Lambda
aws lambda delete-function --function-name neptune-ontology-loader --region us-east-1
# Neptune(インスタンス → クラスターの順)
aws neptune delete-db-instance \
--db-instance-identifier ecommerce-ontology-instance-1 \
--skip-final-snapshot --region us-east-1
# インスタンス削除完了を待ってから:
aws neptune delete-db-cluster \
--db-cluster-identifier ecommerce-ontology \
--skip-final-snapshot --region us-east-1
# サブネットグループ(クラスター削除完了後)
aws neptune delete-db-subnet-group \
--db-subnet-group-name neptune-ontology-subnet-group --region us-east-1
# セキュリティグループ(Lambda ENI 解放後、30秒程待つ)
aws ec2 delete-security-group --group-id <SG_ID> --region us-east-1
# IAM
aws iam remove-role-from-instance-profile \
--instance-profile-name NeptuneEC2SSMProfile --role-name NeptuneEC2SSMRole
aws iam delete-instance-profile --instance-profile-name NeptuneEC2SSMProfile
aws iam detach-role-policy --role-name NeptuneEC2SSMRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam delete-role --role-name NeptuneEC2SSMRole
aws iam detach-role-policy --role-name NeptuneLambdaRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
aws iam delete-role --role-name NeptuneLambdaRole
aws iam delete-role-policy --role-name NeptuneS3LoadRole --policy-name NeptuneS3ReadPolicy
aws iam delete-role --role-name NeptuneS3LoadRole
Neptune インスタンスの削除に 5〜7分 かかります。クラスター削除はインスタンス削除完了後に実行してください。セキュリティグループは Lambda の ENI が解放されるまで削除できないため、30秒ほど待ってから実行してください。
まとめ
RDB と比較した Neptune + OWL の利点
| 観点 | RDB | Neptune + OWL |
|---|---|---|
| 関係性の表現 | 外部キー(フラット) | グラフ走査(多段ホップ) |
| 型の階層 | なし |
rdfs:subClassOf で推論可能 |
| 排他制約 | CHECK制約 | owl:AllDisjointClasses |
| 列挙型 | ENUM |
owl:oneOf で形式定義 |
| スキーマの自己記述 | ERD(外部ドキュメント) | オントロジー自体がデータとして格納 |
| 多段JOIN | 性能劣化 | グラフ走査は一定性能 |
所感
- Neptune + SPARQL は「データの意味を問い合わせる」用途に強い
- OWL 制約はドキュメント代わりにもなる(スキーマがそのまま検索可能)
- セッションマネージャー経由なら SSH キーの管理不要で手軽に検証できる
- データ量が少ない場合は INSERT DATA で十分、バルクローダーは大量データ向け