Slide 1

Slide 1 text

GraphQL データ取得高速化 tsukiji.graphql #1 2023/07/06 ham

Slide 2

Slide 2 text

自己紹介 【略歴】 新卒でSIerとして就職 その後、Web系企業やスタートアップを経てファインディに参画 ファインディではFindy Team+のフロント&バックエンド開発を担当 React+Rails+GraphQL+AWSを使って開発しています 浜田 直人 (ham) ファインディ株式会社 @hamchance0215

Slide 3

Slide 3 text

N+1問題

Slide 4

Slide 4 text

users { id profile { name } } N+1問題 – userを複数返却するQuery – id fieldはusersテーブルから取得 – profileはprofilesテーブルから取得 # Ruby code def resolve User.all end

Slide 5

Slide 5 text

users { id profile { name } } N+1問題 – usersは10件Hit select * from users; – profilesを1件ずつ取得 => N+1問題 select * from profiles where user_id = 1; select * from profiles where user_id = 2; … select * from profiles where user_id = 10;

Slide 6

Slide 6 text

users { id profile { name } } N+1問題 – usersに対応するprofileを事前ロード # Ruby code def resolve User.all.preload(:profile) end

Slide 7

Slide 7 text

users { id profile { name } } N+1問題 – usersは10件Hit select * from users; – hitしたusersに対応したprofilesを事前ロード select * from profiles where user_id in (1, 2, …, 10);

Slide 8

Slide 8 text

users { id } N+1問題 – usersは10件Hit select * from users; – fieldに指定していなくてもprofilesを事前ロー ドしてしまう select * from profiles where user_id in (1, 2, …, 10);

Slide 9

Slide 9 text

users { id profile { name } } N+1問題 – usersに対応するprofileを遅延ロード # Ruby code def resolve # fieldにprofileがある場合、profile を取得するuser_idを記憶しておき、最後にま とめて取得する User.all end

Slide 10

Slide 10 text

N+1問題 – 事前ロード🙅 – 指定していないfieldも読み込んでしまう – 遅延ロード🙆 – 指定されたfieldのみまとめて取得

Slide 11

Slide 11 text

無駄な計算

Slide 12

Slide 12 text

user(id:$id) { id reviewCount } 無駄な計算 – userを1件返却するQuery – userが投稿したレビュー数を取得 – reviewsテーブルをcount # Ruby code def resolve(id:) user = User.find_by(id:) { id: user.id, review_count: user.reviews.count, } end

Slide 13

Slide 13 text

user(id:$id) { id reviewCount } 無駄な計算 – users select * from users where id = 1; – reviews select count(*) from reviews where user_id = 1;

Slide 14

Slide 14 text

user(id:$id) { id } 無駄な計算 – reviewCountを指定していなくても reviews.countが計算される # Ruby code def resolve(id:) user = User.find_by(id:) { id: user.id, review_count: user.reviews.count, } end

Slide 15

Slide 15 text

user(id:$id) { id } 無駄な計算 – reviewCountを指定していなくても reviews.countが計算される # Ruby code def resolve(id:) user = User.find_by(id:) { id: user.id, review_count: user.reviews.count, } end reviewCountが指定されているときだけ、 reviews.countが計算されるような実装に変 更する!

Slide 16

Slide 16 text

無駄な計算 – 常に全てのfieldを計算してしまう🙅 – 指定されたfieldのみ計算🙆

Slide 17

Slide 17 text

直列で取得

Slide 18

Slide 18 text

hoge { heavy1 heavy2 } 直列で取得 – heavy1やheavy2は重い処理 – レスポンスまでheavy1+heavy2の処理時間 # Ruby code def resolve { heavy1: Hoge.heavy1_process, heavy2: Hoge.heavy2_process, } end

Slide 19

Slide 19 text

hoge { heavy1 heavy2 } 直列で取得 – heavy1とheavy2は同時に行われるので遅い 方の時間がレスポンスタイムとなる # Ruby code def resolve # heavy1, 2を非同期で取得 heavy1 = Hoge.sync_heavy1_process heavy2 = Hoge.sync_heavy2_process { heavy1:, heavy2:, } end

Slide 20

Slide 20 text

直列で取得 – 直列だと重い場合は並列取得を検討🙆 – 直列でも十分速い場合は問題なし! – 並列取得にすると実装の複雑度が上がるのでメ リットがない場合はやらない方が良い

Slide 21

Slide 21 text

まとめ

Slide 22

Slide 22 text

- N+1問題 - 遅延ロードを検討する - 無駄な計算 - fieldが指定されているときだけ計算する - 重いfield - 並列実行を検討する まとめ