Los trabajadores son apátridas por diseño. Cada solicitud llega en un aislamiento limpio, sin memoria de lo que sucedió antes y sin estado compartido con otras instancias que se ejecutan en paralelo. Este aislamiento es exactamente lo que le permite escalar millones de solicitudes sin coordinación entre instancias. Los Objetos Durables rompen este contrato intencionalmente, y comprender por qué y qué cambia exactamente determina si los usará correctamente o sufrirá por ellos.
Qué es realmente un objeto duradero
Un Durable Object es una clase de JavaScript con almacenamiento persistente y un detalle de ejecución que lo cambia todo: las solicitudes llegan en serie. No hay competencia dentro de una instancia. Mientras el método fetch() de un DO procesa una solicitud, todos los demás que llegan al mismo DO se ponen en cola afuera.
Esto elimina toda una categoría de errores que surgen del acceso simultáneo al estado compartido. No existe ninguna condición de carrera dentro de una DO. Si incrementa un contador, conserva el valor y responde, no se pueden intercalar otras solicitudes entre estas operaciones. La secuencia está garantizada por la plataforma.
Cada instancia de DO existe en un único PoP de Cloudflare: el punto de presencia más cercano a la primera solicitud que la creó. Las solicitudes posteriores al mismo DO se enrutan a ese PoP específico, independientemente de dónde se encuentre el cliente. Si su instancia se creó en Frankfurt y un cliente de São Paulo le realiza una solicitud, la solicitud viaja a Frankfurt. Esto tiene implicaciones reales de latencia para casos de uso distribuidos geográficamente y es importante saberlo antes de diseñar.
Por qué KV y D1 no resuelven el mismo problema
La comparación natural es con KV y D1, las otras opciones de persistencia de la plataforma. La diferencia no es de conveniencia, sino de coherencia del modelo.
KV finalmente es consistente. La escritura en un PoP se propaga a otros con un retraso mensurable, normalmente entre unos pocos milisegundos y un minuto. Las lecturas en diferentes PoP pueden mostrar diferentes versiones del mismo valor. Para lectura de caché, para configuraciones que rara vez cambian, KV funciona perfectamente. Para cualquier operación que requiera "leer, calcular, escribir" con la garantía de que no se ha realizado ninguna otra escritura en el medio, no funciona.
D1 con transacciones resuelve la atomicidad para lecturas y escrituras, pero introduce latencia entre regiones para escrituras. Toda la escritura se dirige a la región principal del banco, que puede estar en un PoP diferente al de su trabajador. Para muchas aplicaciones esto es aceptable. Para estados que deben modificarse con baja latencia y alta frecuencia, o para coordinación en tiempo real entre clientes, el costo de latencia de cada escritura en una región primaria cambia el problema.
Un DO mantiene el estado en la memoria y persiste atómicamente a través de la API de almacenamiento. La operación await this.ctx.storage.put('count', this.count) es linealizable: cualquier solicitud posterior al mismo DO verá el valor que se escribió, sin excepción. Esta garantía, combinada con la ejecución en serie, es lo que hace posible construir contadores atómicos, bloqueos distribuidos, registros de eventos ordenados y coordinación entre clientes concurrentes sin la complejidad de implementar el control de concurrencia en la aplicación.
Cómo afecta la ejecución en serie al rendimiento
La ejecución en serie es la garantía y el cuello de botella al mismo tiempo. Un DO que procesa cada solicitud en 5 ms puede manejar aproximadamente 200 solicitudes por segundo. Si su aplicación enruta 500 solicitudes/s al mismo DO, 300 de ellas se ponen en cola y agregan latencia.
Este techo existe por diseño. Si un único DO se convierte en un cuello de botella, la solución es fragmentar: en lugar de una ID fija para un recurso, distribuirla por sufijo numérico. Para un recurso identificado por userId, la estrategia user-{userId}-shard-{userId.charCodeAt(0) % 10} distribuye la carga entre diez instancias, cada una con su propio límite de rendimiento. La elección de qué fragmento utilizar debe ser determinista para que las lecturas y escrituras del mismo cliente siempre lleguen a la misma instancia.
La hibernación cambia el modelo de costos de mantener activas muchas instancias. Cuando un DO no tiene solicitudes pendientes, hiberna automáticamente, sin costos de computación mientras está hibernado. Despertar de la hibernación tarda menos de milisegundos. Para aplicaciones con muchos DO dispersos (uno por usuario, uno por sala, uno por sesión), el costo de procesamiento real es proporcional al uso activo, no al número total de instancias.
Lo que el nivel de precios exige de usted
Los objetos duraderos requieren el plan Workers Paid, con un mínimo de $5/mes. A partir de ahí, el costo tiene tres componentes: solicitudes ($0,15/millón, con 1 millón gratis/mes), computación en GB-segundos ($12,50/millón GB-segundos, con 400 mil gratis/mes) y almacenamiento ($0,20/GB-mes, con 1GB gratis).
El segundo GB es la parte confusa. Un DO que se ejecuta con 128 MB de memoria durante 1 segundo consume 0,125 GB-segundo. Con 400.000 GB-segundos gratuitos por mes, esto equivale a 3,2 millones de segundos de ejecución en el espacio mínimo. Una solicitud promedio de 10 ms a 128 MB consume 0,00125 GB-segundos, por lo que el nivel gratuito cubre aproximadamente 320 millones de solicitudes de memoria mínima de 10 ms. Si su DO realiza cálculos pesados, mantiene un gran estado en la memoria o procesa muchas solicitudes por segundo, el consumo de GB por segundo aumenta proporcionalmente.
Otro costo: API de alarma. DO puede programar un método alarm() para que se ejecute en una fecha futura, incluso si hiberna en el medio. El coste es de 0,15 dólares por millón de invocaciones de alarma, la misma tabla que las solicitudes normales.
Cuándo elegir objetos duraderos
La pregunta que determina si DO es la herramienta adecuada es sencilla: ¿el estado que necesita administrar requiere acceso serializado de clientes de la competencia? Si es así, HAZLO. Si no, existe una opción más sencilla y económica.
Casos en los que DO resuelve con la misma garantía algo que nada en la plataforma resuelve: contadores atómicos con alta frecuencia de escritura, coordinación de presencia en tiempo real (quién está conectado a una sala), colas de eventos ordenadas por llegada, bloqueos distribuidos con tiempo de espera vía Alarm API y sesiones de edición colaborativa donde varios clientes modifican un mismo documento.
Lo que no es un caso de uso DO: almacenamiento de datos relacionales con consultas ad hoc (D1 es mucho más adecuado), almacenamiento en caché de lectura con escrituras ocasionales (KV es más barato y está distribuido globalmente), almacenamiento de archivos (R2) y cualquier estado que se asigne naturalmente a una única escritura sin lecturas simultáneas que dependan del estado anterior.
Qué monitorear en producción
Dos indicadores que aparecen temprano en los problemas con los DO: aumento de la latencia de la cola (una señal de que un DO se ha convertido en un cuello de botella y necesita fragmentación) y el consumo de GB-segundos crece más allá de las expectativas (una señal de que los DO mantienen el estado en la memoria más tiempo del necesario, o se mantienen activos con trabajo innecesario).
La API de almacenamiento tiene un detalle operativo que te pilla por sorpresa: list() devuelve un máximo de 128 entradas por llamada de forma predeterminada. Para conjuntos de datos más grandes, la iteración debe utilizar el cursor. Ignorar esto en desarrollo y descubrirlo en producción con mil claves tiene un coste real en solicitudes y tiempo de respuesta.
La decisión de utilizar DO o no tiene menos que ver con el rendimiento y más con la garantía de coherencia que requiere su aplicación. Comprender este requisito antes de elegir la herramienta ahorra una costosa migración posterior.
Lea también
- El modelo de programación de Durable Objects: lo que no se parece a nada que hayas usado
- Objetos duraderos en producción: cómo será el billete y los límites que sorprenden
- Objetos Durables y WebSockets: multijugador sin servidor dedicado
- Cuando los objetos duraderos son la respuesta incorrecta
- Cloudflare KV: ¿Qué significa distribuido globalmente cuando necesitas escribir?
- Cloudflare Workers vs Pages: la diferencia que importa antes de elegir
