そのpreloadは必要?見過ごされたpreloadが技術的負債として爆発した日
by
mugi
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
そのpreloadは必要? 見過ごされたpreloadが技術的負債として爆発した日 Shuta Mugikura (mugi) Kaigi on Rails 2025 2025/9/26
Slide 2
Slide 2 text
自己紹介 1 麦倉 柊太(Shuta Mugikura) / mugi ⚫ X: @mugi_1359 ⚫ GitHub: @mugitti9 ⚫ Kaigi on Rails オーガナイザー ⚫ 株式会社ギフティ
Slide 3
Slide 3 text
自己紹介 2 株式会社ギフティ ⚫ eギフトプラットフォーム ⚫ eギフトの発券・流通・販売まで一気通貫で提供
Slide 4
Slide 4 text
障害の概要 3 ある日の夜… 突然500番台エラーを返しすぎていることを知らせる通知がなる
Slide 5
Slide 5 text
障害の概要 4 ある日の夜… OOM GETのAPI
Slide 6
Slide 6 text
障害の概要 5 ある日の夜… OOM GETのAPI サーバのメモリは 1GBもあるはず
Slide 7
Slide 7 text
障害の概要 6 ある日の夜… 400MB
Slide 8
Slide 8 text
障害の概要 7 ある日の夜… 400MB grandchilrenは不要
Slide 9
Slide 9 text
障害の概要 8 ある日の夜… grandchilrenは不要 400MB ↓ 80%削減
Slide 10
Slide 10 text
9 preloadのメモリコスト
Slide 11
Slide 11 text
preloadのメモリコスト 10 Article ID: 1 Article ID: 2 Article ID: 3 Article.preload(comments: :likes)
Slide 12
Slide 12 text
preloadのメモリコスト 11 Article ID: 1 Article ID: 2 Article ID: 3 SELECT * FROM comments WHERE article_id IN (1, 2, 3) Article.pluck(:id) SELECT * FROM likes WHERE comment_id IN (1, 2, 3, ...)
Slide 13
Slide 13 text
preloadのメモリコスト 12 Article ID: 1 Comment ID: 1 Comment ID: 2 Comment ID: 3 Article ID: 2 Comment ID: 4 Comment ID: 5 Comment ID: 6 Article ID: 3 Comment ID: 7 Comment ID: 8 Comment ID: 9 Like ID: 1 Like ID: 2 Like ID: 3 Like ID: 4 Like ID: 5 Like ID: 6 Like ID: 7 Like ID: 8 Like ID: 9 Like ID: 10 Like ID: 11 Like ID: 12 Like ID: 13 Like ID: 14 Like ID: 15 Like ID: 16 Like ID: 17 Like ID: 18
Slide 14
Slide 14 text
preloadのメモリコスト 13 Article ID: 1 Article ID: 2 Article ID: 10 ・ ・ ・ Comment ID: 1 Comment ID: 2 Comment ID: 3 Comment ID: 100 ・ ・ ・ Like ID: 1 Like ID: 2 Like ID: 3 Like ID: 100 ・ ・ ・
Slide 15
Slide 15 text
preloadのメモリコスト 14 Article ID: 1 Article ID: 2 Article ID: 10 ・ ・ ・ Comment ID: 1 Comment ID: 2 Comment ID: 3 Comment ID: 100 ・ ・ ・ Like ID: 1 Like ID: 2 Like ID: 3 Like ID: 100 ・ ・ ・ 約100MB
Slide 16
Slide 16 text
preloadのメモリコスト 15 Article ID: 1 Article ID: 2 Article ID: 10 ・ ・ ・ Comment ID: 1 Comment ID: 2 Comment ID: 3 Comment ID: 100 ・ ・ ・ Like ID: 1 Like ID: 2 Like ID: 3 Like ID: 100 ・ ・ ・ もしこれが 使われないpreloadだったら?
Slide 17
Slide 17 text
障害時に何が起きていたか 16 content_tag_groups.preload( content_tag: { contents: { content_publish_permission: { content_registrations: :available_content_selections } } } )
Slide 18
Slide 18 text
障害時に何が起きていたか 17 children.sizeをするためpreload Parent parent.children_countを持つ 設計の方針変更 (例: counter_cacheの導入) parent = Parent.preload(:children).first parent.children.each { p it.do_some } scopeの追加 (例: activeのscopeの絞り込みを追加) parent = Parent.preload(:children).first parent.children.active.each { p it. do_some } Parent Children
Slide 19
Slide 19 text
障害時に何が起きていたか 18 content_tag_groups.preload( content_tag: { contents: { content_publish_permission: { content_registrations: :available_content_selections } } } )
Slide 20
Slide 20 text
障害時に何が起きていたか 19 content_tag_groups.preload(:content_tag)
Slide 21
Slide 21 text
障害時に何が起きていたか 20 content_tag_groups.preload( content_tag: { contents: { content_publish_permission: { content_registrations: :available_content_selections } } } ) content_tag_groups.preload(:content_tag) 約 400 MB ↓ 80%削減
Slide 22
Slide 22 text
障害時に何が起きていたか ⚫ 不要なpreloadが維持される ⚫ プロダクトの成長によってデータが増えていく ⚫ preloadをした際に作成されるActiveRecordインスタンスが増加 Out Of Memory が発生 21
Slide 23
Slide 23 text
22 なぜ未然に防げなかったのか 障害後にどのような対応を行ったか
Slide 24
Slide 24 text
なぜ未然に防げなかったのか ⚫ メモリ監視やアラートが弱く、兆候を早期に検知できなかった ⚫ preload のメモリ負荷についての認識がチーム内で不十分だった 23
Slide 25
Slide 25 text
なぜ未然に防げなかったのか ⚫ メモリ監視やアラートが弱く、兆候を早期に検知できなかった ⚫ preload のメモリ負荷についての認識がチーム内で不十分だった 24
Slide 26
Slide 26 text
なぜ未然に防げなかったのか 25
Slide 27
Slide 27 text
なぜ未然に防げなかったのか 26 意識的に見る必要がある ↓ 誰も負債が積み重なることに気付けない
Slide 28
Slide 28 text
なぜ未然に防げなかったのか 27
Slide 29
Slide 29 text
なぜ未然に防げなかったのか 28 ⚫ メモリ監視やアラートが弱く、兆候を早期に検知できなかった ⚫ preload のメモリ負荷についての認識がチーム内で不十分だった
Slide 30
Slide 30 text
なぜ未然に防げなかったのか 29 preloadは「N+1 を防ぐための便利なSolution」 とりあえず preload しておけば安心
Slide 31
Slide 31 text
なぜ未然に防げなかったのか 30 AVOID eager loading detected Parent => [:children] Remove from your query: .includes([:children])
Slide 32
Slide 32 text
なぜ未然に防げなかったのか 31 Model / Service / Viewなど Controllerから叩く部分の変更 それを利用する側のController, APIを確認
Slide 33
Slide 33 text
なぜ未然に防げなかったのか 32 Article HasManyAssociation (Comments) Comment Comment Comment target
Slide 34
Slide 34 text
33 まとめ
Slide 35
Slide 35 text
まとめ 34 ⚫ 不要なpreloadの蓄積によってサービスの可用性に関わるな障害へ ⚫ メモリのメトリクスが不足していた ⚫ preloadに対するリスクを認識できていなかった
Slide 36
Slide 36 text
まとめ 35 ⚫ 些細な負債の積み重ねが大きな障害へとつながる ⚫ 不要なpreloadという些細な問題から、プロダクトの成長によって、その 負債が蓄積し、全社的な障害へとつながった ⚫ Railsにおいても銀の弾丸は存在しない ⚫ 常にトレードオフが存在し、これを入れたら全てが解決するというような 手法は存在しない ⚫ 例えばpreloadの場合は、N+1を防げる一方で、不要なものを放置すると 多大なメモリ利用等につながる
Slide 37
Slide 37 text
まとめ 36 ⚫ 持続可能なRailsを維持するためには、ドメインの変化に合わせて 絶え間なく修復し続けることが必要 ⚫ 今回はそれを怠ったため、大規模な障害を引き起こしてしまった
Slide 38
Slide 38 text
まとめ 37
Slide 39
Slide 39 text
まとめ 38 そのpreloadは(今も)必要?
Slide 40
Slide 40 text
39 Thank you