該文章同步發佈於:我的部落格
也歡迎關注我的 Facebook 以及 Instagram 接收軟體相關的資訊!
在讀取 Elasticsearch ( 以下簡稱 ES ) 的 document 時,一定會注意到一個 _version
的欄位。
這代表什麼呢?第一時間聯想到的會是版本紀錄,可以回朔 document 到某個版本,但其實不是!
這只是 ES 單純紀錄操作次數的一個方式,每一個 CUD 的操作都會讓 _version
加 1 的正整數,而預設值是 1。
而這個 _version
還有分成 external ( 外部 ) 以及 interval ( 內部 ) 兩種類型。
內部就是 ES 內建的,會隨著操作次數遞增,而外部呢?則是將控制權轉到我們的手上。
下面是一個利用 REST API 新增外部版本的方式:
其實就是帶上 version=你自訂的版本號&version_type=external
這樣的參數。
接著你就可以把 _version
的控制權掌握在你的應用程式上。
但說實在 _version
並沒有想像中的好用,因為他並不是真的版本控制,也沒辦法真的提供什麼實質的意義 ( 除非你的應用程式需要 )。
所以早期的 ES 其實是拿這個值並採用樂觀鎖的方式來解決 race condition 的問題。
當然,除了關聯式資料庫之外,任何可以同時接受大量請求的應用程式都會有 race condition 的問題,只是看競爭的資源是什麼。
假設有兩個請求同時對一個 document 進行更新。
在第一時間,兩個請求都是拿到庫存為 10 的值,而這時候第一個請求買了一個,所以將庫存更新為 9。
這時候第二個請求也把庫存更新成 9,對於 ES 來說,它本身並不知道這件事情是錯誤的,但這在商業邏輯上造成很大的問題,糟糕的使用者體驗,因為一定會有一個人買不到。
該怎麼解決呢?在早期,會帶上的 _version
的 payload 去更新 document,這樣第二個請求就會被阻擋下來,因為此時此刻的 _version
已經變成 2 了。
這樣就可以解決 race condition 的問題,接著該怎麼處理呢?
重試是一種策略,第二個請求可以再去讀取一次 inventory 拿到最新的 _version
接著就可以完成更新。
剛剛一直提到 早期 這兩個字,是因為現在的 ES 可以用別的方式來執行樂觀鎖,也就是 上一篇文章 中所提到的 primary term
以及 sequence number
兩個值。
為什麼不繼續使用 _version
?
在 ES 這個巨大的分散式系統中,許多分片是散落在不同的節點上的,當嘗試只使用一個 _version
去執行樂觀鎖時,是不夠準確的,因為我們對於 node 或是 shard 的存活狀況一無所知。
_version
確實有可能在 primary shard 分割時,造成相同的 _version
擁有不同的內容,所以道理和 上一篇文章 中一樣,primary term
搭配 sequence number
就是讚!
使用 primary term
搭配 sequence number
可以算是現代 ES 的一種保障方式,可以確保 primary shard 以及操作步驟的準確性。
該怎麼做呢?
很簡單,其實和帶上 version
的 payload 一樣,只是變成帶上 if_primary_term=
以及 if_seq_no=
的 payload 去判斷就可以做到樂觀鎖而且更穩定。
下面是撞到鎖的畫面:
我帶上的 if_seq_no=19
是錯的,因為 _seq_no
已經到了 20,所以更新會失敗。
_version
這個欄位確實是沒有什麼太大的意義,只能告訴你這個 document 經過了幾次的變動,僅此而已。
而關於 ES 使用樂觀鎖處理 race condition 的方式,現在還是使用 primary term
搭配 sequence number
是最好也是最保險的方式。