Elasticsearch と MySQL の JOIN 機能: データモデル設計の核心

Elasticsearch は MySQL のような多テーブル JOIN クエリを直接サポートしません。これは反正規化設計、分散アーキテクチャ、近似実時間処理の本質に起因します。JOIN を強制的に実装するとパフォーマンス急落、データ不整合、メンテナンスコスト上昇を招きます。

しかし適切なデータモデル設計により、90% の JOIN 要件を ES のネイティブ機能で効率的に処理可能です。

1. JOIN の本質: 関係型データベースの基盤

MySQL JOIN の例

SELECT a.author_name, p.title
FROM authors a
JOIN posts p ON a.id = p.author_id
WHERE a.status = 'verified';
  • 依存要素:
    • 正規化設計(データを複数テーブルに分割)
    • ACID トランザクション(関連データの一貫性保証)
    • ネストループ/ハッシュ JOIN アルゴリズム(実行時計算)

本質の違い: JOIN は「実行時関連付け」、ES は「書き込み時関連付け」

2. ES が JOIN をサポートしない理由

1. 分散アーキテクチャの制約

  • データシャーディング: authorsposts が異なるノードに分散
  • JOIN にはノード間通信が必要 → ネットワークオーバーヘッドが指数関数的に増加(O(n²))

2. パフォーマンスモデルの不一致

  • ES は高スループット書き込み/低遅延検索を最適化
  • JOIN はランダム I/O + 複雑計算を必要 → ES のパフォーマンスモデルを破壊

3. データモデルの根本的違い

  • ES = ドキュメント指向データベース
  • 核心思想: 反正規化(関連データを1ドキュメントに埋め込む)

ES の哲学: 「計算を削減するためのストレージ活用」を基本とする

3. 代替策: 4つのデータモデル変換戦略

戦略1: 反正規化(Denormalization)— 推奨

  • 適用ケース: 1:N かつ次元テーブルが小さい(例: 著者情報)
  • 実装: posts ドキュメントに authors フィールドを埋め込む
  • ES ドキュメント:
    {
      "post_id": 205,
      "title": "JavaScript チュートリアル",
      "author": {
        "id": 301,
        "name": "Alice",
        "status": "verified"
      }
    }
  • クエリ:
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "title": "javascript" } },
            { "term": { "author.status": "verified" } }
          ]
        }
      }
    }
  • メリット: 単一ドキュメントでの検索 → 最高パフォーマンス
  • コスト: データ重複、更新時の同期処理が必要

戦略2: 埋め込みオブジェクト(Nested Objects)

  • 適用ケース: 1:N かつサブオブジェクトを独立して検索する必要あり(例: 投稿 + コメント)
  • ES マッピング:
    {
      "mappings": {
        "properties": {
          "comments": {
            "type": "nested",
            "properties": {
              "user_id": { "type": "keyword" },
              "text": { "type": "text" }
            }
          }
        }
      }
    }
  • クエリ:
    {
      "query": {
        "nested": {
          "path": "comments",
          "query": {
            "match": { "comments.user_id": "user_123" }
          }
        }
      }
    }
  • コスト: 扁平ドキュメントに比べて書き込み/検索パフォーマンスが低下

戦略3: アプリケーション層 JOIN(Client-Side Join)

  • 適用ケース: N:M かつ反正規化が不可能
  • フロー: 1. ES で posts を検索 2. author_id リストを抽出 3. MySQL で authors を取得 4. アプリケーション層で結果をマージ
  • コード例:
    // 1. ES で投稿検索
    $posts = $esClient->search('posts', 'react');
    
    // 2. author_id リストを抽出
    $authorIds = array_column($posts, 'author_id');
    
    // 3. MySQL で著者取得
    $authors = $pdo->query("SELECT id, name FROM authors WHERE id IN (" . implode(',', $authorIds) . ")")->fetchAll();
    
    // 4. マッピングと結合
    $authorMap = array_column($authors, null, 'id');
    foreach ($posts as &$post) {
        $post['author'] = $authorMap[$post['author_id']];
    }
  • メリット: 非常に柔軟
  • コスト: N+1 クエリのリスク、遅延増加

戦略4: 親子ドキュメント(Parent-Child)— 慎用

  • 適用ケース: データが頻繁に更新され反正規化が不可能
  • ES マッピング:
    {
      "mappings": {
        "properties": {
          "doc_type": {
            "type": "join",
            "relations": {
              "author": "post"
            }
          }
        }
      }
    }
  • クエリ(子ドキュメント検索):
    {
      "query": {
        "has_child": {
          "type": "post",
          "query": { "match": { "title": "react" } }
        }
      }
    }
  • コスト: パフォーマンスが非常に悪く、小規模データに限定

4. 実践ガイド: モデル選択の基準

シナリオ 推奨戦略 理由
著者 + 投稿(1:N) 反正規化 著者データが小さく更新頻度が低い
投稿 + コメント(1:N) 埋め込みオブジェクト コメントを独立して検索する必要あり
タグ + 投稿(N:M) アプリケーション層 JOIN タグが動的に変化する
注文 + 商品(リアルタイム) 反正規化 + CDC Debezium を活用した MySQL から ES への同期

絶対に避けるべきパターン

  • ES で Parent-Child を頻繁に使用(パフォーマンス急落の原因)
  • アプリケーション層 JOIN にキャッシュを適用しない(MySQL への負荷増大)

5. 高リスクの誤解

誤解1: "ES 7+ は JOIN をサポート"

  • 事実: join データ型は SQL JOIN とは異なる。Parent-Child の新実装であり、パフォーマンスは依然として低い
  • 対策: 反正規化を優先的に検討

誤解2: "Terms Lookup で JOIN を模倣可能"

  • :
    {
      "query": {
        "terms": {
          "author_id": {
            "index": "authors",
            "id": "1",
            "path": "verified_posts"
          }
        }
      }
    }
  • 事実: 静的リストにのみ適用可能、リアルタイム JOIN には不適
  • 対策: ダイナミックな関連クエリには使用しない

誤解3: "Elasticsearch は MySQL に置き換え可能"

  • 事実: ES は検索エンジン、MySQL はトランザクションデータベース
  • 対策: ES + MySQL の共存(CQRS アーキテクチャ)

6. デザインの本質: ストレージを活用して計算を削減

"ES で JOIN を実装しようとするのではなく、JOIN が不要なデータモデルを設計する"

  • 脆弱な設計: Parent-Child を頻繁に使用 → パフォーマンス急落
  • 頑健な設計: 反正規化 + CDC 同期 → ミリ秒単位の検索
  • 結果: 前者はデータ量増加で崩壊、後者はデータ量増加に伴い拡張可能

7. 実践的アクションリスト

## 2025-10-15 データモデル最適化

### 1. 現在の JOIN を分析
- [ ] MySQL での JOIN クエリを特定

### 2. 代替戦略を決定
- [ ] 1:N → 反正規化
- [ ] N:M → アプリケーション層 JOIN

### 3. ES マッピングを実装
- [ ] posts インデックスに author フィールドを追加

### 4. クエリを検証
- [ ] JOIN を単一 ES クエリで置き換え

タグ: Elasticsearch Denormalization CQRS ParentChild ApplicationJoin

6月5日 16:41 投稿