練習 - 015 (效能改善 N+1 Query)
上一堂課,已經可以正確的顯示投票紀錄。但是透過rails console
可以看到 server 的 log,每次投票都會計算一次。 (N+1 Query)
為了改善效能問題,此時我們就需要使用Counter Cache
Counter Cache 概念
- 當我們的票寫入到投票紀錄的當下,同時會更新一份候選人票數到「候選人表單」的欄位中。
- 這樣我們在讀取候選人票數的時候,就可以不用再 count 票數的欄位。
- 因為在候選人的
table
已經有專門的欄位記載票數。 - 這個欄位的「數字」會在每次按下
vote
的時候更新欄位的數字。
所以我們現在需要幫原本的Candidate Model
增加欄位:
- 欄位名稱(依照慣例<關聯的table名字_count>):vote_logs_count
- 欄位資料型態:integer
打開終端機輸入指令產生一個migration,rails g migration add_counter_cache_to_candidate
再到 db/migrate/
可以找到剛剛透過終端機指令產生的 migration 檔案,檔案裡長出來的class
名稱AddCounterCacheToCandidate
其實會對應上我們輸入的名字。
上網找尋 Rails 新增欄位的方法add_column
- add_column(要加入的表單名稱, 欄位名稱, 資料型態, option = {})
進到AddCounterCacheToCandidate
migration檔案在change
方法裡面增加一行程式碼。
1 | add_column :candidates, :vote_logs_count, :integer, default: 0 |
這裡要非常注意table名稱是複數(candidates)!
這裡要非常注意table名稱是複數(andidates)!
這裡要非常注意table名稱是複數(candidates)!
這裡已經確認migration檔案裡面的內容沒有問題,接下來就是要把這個migration「具現化」,使用終端機執行rails db:migrate
接著打開db/migrate/schema.rb
來看看是否剛剛新增的欄位有加入成功。登愣!!!恭喜成功加入。
但是不是增欄位這樣就有效果,還要進到VoteLog Model
裡面做一件很神奇的事情。要在原本的belongs_to :candidate
後面加上counter_cache: true
。
增加counter_cache: true
的用意,就是當我們把那張票寫入到資料庫的同時,也會執行「更新票數」的動作。把票數更新到哪裡呢?
- 會把票數更新到
candidates
表單的vote_logs_count
欄位
來確認看看是否有成功寫入。重新整理瀏覽器。確認目前頁面資料。
按下vote
,投給ddd
候選人8票。
確定每次的票數都會同步更新到畫面上,目前ddd
候選人有10票。
觀察一下終端機上rails server
顯示的紀錄,每次投票的時候有做了哪些事情,先截圖其中一筆資料。
確實在寫入票數資料的同時也去更新了candidates
表單的vote_logs_count
欄位。
但是我們也會發現現在rails server 每次撈資料的時候,都會再去撈一次,是因為在view/index.html.erb
的<%= candidate.vote_logs.count %>
它會去vote_logs
這個表單
去撈count。
不是從candidates
表單裡面的vote_logs_count
欄位撈資料。所以要更改為<%= candidate.vote_logs_count %>
這時候重新整理瀏覽器畫面,看到的票數資料才是正確的。對eee
候選人按下vote
。
我們可以發現 rails server 的紀錄顯示只有撈一次而已。
是不是發現效能有變更好一點了。但其實還可以再更優化。就是在view/index.html.erb
的<%= candidate.vote_logs_count %>
去修改。
我們現在是讓它直接讀取那個欄位的資料,但是其實還有一個更「漂亮」的寫法。我們一樣透過vote_logs
,畢竟candidate
有 has_many :vote_logs
。
所以我們可以把<%= candidate.vote_logs_count %>
改成<%= candidate.vote_logs.size %>
為什麼用這樣的寫法?目前重新整理頁面看到的結果兩種寫法顯示的結果是一樣的,可以為什麼要用.size
而不是直接讀取vote_logs_count
欄位。為什麼用.size
的寫法?
- 原因就是:以程式碼的可讀性,使用
<%= candidate.vote_logs.size %>
比較可以看出關聯性。
不曉得到這邊大家有沒有發現一個事情,現在顯示的票數,好像跟一開始的不太一樣。打開rails console
來看一下目前總票數。總共投了24票。
可是瀏覽器畫面顯示的票數似乎不太一樣。
是因為後來才加入的counter cache
,現在瀏覽器畫面顯示的票數是counter cashe
紀錄的票數。
我們可以google搜尋關鍵字:rails api reset counter_cache
找到了可以使用的方法,但是我們要測試看看,可以用rake
語法來寫一個task
。要寫在哪個目錄底下呢?
我們到db/lib/tasks
目錄裡面新增一個任務rest_counter.rake
檔案。
這樣以後在終端機輸入rake -T
就可以在任務列表中找到我們今天新增的這個reset_counter
這個 task。
接著我們就要來寫程式了,進到reset_counter.rake
,在tesk ...do...end
裡面來寫程式。
再多增加一行puts "prepare to reset counter"
,表示執行reset_counter
前會先印出prepare to reset counter
執行完畢印出done!
,所以在最下面增加一行puts "done!"
終端機輸入rails db:reset_counter
這時候我們再回到瀏覽器重新整理畫面,會發現票數就更新了。
從原本的 0票 8票 1票 —> 7票 10票 7票。這樣票數就正確了!!!
目前我們已經有 新增/修改/刪除/投票 都有成功寫出來了。但是現在我們的code長得樣子是滿雜亂的,需要再整理一下。把一些功能寫成方法,這樣整個專案的程式碼可讀性會更高。
下一堂課程,整理程式碼
參考來源:為你自己學 Ruby on Rails (https://railsbook.tw/)