データベース

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"))
    );
});