Website logo

Robert Chang

技術部落格

Elasticsearch - 寫入資料以及基礎的錯誤恢復機制

上一篇文章 中,解釋了 Elasticsearch ( 以下簡稱 ES ) 是如何通過 routing 的計算找到正確的 shard 來讀取 document 的內容。

這篇文章則要稍微談論一下寫入時會發生的事情,以及基礎的錯誤恢復機制。

寫入資料時,發生什麼事?

上一篇文章 最後提到了讀取資料時,會找到 replication group 中的 shard 並返回 document 的內容。

但寫入時,ES 一定會找到 primary shard 進行更新,接著當 primary shard 認證過這個請求後,才在背景採用並行的方式去更新 replication group 中的其他 shards,如下圖所示:

screen shot

為什麼一定要先找到 primary shard 呢?

其實這個問題可以想,也可以不用想;啊就是要有一個老大當作參照,所以先更新老大在更新小弟們,很合理啊。

所以可以問得方向比較像是,如果不先更新老大,會怎麼樣?

  1. 沒辦法確定誰是 The truth,也就是誰是真實的?
  2. 避免衝突,如果先更新 replica shard,那是要先更新哪一個 replica shard 呢?我要怎麼確保一制性?

就是為了確保資料的一致性以及方便資料恢復,所以 ES 才會選定一個 primary shard 進行更新,接著藉由這個 primary shard 去散播最新的版本。

複製到一半,primary shard 死了

雖然這種情況不常見,但電腦的世界就是要為了許多很罕見的狀況做準備,許多的情況發生後,若是沒有相對應的手段,都會對系統造成毀滅性的打擊。

如下圖所示,當把新的版本複製到第一個之後,原本的 primary shard 就死了。

screen shot

這時候 ES 就會啟動 recovery 的程序,選出新的 primary shard:

recovery 的程序是一個很大的主題,如果有機會可以更詳細的說明,但總之會選出新的 primary shard

screen shot

接著問題還是沒有解決,現在 primary shard 和 replica shard 之間的狀態和資料不一致,現在讀取資料只有 50% 的機率讀取到正確的資料。

那 ES 是如何處理的呢?接著要介紹三個新的名詞。

Primary Term

這是 ES 用來分辨新舊 primary shard 的值,已剛剛的例子來說,ES 選出一個新的 primary shard,所以現在的 primary term 就會是 2

{
  "_index": "users",
  "_id": "1",
  "_version": 15,
  "_seq_no": 15,
  "_primary_term": 1, // 這個
  "found": true,
  "_source": {
    ...
  }
}

其實就是一個簡單的計數器,來記錄這個 replication group 經歷過幾次的 primary shard 更動。

為什麼需要?

這樣才可以確保一個 replication group 只有一個 primary shard,當舊的那個 primary shard 復原後,它還是把自己當作是 primary shard 然後想要接受寫入的請求時,ES 就會確認其 primary term 是否大於或等於現在的 primary term,如果比較小,說明你是舊的,不可以繼續擔任 primary shard,就會被降級成 replica shard。

Sequence Number

當我們每次對 document 進行 CRUD 時,ES 都會替這次的動作分配一個唯一的號碼,也就是 _seq_no

這可以讓 ES 去追查那些哪些動作還沒有執行。

{
  "_index": "users",
  "_id": "1",
  "_version": 15,
  "_seq_no": 15, // 這個
  "_primary_term": 1,
  "found": true,
  "_source": {
    ...
  }
}

為什麼需要?

我覺得這個比 primary term 還要更好的理解,就像是關聯式資料庫會去讀取 binlog 來根據動作復原資料一樣,Sequence number 也是一樣的概念,當今天 primary shard 是 100 時,那還在 97 的 replica shard 就是落後的,需要 98, 99, 100 的步驟完成更新。

所以我自己對於這個名詞沒有 為什麼需要? 的想法,就覺得很合理。

Check Point

其實 check point 的背後就是 sequence number,那你一定會想問,差在哪?

首先,check point 分成 global checkpoint 以及 local checkpoint

global checkpoint 對應到的是 replication group 中所有 shards 已經執行的 最小的 sequence number

local checkpoint 則是對應到 shard 自己本身的所執行的 最大的 sequence number

為什麼需要?

需要一個值,來確保哪些操作是 已經被執行,這邊就是 global checkpoint 的工作,如果都仰賴 sequence number,那哪一個數字是最小的呢?

遍歷所有的 shards 是一種方式,但當今天 shards 散落在各個節點,各個機器時,這都是額外的消耗,甚至更多的不確定性。

local checkpoint 很好理解,就是今天這個 shard 執行到哪一個 sequence number 了。

如何讓狀態一致?

回到今天的問題,現在新的 primary shard 還沒有被更新到最新的狀態,我們都知道只有其中一個 replica shard 是最新的。

假設,現在的 replica shard 的 local checkpoint 是 100,而新的 primary shard 是 99,還差最後一步沒有被更新到。

這時候新的 primary shard 會去檢查一下這個 replication group 並讀取超過其自身的 local checkpoint

此時的 global checkpoint 是 99,要注意唷!

發現有一個比自己的高,就會更新,並應用上這個 100 的 checkpoint,這樣就也更新到最新囉!

screen shot

過了 5 分鐘,這時候因為一些操作 global checkpoint 變成 103。

這時候原本死掉的 primary shard 復活,會因為 primary term 低於現在 replication group 的緣故,被降級成 replica shard。

接著它會去和新的 primary shard 比對一下自己的 local checkpoint 有沒有趕上 global checkpoint,發現落後了 3 個操作,就會趕緊補上,也變成 103。

screen shot

上一篇文章Elasticsearch - Upsert & Routing

下一篇文章Elasticsearch - Versioning 以及樂觀鎖