Almacenamiento SQLite de latencia cero en todos los Durable Objects
(simonwillison.net)- Los Durable Objects de Cloudflare pasan de un almacén clave/valor a un almacenamiento relacional basado en SQLite, con una arquitectura donde la lógica de la aplicación y los datos se ejecutan en el mismo host físico
- La escalabilidad se aborda dividiendo los Durable Objects por unidades lógicas de estado como documentos, usuarios o shards de base de datos, en lugar de hacer crecer un solo objeto, ya que un objeto individual tiene el límite de una sola máquina y un solo hilo
- En sistemas con unidades de estado claramente definidas, como la reserva de vuelos, cada vuelo puede tener un Durable Object dedicado y su propia base de datos SQLite, lo que puede generar miles de bases de datos nuevas por día para cada aerolínea
- La durabilidad se logra con streaming de entradas WAL, almacenamiento en object storage y replicación a centros de datos cercanos; se agrupa cada 16 MB o cada 10 segundos y permite recuperación puntual de hasta 30 días
- La API de JavaScript adopta un enfoque bloqueante para operaciones persistentes rápidas en un solo hilo, y la ubicación del Durable Object queda fija tras su creación, aunque se planea una reubicación dinámica en el futuro
Diseño de Durable Object renovado con SQLite
- El Durable Object de Cloudflare fue actualizado de un almacén clave/valor a un sistema relacional basado en SQLite
- La idea central es colocar la lógica de la aplicación en el mismo lugar que los datos que esa lógica maneja
- Un Durable Object está compuesto por código que se ejecuta en el mismo host físico que la base de datos SQLite que utiliza
- La meta es procesar lecturas y escrituras sin ida y vuelta por la red, lo que facilita reducir la latencia
- El procesamiento a gran escala se plantea creando más objetos, en vez de aumentar el rendimiento de uno solo
- Un objeto individual se ejecuta en un único hilo de una sola máquina, por lo que tiene un límite inherente de rendimiento
- Es más fácil escalar si distintos componentes lógicos de estado, como documentos, usuarios o shards de base de datos, quedan a cargo de objetos separados
- En un sistema de reserva de vuelos, cada vuelo puede mapearse a un Durable Object dedicado y a su propia base de datos SQLite
- En esta estructura, pueden crearse miles de bases de datos nuevas por día para cada aerolínea
- Cada Durable Object tiene un nombre único, y la red de Cloudflare enruta las solicitudes a la ubicación de ese objeto dentro de su red global
Durabilidad garantizada con streaming de WAL y replicación
- Un sistema basado en Durable Objects, inspirado en Litestream, hace streaming continuo de la secuencia de entradas WAL de cada objeto hacia object storage
- El agrupamiento se realiza cada 16 MB o cada 10 segundos
- Al reproducir las transacciones registradas, es posible la recuperación puntual de hasta 30 días
- Para reforzar la durabilidad dentro de esa ventana de 10 segundos, las escrituras se envían justo después del commit a 5 réplicas en centros de datos cercanos pero separados
- La respuesta de escritura solo se aprueba cuando 3 de esas 5 réplicas la confirman
- El sistema base de Durable Objects es Storage Relay Service, que ya ha operado durante más de un año el sistema independiente D1 SQLite system de Cloudflare
API y comportamiento de la ubicación de los objetos
- La API de JavaScript usa un enfoque bloqueante, no async
- El objetivo del diseño es ofrecer operaciones persistentes rápidas en un solo hilo
- El código de ejemplo usa intencionalmente un patrón de consultas N+1: primero obtiene la lista de documentos y luego recupera el nombre del autor de cada documento con una consulta separada
- Se presenta como un caso donde SQLite encaja bien para manejar ese patrón
- Actualmente, un Durable Object no cambia de ubicación una vez creado
- Por defecto, se instancia en el centro de datos más cercano al lugar donde se originó la primera solicitud
get() - Para crearlo manualmente en otra ubicación, se puede pasar el parámetro opcional
locationHintaget() - La reubicación dinámica de Durable Objects existentes está planificada para el futuro
- Por defecto, se instancia en el centro de datos más cercano al lugar donde se originó la primera solicitud
- where.durableobjects.live es un sitio para rastrear dónde se crean los nuevos Durable Objects dentro de la red de Cloudflare
- En la visita de ejemplo, al cargar la página desde Half Moon Bay se mostraba que el worker de San Jose había creado el Durable Object en San Jose
1 comentarios
Opiniones de Hacker News
Hay algunas cosas más interesantes: la API de escritura es síncrona, pero tiene una espera asíncrona oculta, de modo que si una escritura falla al emitir la siguiente respuesta, el runtime convierte la respuesta en una falla HTTP.
Por eso, sin que el usuario tenga que manejar explícitamente errores o esperas, el runtime puede agrupar automáticamente en lotes las escrituras y asumir de forma optimista que tendrán éxito.
No hay transacciones de lectura, aunque creo que habrían sido útiles para obtener un puntero a una instantánea de un momento específico.
Cada instancia del runtime está limitada a 128 MB de RAM.
Los WebSockets pueden entrar en hibernación y no se cobra por el tiempo en que están dormidos, así que se puede mantener a los clientes conectados incluso mientras el DO está dormido.
También hay algo parecido a RPC automático que permite tratar otros DO o Workers como si fueran llamadas JS normales, aunque en realidad puede estar llamando a otro centro de datos, y el runtime se encarga de la serialización y el parseo.
Litestream, mencionado en el artículo, también propone una técnica similar.
En SQLite normal, las transacciones de lectura son útiles porque varios procesos pueden acceder al mismo tiempo a la misma base de datos.
Aquí, como solo un proceso puede acceder a la base de datos, se puede lograr el mismo efecto haciendo todas las lecturas dentro de una sola función síncrona o implementando directamente un bloqueo a nivel de proceso.
Que las entradas del WAL se transmitan por lotes al almacenamiento de objetos cada 16 MB o cada 10 segundos parece significar que puede tomar hasta 10 segundos leer de forma confiable una escritura a nivel global.
No veo bien cómo esto puede reemplazar a clústeres de bases de datos regionales que entregan respuestas en milisegundos a escala continental.
Entiendo que usan un stream, pero el destino son solo 5 seguidores, y Cloudflare tiene cientos de centros de datos.
Si todas las instancias de SQLite no están conectadas todo el tiempo, físicamente no se pueden garantizar lecturas en cuestión de segundos; e incluso si lo están, la latencia de paquetes puede causar problemas.
Si escribes y luego lees de inmediato, se refleja al instante. Esto se debe a que la escritura también actualiza el estado en memoria del proceso actual.
Si otro proceso, por ejemplo otro DO o Worker, quiere acceder a los datos, tiene que pasar por el DO que posee los datos, por lo que enviará una solicitud RPC o HTTP y recibirá la información más reciente.
La hibernación ocurre después de cierto tiempo de inactividad, así que, contrario a lo que se esperaría, los casos en que una escritura quede unavailable parecen limitarse más o menos a que un DO o Worker crashee justo después de escribir.
El post del blog de Cloudflare cubre esta parte con más detalle. Así mantienen las escrituras rápidas y, a la vez, garantizan durabilidad.
Esa máquina siempre tiene una vista consistente de su propia base de datos SQLite.
Puedes crear miles de millones de objetos, pero cada objeto tiene una base de datos separada.
No hay forma de leer directamente esa base de datos desde una máquina distinta a la que está ejecutando el DO.
Cada DO es único a nivel global, y un DO con un ID específico se ejecuta en un solo lugar; allí corre SQLite sobre el almacenamiento local de ese centro de datos.
La parte que todavía no entiendo de Durable Objects es la ubicación física.
Me pregunto si queda ubicado en la región que alojó la llamada a la API que causó su creación inicial.
Si es así, también me pregunto si existe algún mecanismo para mover automáticamente un DO a otra ubicación cuando se detecta algo como que fue creado en Norteamérica, pero luego todo el tráfico de lectura/escritura viene de Australia.
locationHint. Para Australia se usa"oc": https://developers.cloudflare.com/durable-objects/reference/...También hay que tener en cuenta que “la reubicación dinámica de Durable Objects existentes está planificada para el futuro”.
Solo alrededor del 10~11% de los PoP de Cloudflare alojan Durable Objects.
Las solicitudes que llegan a otros PoP para crear un DO se reenvían a uno de los PoP cercanos que sí lo aloja.
Según recuerdo, Orleans (https://www.microsoft.com/en-us/research/wp-content/uploads/...) puede mover actores entre máquinas, y ese modelo parecería encajar bien con una forma de mover DO entre ubicaciones.
Sin embargo, no hay una llamada de API para mover un Durable Object.
No debe haber conexiones, y se vuelve a crear en el centro de datos más cercano a la siguiente conexión, o a la primera.
En ese momento la memoria desaparece, pero el almacenamiento permanece.
Hace poco aparecieron detalles nuevos relacionados con la hibernación, así que esta explicación podría estar un poco desactualizada.
Me pregunto si hay más personas a las que les resulta difícil entender estas nuevas tecnologías de nube.
Tengo más de 15 años de experiencia en desarrollo web y he usado el stack Laravel / Postgres / Redis, pero cuando leo artículos como este simplemente pienso que esto no es para mí.
Las apps colaborativas en tiempo real que se me vienen de inmediato a la mente son Google Docs/Sheets, Notion, Miro y Figma.
Todas son apps colaborativas a escala global, y no estoy seguro de que un stack Laravel pueda soportar esos casos de uso.
Google probablemente tuvo que implementar la mayor parte por su cuenta y seguramente fue pionero en el uso de CRDT.
Pero a medida que los patrones se vuelven claros y los componentes se convierten en SaaS, la colaboración en tiempo real a gran escala deja de ser un problema gigantesco de ingeniería, y eso hace posibles productos más interesantes.
Me gusta mucho el diseño de Durable Object. En especial, me gusta que sea fácil entender cómo funciona internamente.
A diferencia de muchas otras soluciones diseñadas para datos en tiempo real, Durable Objects tiene una simplicidad como la de Redis y la comida italiana: se ven todos los ingredientes.
Con suficiente tiempo, recursos y centros de datos, creo que un programador competente podría leer la documentación de DO y volver a implementar algo parecido.
Por eso es fácil evaluar los compromisos involucrados.
Sin embargo, me preocupa que DO sea bueno para crear experiencias en tiempo real rápidas y de bajo overhead, por ejemplo que cinco personas editen un documento en tiempo real, pero que pueda hacer muy difícil generar análisis y vistas generales.
Por ejemplo, preguntas como qué grupo editó qué documento la semana pasada, y si los datos se meten dentro de SQLite, eso podría volverse todavía más difícil.
Habría que consultar de algún modo una gran cantidad de pequeñas instancias de SQLite y luego combinar los resultados, así que me pregunto si DO tiene algo para eso.
Al final, esta es la razón por la que uno sigue volviendo a Postgres: porque se puede usar tanto para las funciones centrales de la app como para vistas generales, BI, etc.
Es un diseño realmente interesante, pero estos sistemas inteligentes siempre caen, para mí, en un valle inquietante
Hay exactamente dos casos en los que se necesitan: cuando hay que escalar de forma inteligente un sistema con una carga muy alta, o cuando estás haciendo un proyecto de juguete por diversión
Si es un proyecto de juguete, puedes usar lo que quieras
Pero si es para producción o para el trabajo, necesitas algo probado
Si no sabes que lo necesitas, entonces no lo necesitas, y puedes usar cosas como una aburrida base de datos Postgres y una VM
En cambio, si sabes que lo necesitas, estás en problemas. Todavía es nuevo y no lo suficientemente maduro, así que es muy probable que te encuentres con muchos casos límite raros, y no vas a querer depurarlos ni asumirlos
Al final, no sé para quién es este tipo de sistema
Es tan de nicho que es difícil que madure fácilmente con muchos actores serios usándolo, y es demasiado complejo y con demasiados compromisos para que lo use el 99.9% de las empresas
El único público claro parecen ser los desarrolladores que ven algo brillante y construyen una empresa, o peor aún, construyen la empresa de alguien más sobre eso, para luego arrepentirse pronto y migrar a algo más aburrido
Si quieres crear otro Figma o Google Docs, el modelo de programación de Durable Objects es muy conveniente
Este artículo lo trata con más detalle: https://digest.browsertech.com/archive/browsertech-digest-cl...
Este artículo más antiguo también es bastante relevante: http://ithare.com/scaling-stateful-objects/
Para quien leyó el artículo de Figma sobre multijugador y pensó “esto es más o menos lo que necesito”, Durable Objects parece encajar bien: https://www.figma.com/blog/rust-in-production-at-figma/
También hay otros enfoques. Hace tiempo probé usar CRDT sobre WebRTC y de verdad se sentía como tecnología del futuro
Pero es mucho más complejo que basarse en un WebSocket en alguna parte de la nube y una única instancia de clase
Colocar en el mismo lugar los datos y el comportamiento realmente reduce la complejidad de forma cuantificable
Como elimina las preocupaciones de latencia y ancho de banda, reduce tanto las preocupaciones operativas como las de desarrollo. También disminuye mucho el impacto del famoso problema N+1
Se puede argumentar que Postgres conectado por red es mejor por otros motivos, y de hecho puede ser cierto
Pero SQLite es de lo más aburrido y predecible que hay, y sus fortalezas conocidas son claras
Por eso también está ganando popularidad en servidores
Dicho eso, no me gusta mucho la idea de crear muchas bases de datos pequeñas, como propone Durable Objects
Me recuerda a las pesadillas de NoSQL, y puede romper invariantes importantes de las bases de datos relacionales
Creo que SQLite funciona mucho mejor cuando se usa como una base de datos monolítica, como en el producto D1 de Cloudflare
Cloudflare necesita buenos casos de éxito de clientes, y es muy probable que sus excelentes ingenieros te ayuden a entender cómo funciona esto, cómo puede servirte y cómo manejar bugs, a cambio de conseguir un caso de éxito
Una vez validado, pasará a ser una relación de servicio, pero al principio será más bien una alianza
Hay muchos servicios, como herramientas internas, que no necesitan ajuste de rendimiento ni observación interna profunda
En esos casos, los frameworks serverless encajan bien porque reducen mucho el tiempo dedicado al despliegue
Que sean rápidos es bueno, pero rara vez es un requisito central
Normalmente, el requisito central es poder construir rápido y tener poco mantenimiento
Cloudflare puede ofrecer una buena experiencia de desarrollador aquí, pero ese no es su principal argumento de venta, y hay muchos servicios competidores que también prometen acelerar este tipo de desarrollo
En cambio, cuando se necesita una alta capacidad de depuración y observación interna, creo que estos servicios no encajan bien
Importa qué métricas puedes obtener, si tienes información para entender por qué algunos Durable Objects son lentos, si puedes corregirlo, cómo es el logging y cuánto cuesta
Estos servicios pueden ser buenos para que una startup construya desde el primer día un sistema distribuido inteligente y posponga las preocupaciones de escalado
Pero si se trata de una empresa en etapa de scale-up, probablemente querrá migrar a algo que permita mirar más a fondo, y esa transición será difícil
Como dijeron otros, el caso de uso es multijugador, porque para que la app se sienta bien todos deben ver los cambios lo antes posible
En términos más amplios, la industria del almacenamiento lleva mucho tiempo intentando crear algo consistente, de baja latencia y adecuado para múltiples usuarios
Incluso físicamente suele haber un compromiso entre consistencia y latencia, lo que lo vuelve muy difícil
Por eso se están probando varios modelos, y muchos de esos experimentos están ocurriendo alrededor de SQLite. No todos; hay excepciones como Yugabyte o Cockroach
El diseño de DO no deja de impresionarme
Es fácil tener una reacción instintiva de que este enfoque tiene algo mal, pero en realidad creo que muchos productos reales están organizados implícitamente de esta manera
Por cada unidad atómica que requiere consistencia transaccional, se ejecutan muchas operaciones complejas a una escala muy baja
Viéndolo en retrospectiva, lo que construimos en Framer para un proyecto con soporte multijugador era una versión más aplicada de lo que ahora hace DO
Las ediciones se replicaban a 60 FPS y se aplicaban en el orden correcto en todos los clientes, y terminamos construyendo algo parecido a un WAL para la edición de objetos JSON
Así, aunque una instancia del proyecto crasheara, una copia de respaldo podía tomar el relevo como si nada hubiera pasado
Incluso si no había tiempo para commitear los parches JSON al enorme objeto de datos del proyecto, se procesaba cada N actualizaciones o cada M segundos, como se describe aquí
Puede ser una pregunta tonta, pero me da curiosidad cómo se manejan las migraciones de esquema en una configuración así.
Según entiendo, parece que apuntan a una base de datos por tenant, o incluso a una estructura más fragmentada que eso.
Me pregunto si hay alguna forma sensata de manejar las migraciones de esquema, o si la expectativa es que estas bases de datos sean de más corto plazo y haya que soportar varias versiones de DB/DO hasta que se eliminen.
En mi cabeza, sería interesante crear un servicio de marcadores con un DO por usuario.
Pero en el momento en que quieras agregar un campo nuevo a una tabla existente, te topas con un problema bastante complicado: aplicar ese cambio a cada DO individual.
Quizá ese ejemplo implique datos demasiado persistentes, y este diseño tal vez esté pensado para usos más temporales.
Si alguien ya lo hizo en la práctica, me gustaría saber cómo lo está manejando.
Tengo una versión para SQLite escrita en Python, pero no sé si podría ejecutarse en Durable Objects. Quizá sea posible con WASM y Pyodide.
O habría que portarla a JavaScript.
https://github.com/simonw/sqlite-migrate
Últimamente parece que Cloudflare está impulsando a los desarrolladores a usar DO en todas partes más que Workers.
Las conexiones WebSocket de Workers también hacen timeout después de unos 30 segundos, y la forma recomendada es usar DO.
La demo original de chat también usaba DO y WebSocket desde 2020: https://github.com/cloudflare/workers-chat-demo
¿Esto significa que SQLite para DO podría perder hasta 10 segundos de datos en una situación en la que falle un DO?
Aquí parece que Simon quiso decir within, no “beyond”.