2 puntos por GN⁺ 2024-10-15 | 1 comentarios | Compartir por WhatsApp
  • 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 locationHint a get()
    • La reubicación dinámica de Durable Objects existentes está planificada para el futuro
  • 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

 
GN⁺ 2024-10-15
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.

    • Lo de RPC es bastante interesante. Hay más detalles aquí: https://blog.cloudflare.com/javascript-native-rpc/
    • La espera asíncrona oculta de la API de escritura recuerda a commit_delay de PostgreSQL. El principio no es exactamente el mismo, pero se siente similar: https://www.postgresql.org/docs/current/runtime-config-wal.h...
      Litestream, mencionado en el artículo, también propone una técnica similar.
    • Me da curiosidad si aquí hay algún caso de uso concreto que requiera transacciones de lectura a nivel de base de datos.
      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.

    • Por lo que entiendo, las escrituras y lecturas solo ocurren en el mismo proceso, así que el almacenamiento de largo plazo solo entra en juego cuando el proceso actual está hibernado.
      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.
    • Las escrituras se transmiten casi en tiempo real a 5 seguidores y se confirman casi de inmediato.
      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.
    • Creo que hay un malentendido sobre cómo funcionan los Durable Objects. Todo el tráfico dirigido al mismo objeto se enruta a una única máquina donde existe ese objeto.
      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.
    • Las entradas del WAL que se transmiten al almacenamiento de objetos probablemente sean para backup.
      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.

    • Básicamente está en la región donde se creó, pero como alternativa se puede especificar 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”.
    • https://where.durableobjects.live es un buen sitio que muestra dónde está un DO.
      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.
    • Actualmente, los Durable Objects no cambian de ubicación después de crearse, y la reubicación dinámica de Durable Objects existentes está planificada para el futuro: https://developers.cloudflare.com/durable-objects/reference/...
      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.
    • Los Durable Objects tienen almacenamiento de largo plazo y se hidratan desde ese almacenamiento, así que en ese sentido podrían moverse a cualquier centro de datos de Cloudflare.
      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.
    • No responde a la pregunta, pero recomiendo https://where.durableobjects.live/.
  • 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í.

    • Según el artículo, como contexto útil para la primera versión de Durable Objects recomiendan ver Cloudflare’s durable multiplayer moat, de Paul Butler, donde él explica por qué son populares para crear aplicaciones colaborativas en tiempo real basadas en WebSocket.
      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

    • A mi parecer, la killer app de Durable Objects es multijugador
      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
    • Las bases de datos, como los lenguajes de programación, son un área que madura extremadamente lento, y no todo lo que se sale de Postgres es tecnología hipster brillante
      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
    • Si caes en el caso 1, basta con hablar con 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
    • La distinción aquí la veo en el eje de la capacidad de depuración y la observabilidad interna
      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
    • Este análisis es muy perspicaz. Creo que la mayoría de la gente debería hacer exactamente este tipo de análisis antes de diseñar un sistema
      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.

    • Las migraciones tendrás que implementarlas tú mismo.
      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.

    • Creo que en Cloudflare Workers, para conexiones WebSocket, Durable Objects siempre fue la forma recomendada. Al menos así lo recuerdo.
      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?

    • Hay un pasaje que dice: “Para garantizar la durabilidad más allá de la ventana de 10 segundos, las escrituras se envían, apenas se confirman, a 5 réplicas en otro centro de datos cercano, y 3 de ellas deben confirmar para que la escritura sea aprobada”.
      Aquí parece que Simon quiso decir within, no “beyond”.