博客

连按两次发布,现在只会发布一次

同一个应用的三个发布请求相隔 84 毫秒到达我们的 API,每个都启动了该应用的一次完整部署。它们相互竞争,抢占同一个托管环境,其中好几次发布因为别的发布先一步完成而失败,唯一可见的迹象只是一封「发布失败」的邮件,而应用本身最终是正常的。现在,当同一应用的另一次发布正在进行时到达的新发布,不会再启动第二次构建。数据库决定哪个请求胜出,其余请求则会被告知真实情况:已有一次发布正在进行。

发生了什么

当你在 Mythex 上发布应用时,API 会为其记录一次部署,并请求我们的编排器(负责运行构建的服务)来构建并发布它。发布可以从 Publish 按钮、另一个浏览器标签页,或者在你要求时由代理发起。双击、两个标签页,或者一个被送达两次的代理请求,都可能在几乎同一时刻为同一个应用产生不止一次发布。

这次有三个请求在 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 繁體中文(香港) 繁體中文(台灣)