跳到主要內容
節省工具箱Jason Tools
經驗分享

OpenSearch Unassigned Shard 重試分配

Jason Cheng 發布 瀏覽 1 次
OpenSearch Unassigned Shard 重試分配
本文目錄
  1. 確認 unassigned 的原因
  2. 手動觸發重試
  3. 其他分配相關指令
  4. 結論
  5. 參考資料

上一篇〈OpenSearch 無法取消的 Zombie Task 處理與預防〉最後是靠重新啟動節點解決的,但節點回來之後,叢集一直停在 yellow,有 5 個 shard 一直是 unassigned,等了很久都沒有自己分配回去。

原因是這幾個 shard 在節點出問題期間已經重試分配失敗 5 次,達到上限,之後就算原本卡住的原因排除了,OpenSearch 也不會再自動重試,要手動觸發。這篇整理處理方式,以及幾個相關的分配指令。

確認 unassigned 的原因

以下指令的 $ES 代表 OpenSearch 位址,有開安全外掛的話改用 https 並加上帳密(建議用 .netrc,不要把密碼寫在指令裡)。

ES=https://opensearch.example.com:9200

先列出哪些 shard 沒分配,以及原因:

curl -s "$ES/_cat/shards?v&h=index,shard,prirep,state,node,unassigned.reason" | grep -v STARTED

prirep 欄位的 p 是 primary、r 是 replica。unassigned.reason 常見的有 NODE_LEFT(節點離開)、ALLOCATION_FAILED(分配失敗)、INDEX_CREATED(剛建立還沒分配)。

再用 allocation explain 看詳細原因。不帶參數會解釋第一個找到的 unassigned shard:

curl -s "$ES/_cluster/allocation/explain?pretty"

指定某個 shard:

curl -s -X POST "$ES/_cluster/allocation/explain?pretty" \
  -H 'Content-Type: application/json' -d '{
    "index": "graylog_120",
    "shard": 0,
    "primary": false
  }'

這次看到的重點是這幾行:

"unassigned_info" : {
  "reason" : "ALLOCATION_FAILED",
  "failed_allocation_attempts" : 5,
  ...
},
"can_allocate" : "no",
"allocate_explanation" : "cannot allocate because allocation is not permitted to any of the nodes",

再往下看 node_allocation_decisions,每個節點的 decider 會寫出不能分配的理由,這次是 max_retry:

shard has exceeded the maximum number of retries [5] on failed allocation attempts -
manually call [/_cluster/reroute?retry_failed=true] to retry

OpenSearch 自己就在訊息裡告訴你要怎麼做了。

手動觸發重試

curl -s -X POST "$ES/_cluster/reroute?retry_failed=true&pretty" \
  -H 'Content-Type: application/json' -d '{}'
  • retry_failed=true
    把所有因為分配失敗而停住的 shard 重試次數歸零,重新嘗試分配
  • -d '{}'
    reroute 本來可以在 body 帶分配指令,這裡不需要,送一個空的 JSON 就好,帶著 Content-Type 比較不會遇到格式錯誤

執行後觀察叢集狀態,shard 會進入 INITIALIZING,再變成 STARTED:

# 分配與復原進度
curl -s "$ES/_cat/recovery?v&active_only=true"

# 叢集狀態,等到 green
curl -s "$ES/_cluster/health?pretty&wait_for_status=green&timeout=5m"

這次 5 個 shard 一下就分配回去,叢集回到 green。

使用時機

  • allocation explain 看到 failed_allocation_attempts 達到 5,而且決策理由是 max_retry
  • 原本卡住的原因(shard lock、磁碟空間、節點離線)已經排除

注意:如果原因還沒排除就重試,只會再失敗 5 次又停住。例如磁碟使用率超過水位線(watermark)造成的分配失敗,要先清出空間,重試才有意義。

重試上限可以依 index 調整(index.allocation.max_retries,預設 5),但一般不需要改,與其調高上限,不如找出一直失敗的原因。

其他分配相關指令

預覽 reroute 結果

加上 dry_run=true 只模擬、不實際執行,有疑慮時先跑一次:

curl -s -X POST "$ES/_cluster/reroute?retry_failed=true&dry_run=true&pretty" \
  -H 'Content-Type: application/json' -d '{}'

檢查分配是否被關閉

維護時常會暫時關閉 shard 分配,結束後忘了打開,shard 也會一直 unassigned:

# 看目前設定
curl -s "$ES/_cluster/settings?pretty&include_defaults=false"

# 恢復分配(設回 null 就是預設值 all)
curl -s -X PUT "$ES/_cluster/settings" -H 'Content-Type: application/json' -d '{
  "persistent": { "cluster.routing.allocation.enable": null }
}'

檢查磁碟水位線

curl -s "$ES/_cat/allocation?v"

磁碟使用率超過 low 水位線(預設 85%)就不會再分配新的 replica 到該節點,超過 high(預設 90%)會開始把 shard 搬走。Graylog 環境最常見的就是保留期限設太長、磁碟滿了。

強制分配 primary(最後手段)

如果 primary shard 遺失、所有正常的副本都救不回來,才考慮強制分配。有兩種:

# 用節點上「過期的副本」當 primary,可能遺失最近寫入的資料
curl -s -X POST "$ES/_cluster/reroute?pretty" -H 'Content-Type: application/json' -d '{
  "commands": [{
    "allocate_stale_primary": {
      "index": "graylog_120",
      "shard": 0,
      "node": "opensearch-node2",
      "accept_data_loss": true
    }
  }]
}'

# 分配一個「全新的空 primary」,該 shard 原本的資料全部遺失
curl -s -X POST "$ES/_cluster/reroute?pretty" -H 'Content-Type: application/json' -d '{
  "commands": [{
    "allocate_empty_primary": {
      "index": "graylog_120",
      "shard": 0,
      "node": "opensearch-node2",
      "accept_data_loss": true
    }
  }]
}'

node 填的是節點名稱(_cat/nodes 看得到的 name)或節點 ID,不是 IP。

注意:這兩個指令都會遺失資料,accept_data_loss 一定要設 true 才能執行,就是要你確定自己在做什麼。能用 allocate_stale_primary 就不要用 allocate_empty_primary;如果有快照,從快照還原會比強制分配好。replica 遺失時不需要用這兩個,primary 還在就會自動重建 replica。

Graylog 的另一個選項

如果卡住的是 Graylog 已經不在寫入的舊 index,而且資料不重要,也可以考慮直接在 Graylog 的「System → Indices」刪掉那個 index,比強制分配單純。寫入中的 index 則不要這樣做。

結論

shard 一直 unassigned 時,先用 allocation explain 看原因,大部分情況它會直接告訴你下一步。像這次 max_retry 的狀況,排除原本的問題後一行 retry_failed=true 就解決了,不要一開始就去用 allocate_empty_primary,那是真的救不回來才用的。

參考資料

Jason Cheng

Jason Cheng

企業開源技術顧問,專注架構規劃與實務導入

關於作者 →

延伸閱讀