# 前後端效能優化工具 + 範例 + 心得
## 前端優化範例
某 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
```ruby=
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
```ruby
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
=================================================
```