Mainbrella explica cómo diseñar idempotencia en un backend de sandboxes

El problema clásico que vuelve una y otra vez en sistemas distribuidos

Pides crear una máquina Linux. El servidor la arranca. La respuesta se pierde en el camino. Para el usuario final, parece que la orden nunca llegó. Reintentar parece razonable, pero lanzar una segunda máquina convierte la recuperación en algo más caro que el propio fallo. Es la clase de problema que se repite en cualquier sistema distribuido que gestiona recursos limitados: un comando puede ejecutarse sin que quien lo llamó vea el resultado; una petición privada puede sobrevivir a un cambio de membresía; una captura de disco puede completarse sin dejar el identificador necesario para restaurarla.

Detrás de cada uno de estos casos hay una decisión: qué se permite hacer en un reintento. La respuesta no es trivial, y un backend que la toma mal termina facturando dos veces el mismo trabajo o dejando trabajo zombie que nadie sabe cómo parar.

En esta nota repasamos cómo el equipo de Mainbrella —un servicio de workspaces Linux temporales construido sobre Cloudflare Containers— diseñó su backend para que cada uno de esos puntos tenga una respuesta explícita. Lo hacen público en su commit 6e5bef3 del 7 de octubre de 2026 y lo acompañan con tests de ciclo de vida que cualquier puede ejecutar.

¿Y esto cómo se aplica en tu negocio?

En CAR, dueños de empresa de Latinoamérica y España usan la tecnología para ser más rentables. Empiezas con una ruta de 7 días y terminas con un sistema funcionando en tu negocio.

👥 Probar 7 días

Cómo decides quién obtiene el último slot disponible

El ejemplo típico: una cuenta tiene un slot libre y llegan dos solicitudes create casi simultáneas. Si lees un contador, arrancas una máquina y luego incrementas el contador, las dos peticiones pasan. Cuando te das cuenta, ya gastaste el recurso caro.

Mainbrella resuelve el problema con un patrón concreto: cada cuenta tiene un Cloudflare Durable Object propio —un coordinador persistente con almacenamiento dedicado—. La API pública resuelve el usuario autenticado y su entitlement de pago, y luego se dirige a ese coordinador con el identificador account:<userId>. Cada slot de máquina tiene su propio Durable Object. Para el slot c17, el nombre es user:<userId>:slot:17.

El coordinador de cuenta serializa la admisión: una petición debe reservar el slot, el inicio mensual y el cupo de cómputo antes de pedir al runtime que arranque. Cuando la siguiente petición llega, ve capacidad ocupada incluso aunque la primera máquina todavía esté arrancando. La implementación usa una cola explícita con una cadena de promesas: un await dentro de una decisión de admisión no deja pasar la siguiente decisión por delante.

¿El truco? Sostener el cerrojo hasta que toda máquina esté lista serializaría todos los arranques. Lo sueltan después de la reserva durable y aprovisionan fuera. Una sola cuenta decide en secuencia, pero sus runtimes arrancan en paralelo.

Test estrella: seis peticiones concurrentes sobre un Builder con cinco slots, con la verificación de readiness de cada runtime detrás de un gate. Cinco máquinas llegan a boot antes de que el gate se abra; la sexta recibe un conflicto; la cuenta registra cinco inicios. Verifica admisión exclusiva y aprovisionamiento concurrente al mismo tiempo.

Una «factura» existe antes de que exista la máquina

Para el trabajo del ejemplo (analysis.py), el cliente envía un Idempotency-Key con el POST /containers. Esa clave representa «este intento particular de crear una máquina». La cuenta registra qué slot y reserva pertenecen a esa clave, junto con una huella de la configuración pedida. Cambiar la imagen, el tamaño u otra parte huella mientras reutilizas la misma clave produce un conflicto.

Lo importante es que la escritura es pequeña y atómica. En container-account-core.js:

const reservationId = ++state.nextReservationId;
state.reservations[slot] = reservationId;
const creation = {
  id: crypto.randomUUID(), slot, reservationId, fingerprint,
  expiresAt: this.now() + CREATION_RETENTION_MS,
};
await this.ctx.storage.put({
  [KEY]: state,
  [CREATION_PREFIX + idempotencyKey]: creation,
});

Ese commit multi-clave registra la reserva cargada y el recibo de creación de forma atómica. Después —solo después— el controlador dispatcha el boot.

Si se pierde la respuesta original, un reintento con la misma clave encuentra el recibo antes de intentar admisión nueva. Mientras la reserva está pendiente, devuelve starting. Hay una ventana de reconciliación de 90 segundos; pasado ese tiempo, el coordinador pregunta al runtime qué existe realmente. Si encuentra la máquina corriendo, devuelve esa máquina sin cobrar otro inicio. El recibo dura 24 horas. Si la máquina ya paró o el slot fue reutilizado, devuelve creation_no_longer_running. No lanza un reemplazo en silencio. Si quieres un reemplazo, haces un nuevo intento con una clave nueva.

Mensajes de ayer que llegan a máquinas de mañana

Supón que el dispatch del primer boot se retrasa. Mientras, la cuenta cancela la reserva, libera c17 y asigna el slot a un reemplazo. El dispatch viejo llega por fin. Su target sigue siendo el mismo objeto runtime, pero el slot por nombre no te dice si ese mensaje tiene derecho a arrancar algo.

Cada admisión recibe un número de reserva creciente. En el runtime se persisten dos marcas de nivel máximo: el último inicio aceptado y la última cancelación. Un boot igual o por debajo de cualquiera de las dos marcas es rechazado. La cancelación escribe su marca incluso cuando no hay guest que destruir, así un mensaje que llega tarde no puede resucitar trabajo cancelado.

Pasa al revés también: una limpieza tardía de la reserva 41 no debe frenar la máquina de la reserva 42. El DELETE del runtime avanza la marca de cancelación pero deja al guest intacto cuando la cancelación es más vieja que el inicio aceptado. Los comandos públicos y las peticiones de archivo identifican una generación corriendo con { id, createdAt }. El slot es reutilizable; la generación no. Y pese a su forma de timestamp, createdAt se calcula como max(now, previousCreatedAt + 1). Recrear una máquina en el mismo milisegundo — o mover el reloj hacia atrás — sigue dando al guest nuevo una identidad distinta.

El deadline estricto forma parte de la admisión

Una máquina también necesita permiso para seguir gastando cómputo. Si el contador mensual se chequea solo al lanzamiento, muchas máquinas simultáneas consumen la misma cuota restante. Por eso se reserva antes de que arranquen. Los tamaños tienen pesos (Lite=1, Medium=10, XL=28 unidades de cómputo) y la cuenta reserva unit-milisegundos. La concesión termina en el más temprano de cuatro límites: límite de sesión del plan, expiración del pago, próximo mes UTC, o tiempo restante dividido por el peso de la máquina.

El ejemplo aritmético: reservar una Medium por una hora compromete 10 horas de unidad de cómputo. Si paras a los 5 minutos, el consumo es 10 × 5 / 60, unas 0,833 horas de unidad. La reserva no usada se libera. Un stop que falla o un runtime ilegible mantiene su reserva: tratar «no pude contactarlo» como «debe estar libre» permitiría gastar dos veces el cupo.

El runtime persiste un deadline estricto y un deadline de inactividad. La actividad real puede mover el deadline de inactividad, capeado por el deadline estricto. El polling de estado no lo mueve. Una alarma del Durable Object aplica la expiración aun cuando el cliente se fue. Cambios de plan pueden acortar una vida existente, pero no pueden extender el deadline estricto original.

¿Qué significa esto para tu startup?

Si tu producto tiene cualquier componente con cuota, admisión o ejecución remota —un endpoint que arranca agentes, un servicio que ejecuta código de usuario, un pipeline que dispara jobs— los patrones que Mainbrella publica son directamente reutilizables. La diferencia entre «cobrar dos veces» y «recuperar la petición perdida» se decide en cómo persistes el recibo, no en cómo devuelves el JSON.

Acciones concretas que puedes implementar esta semana:

  • Añade Idempotency-Key a tus POST de creación de recursos y persiste la reserva + el recibo en una sola escritura atómica. Si hoy dependes de un contador leído y luego incrementado, replica el patrón de Durable Object (o su equivalente en tu stack: un lock distribuido con storage transaccional) antes de delegar al backend caro.
  • Implementa marcas de generación y cancelación en cualquier servicio que admita mensajes asíncronos. Cada admisión recibe un número creciente; cada cancelación escribe su marca incluso sin guest que matar. Sin esto, un reintento tardío puede revivir trabajo cancelado.
  • Separa el deadline estricto del deadline de inactividad. El primero es inmutable (los cambios de plan no lo extienden); el segundo se mueve con actividad real pero no con polling. Esto evita tanto zombies como cortes prematuros.
  • Mide el costo de la ambigüedad, no solo el costo del cómputo. Un snapshot sin handle recuperable, una admisión que cobró pero no arrancó, una cancelación que llegó tarde: el costo no es el recurso, es la pérdida de la capacidad de decidir qué pasó.

El contexto sectorial: el terreno donde se mueve Mainbrella

Mainbrella es un actor pequeño comparado con los grandes del segmento, pero su aproximación es interesante porque se apoya explícitamente en Cloudflare Containers y en el patrón Durable Object. Cloudflare anunció el 30 de septiembre de 2026 una actualización importante de Containers con la política de scheduling durable_object, snapshots de filesystem en beta pública, y tiempos de arranque hasta 6 veces más rápidos: según su medición, la mediana de arranque bajó de 4.049 ms a 648 ms, y el percentil 95 pasó de 5.839 ms a 910 ms, según el benchmark independiente de ComputeSDK. Cloudflare también presentó cloudflare/debian-trixie, una imagen base lista para agentes con Debian Trixie Slim y Node.js 24.20.0 LTS, distribuida y preparada antes de las peticiones para evitar tiempos de descarga en caliente.

En el comparativo que Mainbrella publica, aparecen como competidores directos E2B, Daytona, Modal Sandboxes, Blaxel, Runloop Devboxes, Vercel Sandbox, Cloudflare Sandboxes, DigitalOcean Harness Runtime, Fly Sprites, Northflank Sandboxes, Koyeb Sandboxes, CodeSandbox SDK y Morph Devboxes. Cada uno cubre un subconjunto distinto de capacidades (pausa/resume, volúmenes persistentes, BYOC, self-hosting,SDKs), y el plan Builder de Mainbrella arranca en US$5 mensuales con topes fijos por mes sin cobros por overruns automáticos.

La nota de Mainbrella no es marketing: es ingeniería en formato abierto. Los mecanismos de admisión, idempotencia, fencing por generación y reconciliación de snapshots son la materia prima que cualquier backend serio necesita, estén o no construidos sobre Durable Objects. Vale la pena leerla con el código abierto al lado.

Fuentes

¿te gustó o sirvió lo que leíste?, Por favor, comparte.

¿Y esto cómo se aplica en tu negocio?

En CAR, dueños de empresa de Latinoamérica y España usan la tecnología para ser más rentables. Empiezas con una ruta de 7 días y terminas con un sistema funcionando en tu negocio.

👥 Probar 7 días

Daily Shot: Tu ventaja táctica

Lo que pasó en las últimas 24 horas, resumido para que tú no tengas que filtrarlo.

Suscríbete para recibir cada mañana la curaduría definitiva del ecosistema startup e inversionista. Sin ruido ni rodeos, solo la información estratégica que necesitas para avanzar:

  • Venture Capital & Inversiones: Rondas, fondos y movimientos de capital.
  • IA & Tecnología: Tendencias, Web3 y herramientas de automatización.
  • Modelos de Negocio: Actualidad en SaaS, Fintech y Cripto.
  • Propósito: Erradicar el estancamiento informativo dándote claridad desde tu primer café.

📡 El Daily Shot Startupero

Noticias del ecosistema startup en 2 minutos. Gratis, todos los días.

Share to...