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

Railsパフォーマンス・チューニング入門

 Railsパフォーマンス・チューニング入門

Kaigi on Railsでの発表資料です。

オンライン開催ということで比較的広い層が参加するだろうと思い、比較的初学者向けかつ実践的な題材としてパフォーマンスチューニングの話をしました。

kokuyouwind

October 03, 2020
Tweet

More Decks by kokuyouwind

Other Decks in Programming

Transcript

  1. $ whoami 黒曜 @kokuyouwind Misoca → 弥⽣株式会社 (We're Hiring!) ⼀応Rails

    エンジニア 最近はAWS とかDocker 周りを 弄っていることが多い
  2. FULL SCAN の例 id speaker start_time end_time 1 tenderlove 10:10

    10:40 2 kokuyouwind 10:50 11:10 3 toshimaru 11:10 11:30 4 lulalala 11:30 11:40 5 beta_chelsea 12:40 12:50 6 makicamel 12:50 13:10 sessions
  3. FULL SCAN の例 ジョーカー(joker1007) さんの セッション開始時刻はいつ? SELECT start_time FROM sessions

    WHERE speaker = "joker1007"; 1 2 3 Sessions.find_by(speaker: 'joker10 .pluck(:start_time) 1 2
  4. FULL SCAN の例 id speaker start_time end_time 1 tenderlove 10:10

    10:40 2 kokuyouwind 10:50 11:10 3 toshimaru 11:10 11:30 4 lulalala 11:30 11:40 5 beta_chelsea 12:40 12:50 6 makicamel 12:50 13:10 発表者名を順に全部⾒る( テーブルフルスキャン)
  5. インデックスのイメージ 索引 speaker id b beta_chelsea 5 f fukajun 11

    j joker1007 17 k koic 16 kokuyouwind 2 l lulalala 4 index(speaker on sessions)
  6. インデックスのイメージ 索引 speaker id b beta_chelsea 5 f fukajun 11

    j joker1007 17 k koic 16 kokuyouwind 2 l lulalala 4 j から始まるspeaker を⼀発で⾒つける
  7. インデックスのイメージ 索引 speaker id j joker1007 17 ID からレコードを⾒つけてstart_time を⾒つける

    id speaker start_time end_time 16 koic 16:20 16:40 17 joker1007 16:40 17:00 18 a_matsuda 17:10 17:40
  8. Filesort の例 id event_id speaker start_time end_time 1 1 tenderlove

    11:45 12:10 2 2 tenderlove 10:10 10:40 3 2 koic 16:20 16:40 4 1 koic 14:00 14:25 5 2 kokuyouwind 10:50 11:10 id name 1 RubyKaigi Takeout 2020 2 Kaigi on Rails events sessions
  9. Filesort の例 Kaigi on Rails のセッションを 開始時刻順で教えて? SELECT * FROM

    events WHERE name = 'Kaigi on Rails'; SELECT * FROM sessions WHERE event_id = 2 ORDER BY start_time ASC; 1 2 3 4 5 6 Events.find_by(name: 'Kaigi on Rai .sessions.order(:start_time) 1 2
  10. Filesort の例 索引 event_id id 1 1 1 1 4

    2 2 2 2 3 2 5 id name 1 RubyKaigi Takeout 2020 2 Kaigi on Rails events index (event_id on sessions)
  11. Filesort の例 id event_id speaker start_time end_time 2 2 tenderlove

    10:10 10:40 3 2 koic 16:20 16:40 5 2 kokuyouwind 10:50 11:10 sessions index (event_id on sessions) 索引 event_id id 2 2 2 2 3 2 5 ↑ 順に並んでいない!
  12. Filesort の例 id speaker start_time end_time 2 tenderlove 10:10 10:40

    3 koic 16:20 16:40 5 kokuyouwind 10:50 11:10 id speaker start_time end_time 2 tenderlove 10:10 10:40 5 kokuyouwind 10:50 11:10 3 koic 16:20 16:40 ⾒つけたレコードをstart_time 順に メモリ上で並べ替える! (Filesort)
  13. 複合インデックスの例 索引 event_id start_time id (1, 11) 1 11:45 1

    (1, 14) 1 14:00 4 (2, 10) 2 10:10 2 2 10:50 5 (2, 16) 2 16:20 3 index (event_id, start_time on sessions) ↑ event_id とstart_time を 組み合わせた索引
  14. 複合インデックスの例 索引 event_id start_time id (2, 10) 2 10:10 2

    2 10:50 5 (2, 16) 2 16:20 3 index (event_id, start_time on sessions) id speaker start_time end_time 2 tenderlove 10:10 10:40 5 kokuyouwind 10:50 11:10 3 koic 16:20 16:40 sessions start_time でソート済みの状態で取れる!
  15. 複合インデックス( 悪い例) 索引 start_time event_id id (10, 2) 10:10 2

    1 10:50 2 4 (11, 1) 11:45 1 2 (14, 1) 14:00 1 5 (16, 2) 16:20 2 3 index (start_time, event_id on sessions) ↑ start_time が先だと、 event_id=2 を索引から探せない!
  16. 複合インデックス( 悪い例) 索引 start_time speaker id (10:10, t) 10:10 tenderlove

    1 (10:50, k) 10:50 kokuyouwind 2 (11:10, t) 11:10 toshimaru 3 (11:30, l) 11:30 lulalala 4 index (start_time, speaker on sessions) start_time だけで昇順に並ぶため、speaker はソートされない 必要ならam/pm 区分カラムなどを作る必要がある Sessions.where(start_time: '0:00'..'12 .order_by(:speaker) 1 2
  17. N+1 クエリの例 イベントごとに、イベント名と セッションの発表者を表示して? SELECT * FROM events; SELECT *

    FROM sessions WHERE event_id SELECT * FROM sessions WHERE event_id 1 2 3 4 Events.each do |event| p event.name event.sessions.each { p _1.speake end 1 2 3 4
  18. N+1 クエリの例 イベントが100 個あると… SELECT * FROM events; -- =>

    100 個のイベント SELECT * FROM sessions WHERE event_id = 1 SELECT * FROM sessions WHERE event_id = 2 SELECT * FROM sessions WHERE event_id = 3 -- ... SELECT * FROM sessions WHERE event_id = 9 SELECT * FROM sessions WHERE event_id = 1 1 2 3 4 5 6 7 8 9
  19. includes の例 SELECT * FROM events; -- => 100 個のイベント

    SELECT * FROM sessions WHERE event_id IN (1, 2, . 1 2 3 4 Events.includes(:sessions).each do |e p event.name event.sessions.each { p _1.speaker end 1 2 3 4 クエリ2 回で完了!
  20. ケース1: 関連⽂書の取得 任意の2 要素に関連を持たせるため、 クラス名とID から⾃⼒でLookup していた FromType FromID ToType

    ToID Estimate 1 Invoice 1 Invoice 1 DeliverySlip 1 Invoice 1 DeliverySlip 2 def converted_docs DocumentConversion .find_by(from_type: 'Invoice', fro .map do |doc| doc.to_type.constantize.find(doc end end 1 2 3 4 5 6 7
  21. ケース1: 関連⽂書の取得 ポリモーフィック関連付けに書き換え、 includes を指定できるようにした class Invoice has_many :document_conversions, as:

    :source_docu has_many :converted_delivery_slips, through: :document_conversions, source: :converted_document, source_type: 'DeliverySlip' end # usage Invoice.all.includes(:converted_delivery_slips) 1 2 3 4 5 6 7 8 9 10
  22. ケース2: PDF 変換 def group_original_code_points_by_bit(os2) Hash.new { |h, k| h[k]

    = [] }.tap do |result| os2.file.cmap.unicode.first.code_map.each_key do |co # === ↓2 重ループ内で cover? を呼んでいる!!! === range = UNICODE_RANGES.find { |r| r.cover?(code_po # ... 1 2 3 4 5 6 7 8 https://github.com/prawnpdf/ttfunk/blob/1.6.2.1/lib/ttfunk/table/os2.rb#L273-L275
  23. まとめ パフォーマンス改善には、まず計測から とりあえずAPM ツールを⼊れて、定期的に⾒よう 重いDB クエリはEXPLAIN してインデックスを貼ろう FULL SCAN やfilesort

    は重いので倒そう 複合インデックスは効き⽅を想像して貼ろう N+1 クエリが発⽣しないようincludes しよう CPU 処理の問題は、プロファイラで根本原因を調査しよう データ構造やアルゴリズムを⾒直せないか考えよう