Try   HackMD

前後端效能優化工具 + 範例 + 心得

前端優化範例

某 school 反應 admin/students/edit 很慢

每次進入該頁面 progress bar 需要跑個 20 秒

前端優化範例 - 工具 - chrome or edge

前端優化範例 - 工具 - chrome or edge

前端優化範例 - 工具 - chrome or edge

前端優化範例 - 如何抓到問題點

  1. 先找耗時最久的區塊(火焰圖上最大的區塊)
  2. 找看看
    1. 有沒有重複的區塊一直出現(重複的呼叫)
    2. 有沒有某些區塊特別大的(執行速度很慢的 function)

前端優化範例 - 解決方式

此範例的問題點:

  • 當 conditional field 的值有變化時,關聯的 field 會需要根據值改成「顯示」或「隱藏」。
  • 這邊會把所有的 conditional fields 都檢查一次,尋找關聯的 field 時會使用 jQuery 的 find
  • 問題點:同樣的 field 會被重複使用 find

解決方式:

  • 先把 field cache 起來,避免重複呼叫 find 取得 field

前端優化範例 - 結果

Before: 358.31ms

After: 36.70 ms

ref: https://github.com/eduvo/openapply/pull/14478

後端效能優化 - 常見問題

  • N + 1
  • DB 沒有加上適合的 index
  • DB Transaction 太久
  • 一次從 DB 拿太多東西出來
  • 從 DB 拿了不需要的東西出來

後端效能優化 - 什麼時候需要優化?

  • 不破壞程式可讀性,且可以簡易優化的地方,隨時可以優化
    • N + 1 query
    • DB 沒有加上適合的 index
  • 系統瓶頸
  • 客戶反應有問題的地方

後端效能優化 - 檢查工具

newrelic

後端效能優化 - 檢查工具

gem - bullet

https://github.com/flyerhzm/bullet

不過也要注意在加上 includes 時是否要需要加上限制條件,避免拿太多 objects 出來

後端效能優化 - 檢查工具

explain

後端效能優化 - 檢查工具

gem ruby-prof + gem ruby-prof-flamegraph + flamegraph generator

https://ruby-prof.github.io/

https://github.com/brendangregg/FlameGraph

後端效能優化 - 優化 N + 1 query

可以使用 includes 修正

不過也要注意在加上 includes 時是否要需要加上限制條件,避免拿太多 objects 出來

後端效能優化 - 優化 bulk insert 或 bulk update

gem - activerecord-import

https://github.com/zdennis/activerecord-import

可以加速 bulk insert & bulk update

並支援 MySQL 的 ON DUPLICATE KEY UPDATE

後端效能優化 - 優化 query 太慢

加 DB Index

explain

加上 status index 後

before After
Type ALL Ref
Key NULL Index_pamoja_courses_on_status
Rows 169 104

部分 query 會需要多個欄位合併搜尋,可以考慮加 multiple-column Indexe

譬如 checklist_items 的

idx_checklist_items_unique_school_checklist_item_id_student_id (school_checklist_item_id,student_id,active) UNIQUE

後端效能優化 - 優化 query 太慢

可能是拿太多不需要的 row,可以用 where 或是 join 加上限制

Example 1: 譬如我需要的是 enrolled 的 pamoja_courses,那 query 就要下

PamojaCourse.where(status: 'enrolled')

而不是

PamojaCourse.all

Example 2: 譬如我需要的是「有 parent」 的 student

那就可以用 Student.joins(:parent) 來限制

後端效能優化 - 優化 query 太慢

可能是一次拿太多資料

可以利用 find_each 或 find_in_batches 分批拿

https://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each

後端效能優化 - 優化 query 太慢

如果某個計算比較慢,但是參數不常變化

可以考慮放到 Rails 的 Cache 裡面

後端效能優化 - 優化使用者體驗

如果某段程式裡面含有執行速度較慢的程式,但不需立即得到結果

可以考慮把該段程式放到 background job 裡面

後端效能優化 - 程式執行較慢

有一種情況是拿了不需要的資料,可以利用 select 或 pluck 拿需要的欄位即可

譬如

後端效能優化 - 程式執行較慢

其他 ruby 程式的優化可以參考 https://github.com/JuanitoFatas/fast-ruby

後端效能優化 - 驗證工具 benchmark-ips

https://github.com/evanphx/benchmark-ips

ActiveRecord::Base.logger = nil Benchmark.ips do |x| x.report('without select') do Student.all.map{|s| s.name } end x.report('with select') do Student.all.select(:first_name, :last_name, :preferred_name, :other_name).map{|s| s.name } end nil end

後端效能優化 - 驗證工具 scientist

https://github.com/github/scientist

https://github.com/eduvo/openapply/pull/14760/files#diff-02ece8ad5786f65765ddb7e18dfba6a384fff6b9882ab77af6cd3927524ba191R20

class ScientistExperiment
  include Scientist::Experiment

  # ...

  def publish(result)
    puts "================================================="
    puts "matced? #{result.matched?}"
    puts "science.#{name}.control: #{result.control.duration}"
    puts "science.#{name}.candidate: #{result.candidates.first.duration}"
    puts "================================================="
  end
end
=================================================
matced? true
science.family-roster-collect-ids.control: 4.199053999999705
science.family-roster-collect-ids.candidate: 0.04198200000064389
=================================================