Upgrade to Pro — share decks privately, control downloads, hide ads and more …

階層構造を表現するデータ構造とリファクタリング 〜1年で10倍成長したプロダクトの変化と課題〜

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for yuhi yuhi
September 26, 2025

階層構造を表現するデータ構造とリファクタリング 〜1年で10倍成長したプロダクトの変化と課題〜

Kaigi on Rails 2025 資料です。
https://kaigionrails.org/2025/talks/Yuhi-Sato/#day2

Avatar for yuhi

yuhi

September 26, 2025

More Decks by yuhi

Other Decks in Programming

Transcript

  1. yuhi 株式会社プレックス / 24卒 yuhi_junior Yuhi-Sato 自己紹介 • 建設系SaaS サクミル

    開発 • 24th Dev, Wakate.rb主催 • Rails歴 2年 • Kaigi on Rails初参加🙌
  2. 階層構造を持つサービス例 1 クラウドストレージサービスにおけるフォルダは階層構造 id: 3 parent_id: 2 Kaigi on Rails

    id: 3 parent_id: 2 2023 id: 3 parent_id: 2 2024 id: 3 parent_id: 2 2025 id: 3 parent_id: 2 1日目 id: 3 parent_id: 2 2日目
  3. 階層化機能のよくある要件 • 階層構造の読み込み ◦ 親ノードを取得 ◦ ⼦ノードを取得 ◦ 祖先ノードを取得 ◦

    ⼦孫ノードを取得 • 階層構造の書き込み ◦ ⼦ノードを作成 ◦ ノードを移動 ◦ ノードとその全ての⼦孫ノードを削除 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ノード
  4. 本日話すこと • 階層構造を表現するデータ構造とそのリファクタリング • 急成⻑サービスにおける変化と課題 本日話さないこと • 下記のデータ構造について ◦ Path

    Enumeration(経路列挙) , Nested Sets(⼊れ⼦集合) • 階層構造を表現する特定のgemについて ◦ gemの裏側で使われているデータ構造の話をします はじめに
  5. はじめに ゴール 1. 階層構造を表現する2つのデータ構造を理解する(SQLクイズを出題!) ◦ Adjacency List(隣接リスト) ◦ Closure Table(閉包テーブル)

    2. Recursive CTE(再帰CTE)の概要を理解する 3. 階層化機能において適切な⼿法を選択ができるようになる
  6. 本発表では階層構造の中でも ツリー構造(= 親を⼀つだけ持つ) のみを扱います はじめに id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  7. id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3

    parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 Adjacency Listの例 id parent_id 1 null 2 1 3 2 4 3 5 3 6 1 7 6 foldersテーブル
  8. id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3

    parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 Adjacency Listの例 id parent_id 1 null 2 1 3 2 4 3 5 3 6 1 7 6 foldersテーブル
  9. id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3

    parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 Adjacency Listの例 id parent_id 1 null 2 1 3 2 4 3 5 3 6 1 7 6 foldersテーブル
  10. 親ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  11. 親ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 parent_id=3という情報から id=3のノードを取得する
  12. 親ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 parent_id=3という情報から id=3のノードを取得する
  13. 子ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  14. 子ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ? 空欄に⼊るのは? 1. parent_id = 3 2. id = 3
  15. 子ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 空欄に⼊るのは? 1. parent_id = 3 2. id = 3 親のidが3のノードを取得する
  16. 子ノードを作成( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7
  17. 子ノードを作成( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7 親のidを7にする
  18. 子ノードを作成( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7 親のidを7にする
  19. ノードを移動( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6
  20. ノードを移動( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6
  21. ノードを移動( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 親のidを7に変更する
  22. ノードを移動( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 親のidを7に変更する
  23. 祖先ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  24. 祖先ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  25. 祖先ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  26. 祖先ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  27. 全ての祖先ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 クエリ実⾏回数: 3
  28. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  29. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  30. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  31. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2
  32. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2
  33. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6
  34. 子孫ノードを取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6
  35. 子孫ノードの取得( Adjacency List) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 クエリ実⾏回数: 7
  36. Adjacency List まとめ • メリット ◦ 実装が容易 ▪ 直感的なFolderモデルで実装できる ◦

    書き込みが容易 ▪ SQL問い合わせ1回 • デメリット ◦ 親⼦関係を超えた読み込み操作が困難 ▪ SQL問い合わせを複数回⾏う必要がある
  37. 大量のN+1が発生する例 : 子孫ノードを探索( Adjacency List) id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1000 parent_id: xxx
  38. 大量のN+1が発生する例 : 子孫ノードを探索( Adjacency List) id: 3 parent_id: 2 id:

    3 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1000 parent_id: xxx
  39. 大量のN+1が発生する例 : 子孫ノードを探索( Adjacency List) id: 3 parent_id: 2 id:

    3 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1000 parent_id: xxx
  40. 大量のN+1が発生する例 : 子孫ノードを探索( Adjacency List) id: 1 parent_id: null id:

    6 parent_id: 1 id: 700 parent_id: xxx id: 2 parent_id: 1 id: 1,000 parent_id: xxx
  41. 子孫ノードを探索( Adjacency List) id: 1 parent_id: null id: 6 parent_id:

    1 id: 700 parent_id: xxx id: 2 parent_id: 1 id: 1,000 parent_id: xxx クエリ実⾏回数: 1,000 N+1問題が⾟すぎる😭
  42. 子孫ノードを探索( Adjacency List) id: 1 parent_id: null id: 6 parent_id:

    1 id: 700 parent_id: xxx id: 2 parent_id: 1 id: 1,000 parent_id: xxx リファクタリングでN+1問題を解消しよう
  43. Closure Tableの例(始点が id=1の経路のみ) ancestor_id descendant_id depth 1 1 0 1

    2 1 1 3 2 1 4 3 1 5 4 1 6 5 1 7 6 folder_pathsテーブル id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6
  44. Closure Tableの例(始点が id=1の経路のみ) ancestor_id descendant_id depth 1 1 0 1

    2 1 1 3 2 1 4 3 1 5 4 1 6 5 1 7 6 folder_pathsテーブル id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6
  45. Closure Tableの例(始点が id=1の経路のみ) ancestor_id descendant_id depth 1 1 0 1

    2 1 1 3 2 1 4 3 1 5 4 1 6 5 1 7 6 folder_pathsテーブル id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6
  46. Closure Tableの例(始点が id=2の経路のみ) folder_pathsテーブル ancestor_id descendant_id depth 2 2 0

    2 3 1 2 4 2 2 5 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2
  47. Closure Tableの例(始点が id=2の経路のみ) folder_pathsテーブル ancestor_id descendant_id depth 2 2 0

    2 3 1 2 4 2 2 5 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2
  48. Closure Tableの例(始点が id=2の経路のみ) folder_pathsテーブル ancestor_id descendant_id depth 2 2 0

    2 3 1 2 4 2 2 5 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2
  49. 祖先ノードを取得( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  50. 祖先ノードを取得( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  51. 祖先ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 foldersとfolder_pathsを結合
  52. 祖先ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 終点がid=4である経路に絞る
  53. 子孫(descendant)ノードを取得( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  54. 子孫(descendant)ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ? 空欄に⼊るのは? 1. descendant_id = 1 2. ancestor_id = 1
  55. 子孫(descendant)ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ? 空欄に⼊るのは? 1. descendant_id = 1 2. ancestor_id = 1 始点がid=1である経路に絞る
  56. 子孫(descendant)ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 空欄に⼊るのは? 1. descendant_id = 1 2. ancestor_id = 1 始点がid=1である経路に絞る
  57. 子孫(descendant)ノードを取得( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 始点がid=1である経路に絞る 空欄に⼊るのは? 1. descendant_id = 1 2. ancestor_id = 1
  58. 子ノードを作成( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7
  59. 子ノードを作成( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7
  60. 子ノードを作成( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7 ノードを挿⼊して
  61. id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3

    parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7 子ノードを作成( Closure Table) 親が終点の経路を取得し
  62. id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3

    parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 8 parent_id: 7 子ノードを作成( Closure Table) 親の経路を使って ⾃⾝が終点の経路を挿⼊
  63. Folder作成時にFolderPathを作成 id: 3 parent_id: 2 id: 8 parent_id: 7 id:

    3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  64. Folder作成時にFolderPathを作成 ノード作成時にコールバックを実⾏ id: 3 parent_id: 2 id: 8 parent_id: 7

    id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  65. Folder作成時にFolderPathを作成 経路をバルクインサートするための配列を定義 id: 3 parent_id: 2 id: 8 parent_id: 7

    id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 バルクインサート
  66. Folder作成時にFolderPathを作成 ⾃⾝への経路を配列に追加 id: 3 parent_id: 2 id: 8 parent_id: 7

    id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  67. Folder作成時にFolderPathを作成 親ノードが終点の経路を取得 id: 3 parent_id: 2 id: 8 parent_id: 7

    id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  68. Folder作成時にFolderPathを作成 ⾃⾝が終点の経路作成 id: 3 parent_id: 2 id: 8 parent_id: 7

    id: 3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  69. Folder作成時にFolderPathを作成 id: 3 parent_id: 2 id: 8 parent_id: 7 id:

    3 parent_id: 2 id: 6 parent_id: 1 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 7 parent_id: 6 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 バルクインサート
  70. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6
  71. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6
  72. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 親を更新して
  73. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⼦孫を取得して
  74. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  75. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  76. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  77. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  78. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  79. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  80. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 3 parent_id:

    2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝と⼦孫が終点である 経路を削除
  81. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⾃⾝が終点の経路を作成
  82. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⼦孫が終点の経路を作成
  83. ノードを移動( Closure Table) id: 3 parent_id: 2 id: 6 parent_id:

    1 id: 3 parent_id: 2 id: 3 parent_id: 7 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 ⼦孫が終点の経路を作成
  84. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  85. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 親の変更時にコールバックを実⾏
  86. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫のノードを取得
  87. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  88. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  89. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  90. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  91. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  92. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  93. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を削除
  94. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を作成
  95. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を作成
  96. Folder移動時にFolderPathを削除・作成 id: 3 parent_id: 2 id: 3 parent_id: 2 id:

    3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾃⾝と⼦孫の経路を作成
  97. Closure Table まとめ • メリット ◦ 親⼦関係を超えた読み込みが容易 ▪ JOINを伴うSQL問い合わせ1回 •

    デメリット ◦ ストレージコスト ◦ 書き込み操作が困難 ▪ 経路を正しい状態に維持する必要がある ◦ 実装が複雑
  98. • SQLにおけるCTE (Common Table Expression) の⼀種 ◦ WITH句のやつ • 再帰的にクエリを実⾏できる

    • 主要なRDBMSでサポートされている ◦ MySQL 8.0 ◦ PostgreSQL 8.4 ◦ SQLite 3.8.3 • Rails 7.2.0からQueryMethods#with_recursiveが登場 Recursive CTE(再帰CTE)とは
  99. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2
  100. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 CTEを定義
  101. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾮再帰項: 初回だけ実⾏される
  102. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 再帰項: 繰り返し実⾏される
  103. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 再帰項: 繰り返し実⾏される
  104. 祖先ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 3 parent_id: 2 CTEを利⽤
  105. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2
  106. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 CTEを定義
  107. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 ⾮再帰項: 初回だけ実⾏される
  108. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 7 parent_id: 6 再帰項: 繰り返し実⾏される
  109. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 再帰項: 繰り返し実⾏される
  110. 子孫ノードを取得( Adjacency List + Recrusive CTE) id: 3 parent_id: 2

    id: 6 parent_id: 1 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 2 parent_id: 1 id: 3 parent_id: 2 id: 4 parent_id: 3 id: 3 parent_id: 2 id: 5 parent_id: 3 id: 3 parent_id: 2 id: 7 parent_id: 6 CTEを利⽤
  111. Adjacency List + Recursive CTE 概要 • メリット ◦ Adjacency

    Listからデータ構造を変える必要がない ▪ 問い合わせ⽅法が変わるのみ ◦ 親⼦関係を超えた読み込みをクエリ1回で取得できる ◦ Adjacency Listのままなので書き込みが容易 • デメリット ◦ DB内で再帰処理を繰り返す必要があるため深い階層だと低速になる
  112. リファクタリング手法の決定 Closure Table 読み込み 非常に高速 書き込み 低速 データ構造 閉包テーブルを追加 実装

    複雑 Recursive CTE 高速 高速(追加操作なし) 変更なし 容易 • Adjacency Listからの移⾏についてClosure TableとRecursive CTEを⽐較
  113. 実験:子孫ノードを探索 • ActiveRecord上でトップレベルの ノードから葉ノード探索処理の 実⾏時間を計測 • 3つの⼿法(Adjacency List, Closure Table,

    Adjacency List + Recursive CTE)の パフォーマンスを⽐較 • 階層構造は完全2分⽊とする • 深さを変えて実験 id: 3 parent_id: 2 id: 3 parent_id: 2 id: 1 parent_id: null id: 3 parent_id: 2 id: 3 parent_id: 2 id: 512 parent_id: xxx id: 3 parent_id: 2
  114. 実験結果 深さ6 (127ノード) 深さ9 (1,023ノード) 深さ13 (16,383ノード) Adjacency List (real

    [ms]) 38.050 244.622 3749.056 Closure Table (real [ms]) 4.228 4.020 4.024 Recursive CTE (real [ms]) 10.869 10.918 14.111 • Adjacency Listと⽐較して、Closure Tableでは⼤幅に⾼速化を達成 • Closure Tableは横ばい、Recursive CTEは深くなるほど低速
  115. 深さ6 (127ノード) 深さ9 (1,023ノード) 深さ13 (16,383ノード) Adjacency List (real [ms])

    38.050 244.622 3749.056 Closure Table (real [ms]) 4.228 4.020 4.024 Recursive CTE (real [ms]) 10.869 10.918 14.111 実験結果 • Adjacency Listと⽐較して、Closure Tableでは⼤幅に⾼速化を達成 • Closure Tableは横ばい、Recursive CTEは深くなるほど低速 x 8 x 60 x 931
  116. 実験結果 • Adjacency Listと⽐較して、Closure Tableでは⼤幅に⾼速化を達成 • Closure Tableは横ばい、Recursive CTEは深くなるほど低速 深さ6

    (127ノード) 深さ9 (1,023ノード) 深さ13 (16,383ノード) Adjacency List (real [ms]) 38.050 244.622 3749.056 Closure Table (real [ms]) 4.228 4.020 4.024 Recursive CTE (real [ms]) 10.869 10.918 14.111
  117. 実験結果 • Adjacency Listと⽐較して、Closure Tableでは大幅に高速化を達成 • Closure Tableは横ばい、Closure Tableは深くなるほど低速 深さ6

    (127ノード) 深さ9 (1,023ノード) 深さ13 (16,383ノード) Adjacency List (real [ms]) 38.050 244.622 3749.056 Closure Table (real [ms]) 4.228 4.020 4.024 Recursive CTE (real [ms]) 10.869 10.918 14.111 Closure TableとRecursive CTEについて SQLクエリの実⾏時間を⾒てみよう
  118. 深さ6 (127ノード) 深さ9 (1,023ノード) 深さ13 (16,383ノード) Closure Table (Execution Time

    [ms]) 0.037 0.031 0.023 Recursive CTE (Execution Time [ms]) 0.056 0.274 4.185 クエリの実行時間( Closure Table vs Recursive CTE) • EXPLAIN ANALYZE で実⾏時間の測定 • 結果、クエリ実⾏⾃体はClosure Tableの⽅が⾼速 ◦ ActiveRecord上ではクエリ実⾏以外にボトルネックがあり、 パフォーマンス差がでていなかった可能性
  119. まとめ ゴール 1. ✅ 階層構造を表現する2つのデータ構造を理解する ◦ Adjacency List(隣接リスト) ◦ Closure

    Table(閉包テーブル) 2. ✅ Recursive CTE(再帰CTE)の概要を理解する 3. 階層化機能において適切な⼿法を選択ができるようになる
  120. まとめ • 階層化機能(親⼦関係を超えた読み込みを伴う場合)のおすすめ⼿法 おすすめ手法 開発初期 Adjacency List 小規模の階層(100ノード) Adjacency List

    + Recursive CTE 中規模の階層(1,000ノード) 読み込みが多い → Closure Table 書き込みが多い → Adjacency List + Recursive CTE 大規模の階層(10,000ノード) 頻繁に書き込みがなければ Closure Table
  121. まとめ ゴール 1. ✅ 階層構造を表現する2つのデータ構造を理解する a. Adjacency List(隣接リスト) b. Closure

    Table(閉包テーブル) 2. ✅ Recursive CTE(再帰CTE)の概要を理解する 3. ✅ 階層化機能において適切な⼿法を選択ができるようになる
  122. 参考文献 • Models for hierarchical data with SQL and PHP

    ◦ https://www.slideshare.net/slideshow/models-for-hierarchical-data/4179181 • Bill Karwin「SQLアンチパターン 第⼆版 」 ◦ https://www.oreilly.co.jp/books/9784814400744 • Add support for recursive CTEs in ActiveRecord ◦ https://github.com/rails/rails/pull/51601 • ancestory ◦ https://github.com/stefankroes/ancestry • closure_tree ◦ https://github.com/ClosureTree/closure_tree • PostgreSQL Wiki CTEReadme ◦ https://wiki.postgresql.org/wiki/CTEReadme • MySQL 8.0 レファレンスマニュアル 13.2.15 WITH (共通テーブル式) ◦ https://dev.mysql.com/doc/refman/8.0/ja/with.html
  123. 1. folder_pathsテーブルを作成 2. FolderPathモデルを作成 3. Folderモデルに build_pathメソッドを実装 4. 右のスクリプトを実⾏する ◦

    トップフォルダから順に build_pathメソッドを叩いて 経路を作成している Adjacency List から Closure Table への移行手順