部落格

連按兩次發布,現在只會發布一次

同一個應用程式的三個發布請求,在相隔 84 毫秒後抵達我們的 API,每一個都啟動了該應用程式的一次完整部署。它們彼此爭搶同一個主機,結果有好幾個發布因為另一個已經先到達而失敗,唯一可見的跡象只是一封「發布失敗」的郵件,應用程式本身最後卻是正常的。現在,如果有發布在同一應用程式的另一個發布進行中抵達,就不會再啟動第二次建置。資料庫決定哪個請求獲勝,其他請求則會被告知真相:已有一個發布正在進行中。

發生了什麼

當你在 Mythex 上發布應用程式時,API 會為它記錄一個部署,並要求我們的協調器(負責執行建置的服務)建置並發布它。發布可以從「發布」按鈕開始、從另一個瀏覽器分頁開始,或在你要求時由代理開始。連按兩下、兩個分頁,或一個重複抵達的代理請求,都可能讓同一個應用程式幾乎同時產生多次發布。

在這個案例中,三個請求在 84 毫秒內抵達,每一個都執行了完整部署。在主機平台上,同一應用程式的發布版本在同一秒內被建立,其中三個因為其他版本已在進行中而失敗。之後有一個版本卡住了,但應用程式事後是可以運作的。

這正是它容易被忽略的原因。使用者看到的是一個正常運作的應用程式。失敗只會以發布失敗的郵件形式出現。

為什麼沒有任何東西阻止它

發布程式碼其實有一個步驟會查看該專案中進行中的部署。但它做的事情是:在開始之前,把每個進行中的部署都標記為失敗,理由是「已被新的發布取代」。它原本是為了清理因程序當機而遺留的建置。

實際上,這表示在建置中途抵達的第二個發布,會宣告第一個已經死亡,並開始自己的建置。兩者都會對同一個應用程式跑到完成。沒有任何鎖;反而有一個步驟會主動替競爭清出道路。

對於應用程式的第一次發布,還有另一個更奇怪的症狀。由於還沒有可以重用的部署紀錄,兩個同時進行的第一次發布都會嘗試插入同一筆紀錄。資料庫對應用程式的連結名稱(也就是 slug,例如 your-app.mythex.ai 中的 your-app)有唯一索引,所以第二次插入失敗。API 把這個結果翻譯成「該連結名稱已被使用」,建議使用者去改名一個其實正在用該名稱成功發布的應用程式。

修正方式:宣告即是鎖

現在,宣告一筆部署紀錄是一次有條件的資料庫更新:只有在沒有人正在發布的情況下,才會把這一列取出並標記為建置中。因為它是一個陳述式,資料庫會在同一毫秒內抵達的請求之間做出決定。只有一個請求會改變這一列,其他請求會發現零列被改變,於是停止,且永遠不會抵達協調器。

鎖也需要在持有者死亡時能夠釋放。如果 API 在發布中途重新啟動,那一列就會永遠停留在「建置中」,應用程式也就無法再發布。因此,擁有者已停止送出心跳訊號的列可以被重新宣告:進行中的發布每 30 秒會更新一次這一列,而沉默五分鐘就視為死亡。這個門檻來自心跳,而不是對建置時間的猜測。建置使用者自己的 Dockerfile 可能合法地執行很長時間,而一個仍在送出心跳的慢速建置會保留它的宣告。

在發布之前執行、會把所有進行中部署標記為失敗的那個步驟,已不再執行。現在只會清除由已死亡的程序遺留下來的列。

告訴落敗的一方真相

落敗的請求現在會得到一個清楚的回答:publish_in_progress,並附上正在執行中的部署 ID。每一個可以啟動發布的地方都會處理它:

  • 工作區會跟隨已在執行中的建置,而不是在它上面顯示一個錯誤。
  • 代理會被告知該應用程式的發布已在進行中,請等待它完成,而不是被告知某件事失敗了。過去那樣的訊息可能會讓它去「修正」Dockerfile 並再試一次。
  • 首次發布時的連結名稱衝突會被檢查:如果佔用該名稱的那一列是本專案自己正在進行中的發布,回答就是「已在發布中」,而不是「該名稱已被使用」。與其他專案的真正衝突,仍會得到改名的提示。

我們如何驗證

我們寫了一個重現腳本,在真實的資料庫與一個模擬的協調器上,同時執行同一個應用程式的三次發布,並計算協調器被要求了多少次部署。它涵蓋首次發布與一個已上線應用程式的重新發布。修正前,它計數為兩次部署;修正後為一次。

該腳本也涵蓋了退路:它把應用程式的列標記為建置中,但十分鐘沒有心跳,然後再次發布,並檢查新的發布是否能宣告該列並執行。

單元測試把鎖限定在它必須做到的兩件事上:三個同時進行的重新發布中,只有一個能通過,另外兩個會被告知有發布正在進行;而擁有者已死亡的建置可以被宣告。另一個測試則檢查新的嘗試不會沿用上一次嘗試的錯誤訊息。

我們的收穫

  • 清理程式碼可能暗藏競爭。 「先把進行中的全部標記為失敗,然後開始」讀起來像是衛生措施。但若有兩個呼叫者,就會變成彼此互相取消。
  • 讓資料庫來裁決。 先讀取再寫入,會給另一個請求留下空隙。單一條件式更新則不會。
  • 每個鎖都需要與存活狀態綁定的過期機制。 沒有它,當機就會變成應用程式永遠卡住。
  • 錯誤訊息就是指示。 「該名稱已被使用」讓人去做錯誤的事。正確的訊息更簡單,而且是真的。

關於發布的更多資訊,請參閱文件:如何發布。另一個相關的修正是:你的已發布應用程式在重新發布時仍保持上線。

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 简体中文 繁體中文(香港)