Postgres 在建立索引時,會阻塞 DML 也就是 lock 整個 table 的寫入(讀取則不影響),所以當需要對大資料量的 Table 打 index 時,會造成有 Downtime 時間,這在 Production 這種高並發的環境下是不適合的。
並發計算建立 index
Postgres 在建立索引時有提供 CONCURRENTLY 的選項可以設定(並發計算),這樣一來就不會影響 table 的寫入行為(INSERT, UPDATE, DELETE)
可以同時處理一般的寫入行為,也可以打 index, 不過是生成時間會再慢一些,但至少沒有停機時間。
在 Rails 4 之後可以直接加入 algorithm: :concurrently 在你的 migration 檔案內
例如:
class AddIndexToUsers < ActiveRecord::Migration
disable_ddl_transaction!
def change
add_index :users, :alive, algorithm: :concurrently
end
end
不過這邊要注意的是, disable_ddl_transaction!
與 algorithm: :concurrently
是一組的,必須一起使用。
如果你有其他的 migrate 要做,建議分開。
為什麼要用 disable_ddl_transaction! ?
因為在 Rails 的 migration 中預設每個執行都會包一個 transaction ,如果有失敗的情況就會整個 Rollback,但如果要使用 Postgres 當中的 Concurrently 去建立 index 的話,必須要在 transaction 之外 (concurrent indexes must be created outside a transaction.)
副作用
一般 CREATE INDEX 情況下,只會掃一次表,但如果使用這個並發計算參數時,則會引發 Database 掃兩次表,並且等待所有潛在會讀到該索引的事務結束,這樣一來會增加 CPU + I/O 的負擔,不過拿這個負擔來換 Downtime 想必是划算的。
失敗情況
使用 concurrently 參數時,有可能遇到創建索引失敗的狀況,比方說建立 uniqu 索引結果發現數據重複了,此時 Table 上會存在索引,這是因為帶 concurrently 參數的指令發出後,會馬上在 Log 裡面插一個 index 記錄進去,又因為這個索引建立失敗了,會被標記一個 INVALID 的狀態
"idx" btree (col) INVALID