Nic Lin's Blog

喜歡在地上滾的工程師

[Rails] 如何快速的對大資料量建立索引,避免 Downtime

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

參考來源

comments powered by Disqus