データベース
ArangoDB
概要
ArangoDBは、ドキュメント、グラフ、キーバリューの3つのデータモデルを単一のエンジンで統合したマルチモデルデータベースです。独自のクエリ言語「AQL(ArangoDB Query Language)」により、異なるデータモデル間を横断する複雑なクエリを効率的に実行できます。ネイティブグラフ処理、ACID特性、水平スケーリングを兼ね備えた高性能なNoSQLデータベースソリューションです。
詳細
ArangoDBは2011年にドイツで開発が開始され、2012年にオープンソースとして公開されました。従来のデータベースでは複数のシステムが必要だった用途を、単一のデータベースで解決できる革新的なアプローチが特徴です。
ArangoDBの主な特徴:
- マルチモデルアーキテクチャ: ドキュメント、グラフ、キーバリューを統合
- AQLクエリ言語: SQLライクな統一クエリ言語
- ネイティブグラフ処理: 高速なトラバーサルアルゴリズム
- ACID特性: 完全なトランザクション保証
- 分散アーキテクチャ: 自動シャーディングとレプリケーション
- Foxxマイクロサービス: データベース内でのアプリケーション実行
- RESTful API: HTTPベースのアクセスインターフェース
- 地理空間インデックス: GeoJSONサポート
- 全文検索: ArangoSearchエンジン内蔵
- Web インターフェース: 直感的な管理・可視化ツール
メリット・デメリット
メリット
- 統合性: 複数のデータモデルを単一システムで管理
- 柔軟性: スキーマレス設計で進化するデータに対応
- パフォーマンス: 最適化されたクエリ実行エンジン
- スケーラビリティ: 水平スケーリングとクラスタリング
- 開発効率: 統一されたクエリ言語による開発生産性向上
- 豊富な機能: グラフ分析、全文検索、地理空間処理を内蔵
- マイクロサービス: Foxxによるサーバーサイドアプリケーション
- 運用性: Web UI による直感的な管理
デメリット
- 学習コスト: AQLとマルチモデル概念の習得が必要
- メモリ使用量: インメモリ処理により多くのメモリを消費
- エコシステム: 特定分野ではNeo4jやMongoDBより限定的
- 複雑性: 多機能ゆえの設定・チューニングの複雑さ
- 特化システムとの差: 単一モデル特化DBと比較した性能劣化の可能性
主要リンク
書き方の例
インストール・セットアップ
# Docker での実行
docker run -e ARANGO_ROOT_PASSWORD=password \
-p 8529:8529 \
-v arango-data:/var/lib/arangodb3 \
-v arango-apps:/var/lib/arangodb3-apps \
arangodb:latest
# Docker Compose 設定
cat > docker-compose.yml << EOF
version: '3.8'
services:
arangodb:
image: arangodb:latest
environment:
ARANGO_ROOT_PASSWORD: password
ports:
- "8529:8529"
volumes:
- arango-data:/var/lib/arangodb3
- arango-apps:/var/lib/arangodb3-apps
volumes:
arango-data:
arango-apps:
EOF
docker-compose up -d
# Web インターフェースアクセス
# http://localhost:8529
# Python ドライバーインストール
pip install python-arango
# Node.js ドライバーインストール
npm install arangojs
# ArangoDB CLI クライアント(arangosh)
docker exec -it arangodb_container arangosh
基本操作(マルチモデルCRUD)
// AQLによる基本CRUD操作
// コレクション作成
db._create("users"); // ドキュメントコレクション
db._createEdgeCollection("friends"); // エッジコレクション
// ドキュメント作成(Create)
FOR doc IN [
{name: "田中太郎", age: 30, email: "[email protected]", city: "東京"},
{name: "佐藤花子", age: 25, email: "[email protected]", city: "大阪"},
{name: "鈴木一郎", age: 35, email: "[email protected]", city: "名古屋"}
]
INSERT doc INTO users
// キーバリューアクセス
INSERT {_key: "user001", name: "山田太郎", status: "active"} INTO users
// ドキュメント読み取り(Read)
FOR user IN users
RETURN user
// 条件付きクエリ
FOR user IN users
FILTER user.age > 25
RETURN {name: user.name, age: user.age}
// キーによる直接アクセス
RETURN DOCUMENT("users/user001")
// ドキュメント更新(Update)
FOR user IN users
FILTER user.name == "田中太郎"
UPDATE user WITH {age: 31, location: "東京都渋谷区"} IN users
// 条件付き更新
UPDATE {_key: "user001"} WITH {lastLogin: DATE_NOW()} IN users
// ドキュメント削除(Delete)
FOR user IN users
FILTER user.email == "[email protected]"
REMOVE user IN users
// キー指定削除
REMOVE "user001" IN users
// エッジ(関係性)作成
INSERT {_from: "users/user1", _to: "users/user2", type: "friend", since: "2024-01-01"}
INTO friends
マルチモデルクエリ
// ドキュメント + グラフ + キーバリューの統合クエリ
// グラフトラバーサル
FOR v, e, p IN 1..3 OUTBOUND "users/user1" friends
RETURN {
friend: v.name,
relationship: e.type,
path_length: LENGTH(p.edges)
}
// ドキュメント検索 + グラフ分析
FOR user IN users
FILTER user.city == "東京"
FOR friend IN 1..2 OUTBOUND user friends
COLLECT friendCity = friend.city WITH COUNT INTO friendCount
RETURN {
city: friendCity,
friends_count: friendCount
}
// キーバリュー + ドキュメント結合
LET userKeys = ["user001", "user002", "user003"]
FOR key IN userKeys
LET user = DOCUMENT(CONCAT("users/", key))
FILTER user != null
RETURN {
key: key,
user: user,
is_active: user.status == "active"
}
// 複合データモデルクエリ
FOR order IN orders
LET customer = DOCUMENT(order.customer_id)
LET items = (
FOR item_id IN order.items
RETURN DOCUMENT(CONCAT("products/", item_id))
)
RETURN {
order_id: order._key,
customer_name: customer.name,
total_items: LENGTH(items),
total_value: SUM(items[*].price)
}
インデックス・最適化
// インデックス作成
db.users.ensureIndex({type: "hash", fields: ["email"], unique: true});
db.users.ensureIndex({type: "skiplist", fields: ["age"]});
db.users.ensureIndex({type: "fulltext", fields: ["name", "description"]});
// 地理空間インデックス
db.locations.ensureIndex({type: "geo", fields: ["coordinates"]});
// 複合インデックス
db.users.ensureIndex({type: "skiplist", fields: ["city", "age"]});
// インデックス確認
db.users.getIndexes();
// クエリ最適化分析
db._explain(`
FOR user IN users
FILTER user.age > 25 AND user.city == "東京"
RETURN user
`);
// プロファイリング
db._profile(`
FOR user IN users
FOR friend IN 1..2 OUTBOUND user friends
RETURN {user: user.name, friend: friend.name}
`);
// 全文検索インデックス
db.articles.ensureIndex({type: "fulltext", fields: ["title", "content"]});
// 全文検索クエリ
FOR doc IN FULLTEXT(articles, "title,content", "ArangoDB マルチモデル")
RETURN doc
高度な機能
// 地理空間クエリ
FOR location IN locations
FILTER GEO_DISTANCE(location.coordinates, [139.6917, 35.6895]) < 1000
RETURN {
name: location.name,
distance: GEO_DISTANCE(location.coordinates, [139.6917, 35.6895])
}
// グラフアルゴリズム(最短パス)
FOR path IN OUTBOUND SHORTEST_PATH "users/alice" TO "users/bob" friends
RETURN path
// 中心性分析
FOR user IN users
LET connections = LENGTH(
FOR v IN 1..1 ANY user friends
RETURN v
)
SORT connections DESC
LIMIT 10
RETURN {user: user.name, connections: connections}
// ウィンドウ関数
FOR sale IN sales
SORT sale.date
RETURN {
date: sale.date,
amount: sale.amount,
running_total: SUM(
FOR s IN sales
FILTER s.date <= sale.date
RETURN s.amount
)
}
// 配列操作
FOR user IN users
FILTER LENGTH(user.skills) > 0
RETURN {
name: user.name,
primary_skill: FIRST(user.skills),
skill_count: LENGTH(user.skills),
has_javascript: "JavaScript" IN user.skills
}
// JSON処理
FOR document IN documents
LET parsed = JSON_PARSE(document.json_data)
FILTER parsed.type == "user_event"
RETURN {
id: document._key,
event_type: parsed.event_type,
timestamp: parsed.timestamp
}
実用例
// 推薦システム
FOR user IN users
FILTER user._key == "current_user"
FOR friend IN 2..3 OUTBOUND user friends
FOR product IN 1..1 OUTBOUND friend purchases
FILTER NOT (user)-[:PURCHASED]->(product)
COLLECT productId = product._key WITH COUNT INTO score
SORT score DESC
LIMIT 5
RETURN {
product_id: productId,
recommendation_score: score
}
// 不正検知(異常パターン)
FOR transaction IN transactions
FILTER transaction.amount > 100000
AND transaction.timestamp > DATE_SUBTRACT(DATE_NOW(), 1, "day")
LET user_transactions = (
FOR t IN transactions
FILTER t.user_id == transaction.user_id
AND t.timestamp > DATE_SUBTRACT(DATE_NOW(), 1, "day")
RETURN t
)
FILTER LENGTH(user_transactions) > 10
RETURN {
user_id: transaction.user_id,
suspicious_transactions: LENGTH(user_transactions),
total_amount: SUM(user_transactions[*].amount)
}
// ソーシャルネットワーク分析
FOR user IN users
FILTER user._key == "target_user"
FOR friend IN 2..2 OUTBOUND user friends
FILTER friend._key != user._key
AND NOT (user)-[:FRIENDS_WITH]->(friend)
COLLECT suggestion = friend WITH COUNT INTO mutualFriends
SORT mutualFriends DESC
LIMIT 5
RETURN {
suggested_friend: suggestion.name,
mutual_connections: mutualFriends
}
// リアルタイム分析ダッシュボード
LET stats = {
total_users: LENGTH(users),
active_sessions: LENGTH(
FOR session IN sessions
FILTER session.last_activity > DATE_SUBTRACT(DATE_NOW(), 15, "minute")
RETURN session
),
recent_orders: LENGTH(
FOR order IN orders
FILTER order.created_at > DATE_SUBTRACT(DATE_NOW(), 1, "hour")
RETURN order
)
}
RETURN stats
Pythonでの使用例
from arango import ArangoClient
# データベース接続
client = ArangoClient(hosts='http://localhost:8529')
sys_db = client.db('_system', username='root', password='password')
# データベース作成
if not sys_db.has_database('example_db'):
sys_db.create_database('example_db')
# データベース接続
db = client.db('example_db', username='root', password='password')
# コレクション作成
if not db.has_collection('users'):
users = db.create_collection('users')
else:
users = db.collection('users')
if not db.has_collection('friends'):
friends = db.create_collection('friends', edge=True)
else:
friends = db.collection('friends')
# ドキュメント操作
def create_user(name, age, email):
user_data = {
'name': name,
'age': age,
'email': email,
'created_at': '2024-01-01'
}
return users.insert(user_data)
def find_users_by_city(city):
aql = """
FOR user IN users
FILTER user.city == @city
RETURN user
"""
return list(db.aql.execute(aql, bind_vars={'city': city}))
def create_friendship(user1_id, user2_id):
edge_data = {
'_from': f'users/{user1_id}',
'_to': f'users/{user2_id}',
'type': 'friend',
'created_at': '2024-01-01'
}
return friends.insert(edge_data)
def get_user_friends(user_id):
aql = """
FOR v, e, p IN 1..1 OUTBOUND @start_vertex friends
RETURN {
friend: v,
relationship: e,
path_length: LENGTH(p.edges)
}
"""
return list(db.aql.execute(aql, bind_vars={'start_vertex': f'users/{user_id}'}))
# 実行例
user1 = create_user("田中太郎", 30, "[email protected]")
user2 = create_user("佐藤花子", 25, "[email protected]")
friendship = create_friendship(user1['_key'], user2['_key'])
friends_list = get_user_friends(user1['_key'])
print(f"Created users: {user1['_key']}, {user2['_key']}")
print(f"Friends: {len(friends_list)}")
設定とチューニング
# arangod.conf の主要設定
[server]
endpoint = tcp://0.0.0.0:8529
[database]
maximal-journal-size = 1073741824
[cache]
size = 2147483648
[javascript]
startup-directory = /var/lib/arangodb3-apps
[cluster]
my-role = SINGLE
[rocksdb]
block-cache-size = 1073741824
total-write-buffer-size = 536870912
[log]
level = info
file = /var/log/arangodb3/arangod.log
[ssl]
keyfile = /etc/ssl/arangodb/server.pem
# パフォーマンスチューニング
[query]
cache-mode = on
smart-joins = true
# セキュリティ設定
[server]
authentication = true
jwt-secret = your-secret-key
[ssl]
protocol = 5