Blog

Pulsar Publicar dos veces ahora publica una sola vez

Tres peticiones de publicación de la misma app llegaron a nuestra API con 84 milisegundos de diferencia, y cada una inició un despliegue completo de esa app. Competían entre sí por el mismo alojamiento, varias de las versiones resultantes fallaron porque otra había llegado antes, y la única señal visible era un correo de «la publicación falló»; la app en sí acabó bien. Ahora, una publicación que llega mientras otra ya se está ejecutando para la misma app no inicia una segunda compilación. La base de datos decide qué petición gana, y a las demás se les dice la verdad: ya hay una publicación en curso.

Qué pasó

Cuando publicas una app en Mythex, la API registra un despliegue para ella y pide a nuestro orquestador (el servicio que ejecuta las compilaciones) que la compile y la publique. La publicación puede iniciarse desde el botón Publicar, desde otra pestaña del navegador, o desde el agente cuando le pides que publique. Un doble clic, dos pestañas o una petición del agente que llega dos veces pueden generar más de una publicación de la misma app casi al mismo tiempo.

En este caso, llegaron tres en 84 ms. Cada una ejecutó un despliegue completo. En la plataforma de alojamiento, las versiones de la misma app se crearon en el mismo segundo, y tres de ellas fallaron porque las otras ya estaban en curso. Una versión posterior se quedó y la app funcionó después.

Eso es lo que lo hizo fácil de pasar por alto. El usuario vio una sola app funcionando. Los fallos solo aparecían como correos de fallo de versión.

Por qué nada lo detenía

El código de publicación sí tenía un paso que revisaba los despliegues en curso del proyecto. Pero lo que hacía era marcar como fallido cada despliegue en curso antes de empezar («reemplazado por una nueva publicación»). Estaba pensado para limpiar compilaciones dejadas por un proceso que se cayó.

En la práctica, una segunda publicación que llegaba a mitad de compilación declaraba muerta a la primera e iniciaba la suya. Ambas se ejecutaban hasta el final contra la misma app. No había ningún bloqueo; había un paso que despejaba activamente el camino para una carrera.

Había además un síntoma más extraño en la primera publicación de una app. Sin un registro de despliegue que reutilizar, dos primeras publicaciones concurrentes intentaban insertar uno. La base de datos tiene un índice único sobre el nombre de enlace de la app (su slug, como tu-app en tu-app.mythex.ai), así que la segunda inserción fallaba. La API traducía eso en «ese nombre de enlace ya está ocupado», aconsejando al usuario que renombrara una app que, en ese momento, se estaba publicando correctamente con ese nombre.

El arreglo: la reclamación es el bloqueo

Reclamar el registro de despliegue es ahora una única actualización condicional en la base de datos: toma esta fila y la marca como compilando solo si nadie la está publicando. Como es una sola sentencia, la base de datos resuelve las peticiones que llegan en el mismo milisegundo. Exactamente una de ellas cambia la fila. Las otras ven que no cambió ninguna fila, se detienen y nunca llegan al orquestador.

Un bloqueo también necesita una salida cuando su titular muere. Si la API se reiniciaba a mitad de una publicación, la fila quedaría marcada como «compilando» para siempre y la app no podría volver a publicarse. Así que una fila cuyo dueño ha dejado de enviar latidos —la publicación en curso actualiza la fila cada 30 segundos, y cinco minutos de silencio cuentan como muerte— puede ser reclamada de nuevo. Ese umbral viene del latido, no de una suposición sobre cuánto tarda una compilación. Compilar un Dockerfile propio del usuario puede durar mucho de forma legítima, y una compilación lenta que sigue enviando latidos conserva su reclamación.

El paso que marcaba como fallidos todos los despliegues en curso ya no se ejecuta antes de una publicación. Solo se limpian las filas que dejó atrás un proceso muerto.

Decirle la verdad al que pierde

La petición que pierde ahora recibe una respuesta clara: publish_in_progress, junto con el ID del despliegue que se está ejecutando. Cada punto que puede iniciar una publicación lo gestiona:

  • El espacio de trabajo sigue la compilación que ya está en marcha en lugar de mostrar un error encima.
  • El agente recibe la indicación de que ya hay una publicación de esta app en curso y de que espere a que termine, en lugar de que algo falló; antes eso podía llevarle a «arreglar» un Dockerfile y reintentar.
  • Una colisión en la primera publicación por el nombre de enlace se comprueba: si la fila que tiene el nombre es la publicación en curso de este mismo proyecto, la respuesta es «ya se está publicando», no «ese nombre está ocupado». Una colisión real con otro proyecto sigue recibiendo el mensaje de renombrar.

Cómo lo comprobamos

Escribimos un script de reproducción que ejecuta tres publicaciones de una misma app al mismo tiempo contra una base de datos real y un orquestador simulado, y cuenta cuántos despliegues se le piden al orquestador. Cubre una primera publicación y una republicación de una app en vivo. Antes del arreglo, contaba dos despliegues. Después, uno.

El script también cubre la vía de escape: marca la fila de la app como compilando sin latidos durante diez minutos, vuelve a publicar, y comprueba que la nueva publicación reclama la fila y se ejecuta.

Las pruebas unitarias mantienen el bloqueo a las dos cosas que debe hacer: de tres republicaciones simultáneas, solo una pasa y a las otras dos se les dice que hay una publicación en curso; y una compilación cuyo dueño murió puede ser reclamada. Otra prueba comprueba que un nuevo intento no arrastra el mensaje de error del intento anterior.

Qué aprendimos

  • El código de limpieza puede ser una carrera disfrazada. «Fallar todo lo que esté en curso, y luego empezar» parece higiene. Con dos llamadores, cada uno cancela al otro.
  • Deja que la base de datos arbitre. Una lectura seguida de una escritura deja un hueco para otra petición. Una sola actualización condicional no.
  • Todo bloqueo necesita una caducidad ligada a la actividad. Sin ella, un fallo se convierte en una app bloqueada.
  • Los mensajes de error son instrucciones. «Ese nombre ya está ocupado» llevaba a la gente a hacer lo incorrecto. El mensaje correcto era más simple y verdadero.

Más sobre publicación en la documentación: cómo publicar. Y un arreglo relacionado: tu app publicada sigue activa mientras la vuelves a publicar.

Mythex · Pricing · Crea lo que quieras con IA · Plantillas de sitios web y apps · Mythex comparado · Glosario para crear apps con IA · Herramientas gratis para crear apps con IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά 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 简体中文 繁體中文(香港) 繁體中文(台灣)