網誌

撳兩次發布,而家只會發布一次

同一個 app 嘅三個發布請求,相隔 84 毫秒抵達我哋嘅 API,每個都觸發咗該 app 嘅一次完整部署。佢哋互相爭奪同一個託管平台,而結果是幾個發布因為另一個已經搶先,而失敗;唯一可見嘅徵兆,就係一封「發布失敗」嘅電郵,而 app 本身最終都正常。而家,如果同一 app 嘅發布喺另一個發布運行期間抵達,就唔會再啟動第二次建構。資料庫會決定邊個請求勝出,其他請求則會得到實際嘅答案:發布已經在進行中。

發生咗咩事

當你喺 Mythex 發布一個 app,API 會為佢記錄一個部署,並要求我哋嘅編排器(即係執行建構嘅服務)去建構並發布佢。發布可以由發布按鈕開始、由另一個瀏覽器分頁開始,或者由代理在你要求發布時開始。連按兩下、兩個分頁,或者代理請求被送出兩次,都可能同時為同一個 app 產生多次發布。

呢次個案中,三個請求喺 84 毫秒內抵達。每個都運行咗一次完整部署。喺託管平台上面,同一個 app 嘅發布會喺同一秒內建立,而其中三個因為其他嘅已經在進行中而失敗。之後一個發布卡住,但 app 之後運作正常。

咁樣令問題好容易被忽略。用戶見到嘅係一個正常運作嘅 app。問題只會以發布失敗嘅電郵形式浮現。

點解冇嘢阻止佢

發布代碼確實有一個步驟,會檢查該項目進行中嘅部署。但佢所做嘅,係喺開始之前,將所有進行中嘅部署標記為失敗,理由是「被新發布取代」。佢本意係清理因崩潰程序而遺留下嚟嘅建構。

實際上,就係一個喺建構中途抵達嘅第二個發布,會宣告第一個已死,然後啟動自己嘅建構。兩個建構都會針對同一個 app 完整運行。冇任何鎖;反而有一個步驟,主動幫忙清出一條路,令競爭得以發生。

對於一個 app 嘅第一次發布,仲有一個更奇怪嘅徵狀。由於仲未有部署記錄可以重用,兩個同時發生嘅第一次發布,都會試圖插入同一筆記錄。資料庫對 app 嘅連結名稱(即 slug,例如 your-app.mythex.ai 入面嘅 your-app)有唯一索引,所以第二次插入會失敗。API 將呢個失敗翻譯成「呢個連結名稱已被使用」,並建議用戶改名,但該 app 其實正正喺嗰一刻,以嗰個名稱成功發布緊。

修正:宣告即係鎖

而家認領一筆部署記錄,就係一個單一嘅條件式資料庫更新:只有當冇人正在發布該記錄時,先將它取過來並標記為建構中。因為係一個陳述式,資料庫會解決同一毫秒內抵達嘅請求。只有一個請求會改變該資料列,其他請求會看到冇資料列被改變,於是停止,永遠唔會到達編排器。

鎖亦需要一個出口,以應付持有者死亡嘅情況。如果 API 喺發布途中重新啟動,該資料列就會永遠停留喺「建構中」,而 app 就會再冇辦法發布。所以,如果一個資料列嘅擁有者已經停止發送心跳,就可以被重新認領。正在運行嘅發布每 30 秒更新一次該資料列,而沉默五分鐘就視為死亡。呢個門檻來自心跳,而唔係估計建構需要幾耐。用戶自己嘅 Dockerfile 建構可以合理咁運行好長時間,而一個仍然持續發送心跳嘅慢速建構,會保留自己嘅認領。

喺發布之前,已經唔會再運行將所有進行中部署標記為失敗嘅步驟。只有被死掉嘅程序遺留嘅資料列,才會被清理。

誠實告知輸家

輸掉嘅請求而家會收到一個清楚嘅答案:publish_in_progress,並附上正在運行中嘅部署 ID。每個可以啟動發布嘅地方都會處理呢個情況:

  • 工作區會跟住已經運行中嘅建構,而唔會喺上面顯示錯誤。
  • 代理會被告知該 app 嘅發布已經在進行中,並叫佢等待,而唔係被告知有嘢失敗。以前呢種資訊可能會令佢去「修正」Dockerfile 並重試。
  • 第一次發布遇到連結名稱衝突會被檢查:如果佔住該名稱嘅資料列,屬於本項目自己正在進行中嘅發布,答案就係「已經發布緊」,而唔係「該名稱已被使用」。真正與另一個項目發生衝突,仍然會收到改名嘅訊息。

點樣驗證

我哋寫咗一個重現腳本:喺真實資料庫同一個假扮嘅編排器上,同時運行同一個 app 嘅三次發布,然後計算編排器被要求建構幾次。呢個測試涵蓋第一次發布,以及一個正在運行中 app 嘅重新發布。修正之前,計數係兩次部署。修正之後,係一次。

腳本亦涵蓋咗逃生出口:佢將該 app 嘅資料列標記為建構中,但十分鐘都冇心跳,然後再發布一次,並檢查新嘅發布會認領該資料列並成功運行。

單元測試將鎖限制喺它必須做到嘅兩件事:同時三次重新發布之中,只有一個可以通過,另外兩個會被告知發布正在進行中;以及擁有者已經死亡嘅建構可以被認領。另一個測試則確認,新嘗試唔會沿用上一次嘗試嘅錯誤訊息。

我哋學到嘅嘢

  • 清理代碼有時會隱藏競爭。 「先將所有進行中嘅嘢標記為失敗,然後開始」聽落好似衛生措施。但如果有兩個呼叫者,就等於每一個都會取消另一個。
  • 讓資料庫負責仲裁。 先讀後寫,會留下一個容許另一個請求插入嘅空隙。一個單一嘅條件式更新就冇呢個空隙。
  • 每個鎖都需要與存活狀態掛鉤嘅過期機制。 冇咗佢,崩潰就會變成 app 卡住。
  • 錯誤訊息就係指示。 「該名稱已被使用」令人去做錯誤嘅事。正確嘅訊息更簡單,而且真實。

更多關於發布嘅資料,可以睇文件:點樣發布。另一個相關嘅修正係:你已發布嘅 app 喺重新發布時仍然保持運行。

Mythex · Pricing · 用 AI 建造任何嘢 · 網站及應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(台灣)