データベース
OrientDB
概要
OrientDBは、オープンソースのマルチモデルデータベース管理システムです。グラフ、ドキュメント、オブジェクト、キーバリューの4つのデータモデルを単一のエンジンで統合し、開発者がアプリケーションの要件に応じて最適なモデルを選択できます。SQLクエリ言語をサポートし、既存のリレーショナルデータベースからの移行が容易で、Java環境での柔軟なデータ管理を提供します。
詳細
OrientDBは2010年にLuca Garulli氏によって開発されました。従来のデータベースでは複数の製品を組み合わせる必要があった用途を、単一のデータベースエンジンで実現できることが最大の特徴です。これは単なるAPIレイヤーによる擬似的なマルチモデル対応ではなく、エンジンレベルでネイティブに4つのモデルをサポートしています。
OrientDBの主な特徴:
- ネイティブマルチモデルエンジン(グラフ、ドキュメント、オブジェクト、キーバリュー)
- 標準SQL言語サポート(拡張SQL構文)
- ACID特性の完全サポート
- スキーマレス、スキーマフル、混合モードの柔軟性
- 分散処理とマルチマスター構成
- 高速なレコード挿入性能(220,000レコード/秒)
- フルテキスト検索と地理空間インデックス
- 強力なセキュリティ機能(ユーザー、ロール、述語セキュリティ)
- リアクティブクエリサポート
- Apache 2.0ライセンス
メリット・デメリット
メリット
- 統合データモデル: 複数のデータベース製品を統合可能
- 高いパフォーマンス: JOINなしの高速トラバーサル
- SQL互換性: 既存のSQLスキルを活用可能
- 柔軟なスキーマ: スキーマフリーから厳密な型定義まで対応
- コスト効率: オープンソースで無料利用可能
- Java統合: Javaアプリケーションとの親和性が高い
- 分散アーキテクチャ: スケーラブルなマルチマスター構成
デメリット
- 学習コスト: マルチモデルの概念習得が必要
- コミュニティサイズ: Neo4jやMongoDBと比較して小規模
- エンタープライズサポート: 商用サポートの選択肢が限定的
- エコシステム: サードパーティツールの連携が限定的
- 大規模運用: 超大規模環境での実績が限定的
主要リンク
書き方の例
インストール・セットアップ
# Docker での実行
docker run --name orientdb-container \
-p 2424:2424 -p 2480:2480 \
-e ORIENTDB_ROOT_PASSWORD=rootpass \
-v orientdb-data:/orientdb/databases \
orientdb:latest
# Docker Compose での設定
version: '3.8'
services:
orientdb:
image: orientdb:latest
ports:
- "2424:2424"
- "2480:2480"
environment:
- ORIENTDB_ROOT_PASSWORD=rootpass
volumes:
- orientdb-data:/orientdb/databases
# サーバーアクセス
# OrientDB Studio: http://localhost:2480
# バイナリプロトコル: localhost:2424
# Javaドライバー依存関係(Maven)
<dependency>
<groupId>com.orientechnologies</groupId>
<artifactId>orientdb-client</artifactId>
<version>3.2.15</version>
</dependency>
# Python ドライバーインストール
pip install pyorient
# Node.js ドライバーインストール
npm install orientjs
基本操作(CRUD)
-- データベース作成と接続
CREATE DATABASE remote:localhost/testdb admin admin;
CONNECT remote:localhost/testdb admin admin;
-- クラス(テーブル)作成
CREATE CLASS Person EXTENDS V;
CREATE CLASS Company EXTENDS V;
CREATE CLASS WorksFor EXTENDS E;
-- ドキュメントとして作成
INSERT INTO Person SET name = '田中太郎', age = 30, email = '[email protected]';
INSERT INTO Company SET name = 'テック株式会社', industry = 'IT', founded = 2010;
-- グラフとして関係性作成
CREATE VERTEX Person SET name = '佐藤花子', age = 28, department = '開発';
CREATE VERTEX Company SET name = 'イノベーション株式会社', industry = 'Software';
-- エッジ作成(関係性)
CREATE EDGE WorksFor FROM (SELECT FROM Person WHERE name = '佐藤花子')
TO (SELECT FROM Company WHERE name = 'イノベーション株式会社')
SET position = 'エンジニア', startDate = '2022-04-01';
-- データ読み取り
SELECT FROM Person;
SELECT FROM Person WHERE age > 25;
-- グラフトラバーサル
SELECT FROM Person WHERE out('WorksFor').name = 'テック株式会社';
-- 関係性を含むクエリ
SELECT person.name, company.name, edge.position
FROM (
MATCH {class: Person, as: person}-WorksFor{as: edge}-{class: Company, as: company}
RETURN person, edge, company
);
-- データ更新
UPDATE Person SET age = 31 WHERE name = '田中太郎';
UPDATE WorksFor SET position = 'シニアエンジニア' WHERE out.name = '田中太郎';
-- データ削除
DELETE FROM Person WHERE name = '田中太郎';
DELETE EDGE WorksFor WHERE out.name = '佐藤花子';
マルチモデル操作
-- ドキュメントモデル
INSERT INTO User CONTENT {
"name": "鈴木次郎",
"profile": {
"age": 25,
"skills": ["Java", "Python", "JavaScript"],
"address": {
"city": "東京",
"prefecture": "東京都"
}
},
"projects": [
{"name": "プロジェクトA", "status": "active"},
{"name": "プロジェクトB", "status": "completed"}
]
};
-- キーバリューモデル
CREATE CLASS Configuration;
INSERT INTO Configuration SET key = 'app.timeout', value = '30000';
INSERT INTO Configuration SET key = 'app.debug', value = 'true';
-- キーバリュー検索
SELECT value FROM Configuration WHERE key = 'app.timeout';
-- オブジェクトモデル(Java統合)
CREATE CLASS Employee EXTENDS V;
INSERT INTO Employee SET
name = '山田三郎',
salary = 5000000,
hireDate = date('2023-01-15'),
skills = ['Java', 'Spring Boot', 'Docker'];
-- 複合クエリ(マルチモデル結合)
SELECT
person.name,
person.profile.skills,
company.name as companyName
FROM Person person, Company company
WHERE person.out('WorksFor') = company
AND person.profile.skills CONTAINS 'Java';
インデックス・最適化
-- インデックス作成
CREATE INDEX Person.name ON Person (name) NOTUNIQUE;
CREATE INDEX Person.email ON Person (email) UNIQUE;
CREATE INDEX Person.age_name ON Person (age, name) NOTUNIQUE;
-- フルテキストインデックス
CREATE INDEX Person.fulltext ON Person (name, email) FULLTEXT ENGINE LUCENE;
-- 地理空間インデックス
CREATE CLASS Location EXTENDS V;
CREATE PROPERTY Location.coordinates EMBEDDED OPoint;
CREATE INDEX Location.coordinates ON Location (coordinates) SPATIAL ENGINE LUCENE;
-- インデックス確認
SELECT FROM OIndexes;
-- クエリプラン確認
EXPLAIN SELECT FROM Person WHERE name = '田中太郎';
-- プロファイリング
PROFILE SELECT FROM Person WHERE age > 25;
-- データベース統計
SELECT FROM OFunction WHERE name = 'graph.info';
-- パフォーマンス設定
ALTER DATABASE CUSTOM useLightweightEdges = false;
ALTER DATABASE CUSTOM useClassForEdgeLabel = true;
高度な機能
-- トランザクション処理
BEGIN;
INSERT INTO Person SET name = '新規ユーザー', email = '[email protected]';
CREATE EDGE WorksFor FROM $lastRid TO (SELECT FROM Company WHERE name = 'テック株式会社');
COMMIT;
-- 関数定義
CREATE FUNCTION getEmployeesByCompany "
SELECT person.name
FROM Person person, Company company
WHERE person.out('WorksFor') = company
AND company.name = :companyName
" PARAMETERS [companyName] LANGUAGE SQL;
-- 関数実行
SELECT getEmployeesByCompany('テック株式会社');
-- トリガー作成
CREATE TRIGGER PersonAudit AFTER INSERT ON Person
FOR EACH ROW
INSERT INTO AuditLog SET
action = 'INSERT',
className = 'Person',
recordId = $new.@rid,
timestamp = sysdate();
-- バッチ処理
BATCH
INSERT INTO Person SET name = 'ユーザー1', age = 25;
INSERT INTO Person SET name = 'ユーザー2', age = 30;
INSERT INTO Person SET name = 'ユーザー3', age = 35;
END;
-- 分散クエリ(クラスター環境)
SELECT FROM cluster:person_europe WHERE country = 'Germany';
実用例
-- ソーシャルネットワーク分析
SELECT person.name,
out('Follows').size() as following,
in('Follows').size() as followers
FROM Person person
ORDER BY followers DESC
LIMIT 10;
-- 推薦システム(協調フィルタリング)
SELECT DISTINCT recommendation.title, count(*) as score
FROM (
SELECT expand(out('Purchased')) as products
FROM User
WHERE name = '田中太郎'
) user_products
JOIN (
SELECT in('Purchased') as users, @rid as product_id
FROM Product
WHERE @rid IN user_products
) similar_users
JOIN (
SELECT expand(out('Purchased')) as recommendation
FROM User
WHERE @rid IN similar_users.users
AND @rid <> (SELECT FROM User WHERE name = '田中太郎')
) recommendations
WHERE recommendation NOT IN user_products
GROUP BY recommendation
ORDER BY score DESC
LIMIT 5;
-- 最短パス検索
SELECT shortestPath(
(SELECT FROM Person WHERE name = 'アリス'),
(SELECT FROM Person WHERE name = 'ボブ'),
'out',
'Knows'
);
-- 不正検知パターン
SELECT suspicious_account.name,
count(*) as transaction_count,
sum(amount) as total_amount
FROM (
SELECT expand(out('Transfer')) as transactions
FROM Account
WHERE @rid = #12:1
) account_transactions
WHERE transactions.timestamp > (sysdate() - 86400000)
AND transactions.amount > 100000
GROUP BY transactions.in
HAVING count(*) > 5;
-- 地理空間クエリ
SELECT name, coordinates
FROM Location
WHERE coordinates WITHIN circle(35.6762, 139.6503, 1000);
Javaでの使用例
import com.orientechnologies.orient.core.db.ODatabaseSession;
import com.orientechnologies.orient.core.db.OrientDB;
import com.orientechnologies.orient.core.db.OrientDBConfig;
import com.orientechnologies.orient.core.record.OVertex;
import com.orientechnologies.orient.core.record.OEdge;
// データベース接続
OrientDB orient = new OrientDB("remote:localhost", OrientDBConfig.defaultConfig());
ODatabaseSession db = orient.open("testdb", "admin", "admin");
try {
// トランザクション開始
db.begin();
// 頂点作成
OVertex person = db.newVertex("Person");
person.setProperty("name", "田中太郎");
person.setProperty("age", 30);
person.save();
OVertex company = db.newVertex("Company");
company.setProperty("name", "テック株式会社");
company.setProperty("industry", "IT");
company.save();
// エッジ作成
OEdge worksFor = person.addEdge(company, "WorksFor");
worksFor.setProperty("position", "エンジニア");
worksFor.setProperty("startDate", new Date());
worksFor.save();
// コミット
db.commit();
// クエリ実行
db.query("SELECT FROM Person WHERE name = ?", "田中太郎")
.stream()
.forEach(result -> {
System.out.println("Name: " + result.getProperty("name"));
System.out.println("Age: " + result.getProperty("age"));
});
} catch (Exception e) {
db.rollback();
e.printStackTrace();
} finally {
db.close();
orient.close();
}
// 非同期処理
CompletableFuture<List<OResult>> future = db.queryAsync("SELECT FROM Person");
future.thenAccept(results -> {
results.forEach(result ->
System.out.println(result.getProperty("name"))
);
});