1 puntos por GN⁺ 2024-08-11 | 1 comentarios | Compartir por WhatsApp
  • Screenshotbot usa el estado en RAM del proceso del servidor web como almacén de datos sin una BD separada, y recupera el estado tras fallas con snapshots y un registro de transacciones
  • Reduce la serialización SQL, los viajes de ida y vuelta a la BD y los servicios separados de trabajos en segundo plano para simplificar la experiencia de desarrollo en un solo proceso, aprovechando índices en memoria y procesamiento basado en hilos
  • La alta disponibilidad se logra con el algoritmo de consenso Raft, replicando el registro de transacciones en 3 servidores; si falla el líder, un nuevo líder retoma las solicitudes en pocos segundos
  • La implementación real combina Common Lisp, bknr.datastore, el proyecto open source bknr.cluster, Baidu Braft y EFS, y los reinicios del servidor suelen ocurrir solo una vez cada 1 o 2 meses
  • Incluso cuando el CI de clientes empresariales conocidos llama a la API cientos de veces por cada commit y Pull Request, el uso de CPU en una máquina de 4 núcleos y 16 GB se mantiene en un máximo de alrededor del 20%, y la mayor parte proviene del procesamiento de imágenes

Una arquitectura que trata el estado en RAM como si fuera una base de datos

  • En los servicios web tradicionales, era común elegir un framework web como Rails, Django o Node junto con una base de datos como MySQL, PostgreSQL o MongoDB
  • En los últimos 10 años han aparecido cambios de contexto que justifican volver a mirar este enfoque
    • Los discos se volvieron más rápidos con NVMe
    • Los discos como EBS/EFS se volvieron más robustos
    • La RAM se abarató lo suficiente como para que la mayoría de los datos de una startup quepan en memoria
    • Se pueden rentar máquinas con cientos de núcleos
    • En 2014 se publicó el algoritmo de consenso Raft y aumentaron las implementaciones robustas
  • Esta arquitectura combina el servicio web y la instancia de base de datos, usando el estado de memoria del proceso como si fuera una base de datos
  • En vez de serializar y guardar los datos como en SQLite, maneja directamente los objetos y campos en RAM como estado de la aplicación

Explore: cómo simplifica las iteraciones iniciales del producto

  • Si todos los datos están en RAM, no hace falta serializarlos con consultas SQL, y también se reduce el esquema de varios servidores frontend conectados a una sola BD
  • Cuando aumenta la carga, se puede responder usando servidores más grandes con más RAM y CPU
  • Los índices pueden usar estructuras como tablas hash en memoria en lugar de B-trees pensados para la latencia de disco
    • En Screenshotbot, los índices construidos con functional collections fueron importantes para la escalabilidad
  • Como la lectura de datos es una lectura de RAM, no hacen falta estructuras especiales ni Async-IO para reducir viajes de ida y vuelta a la BD
  • Los trabajos en segundo plano pasan a ser hilos que corren dentro del mismo proceso grande, y la concurrencia se puede resolver mayormente con mutex y condition variables en memoria

Recuperación ante fallas: snapshots y registro de transacciones

  • Para prepararse ante un crash del proceso, se guarda periódicamente un snapshot de todo el estado en RAM
  • Los cambios posteriores al último snapshot se escriben primero a disco como registro de transacciones y luego modifican el estado en memoria
    • foo.setBar(2) registra una transacción indicando que el campo bar de foo cambió a 2 y después modifica el campo real
    • new Foo() registra una transacción indicando que se creó un objeto Foo y luego devuelve el nuevo objeto
  • Al reiniciar, primero se lee el snapshot y luego se reproduce el registro de transacciones para recuperar el estado
  • Los cambios en índices no tienen que incluirse en el registro de transacciones
    • Por ejemplo, si el campo bar de Foo tiene un índice, setBar actualiza el índice, y esa actualización vuelve a ocurrir al cargar el snapshot o al reproducir las transacciones

La forma del código que hace posible un solo proceso

  • Como todas las solicitudes se procesan en el mismo proceso, se pueden guardar closures en la memoria del servidor y usarlas para manejar solicitudes de páginas
  • Las URL del tipo https://screenshotbot.io/n/nnnnnnn en Screenshotbot se mapean a closures internas
  • Como las closures mantienen referencias a objetos, se reduce la necesidad de pasar IDs de objetos o serializar objetos en cada navegación de página
  • También se reduce a un solo servicio lo que hay que depurar, perfilar y monitorear
    • Desaparecen puntos de observación separados de la BD, como el slow query log de MySQL
    • Si ese único servicio cae, el sitio también cae, pero si solo hay un servicio y un servidor, consideran que también baja la probabilidad de falla
    • Si el servidor muere, AWS puede levantar uno nuevo en pocos minutos
  • El código de pruebas también se vuelve más simple porque no hace falta hacer mock de la base de datos

Expand: alta disponibilidad con Raft

  • Si clientes de alto riesgo exigen 99.999% de disponibilidad, una arquitectura de un solo servidor no alcanza
    • Si falla el servidor, AWS puede tardar varios minutos en volver a levantarlo
    • Restaurar snapshots desde disco al proceso también puede tardar varios minutos
    • Durante un redespliegue, el reinicio del servicio puede dejar el servidor fuera por varios minutos
  • El algoritmo de consenso Raft replica el registro de transacciones del servidor web/base de datos, modelado como una máquina de estados finitos
  • Si se replica en 3 máquinas, cuando falla el líder se elige uno nuevo en pocos segundos y las solicitudes siguen procesándose
  • Sin cambiar demasiado la forma en que los desarrolladores escriben código, se puede convertir un servicio simple en una base de datos de alta disponibilidad
  • En una configuración basada en Raft también se puede hacer despliegue rolling sin bajar los servidores

Extract: sharding y cuellos de botella previstos

  • Cuando llega el momento de atender más clientes grandes, se puede aplicar sharding, igual que hacen las grandes empresas con sus bases de datos
  • Cada shard puede convertirse en su propio clúster
  • Screenshotbot ya ofrece un clúster dedicado por cliente enterprise
  • Meta cambió a Raft para manejar la replicación de clústeres MySQL, y Screenshotbot usa una idea similar sin una base de datos separada
  • El principal candidato a cuello de botella es la escalabilidad del commit-thread
    • Los hilos de lectura se paralelizan bien
    • Un solo commit-thread aplica las transacciones una por una
    • Como Raft confirma varias transacciones juntas en disco, la latencia de disco no es importante
    • La preocupación es cuando el costo en CPU de aplicar transacciones supera el rendimiento de un solo núcleo
    • En ese caso, se puede perfilar el costo del commit para mover parte del trabajo fuera del hilo de transacciones o considerar sharding

La pila real de Screenshotbot

  • Screenshotbot usa Common Lisp
  • La implementación inicial usaba MySQL, pero como era difícil manejar la concurrencia con MySQL, lo reemplazaron por bknr.datastore
  • bknr.datastore es una librería que ofrece para Common Lisp la estructura descrita en la etapa Explore
  • En esta estructura, los hilos dentro de un solo proceso manejan las solicitudes web, por lo que el multithreading sólido es importante
    • Por ese requisito, consideran que Ruby y Python no son adecuados
  • Como mantienen closures en la memoria del servidor, no es fácil reiniciar el servidor con frecuencia
    • Al reiniciar, se pierden las closures
    • En su lugar, actualizan el código con hot reloading sobre el proceso en ejecución
    • Common Lisp ofrece funciones como reinitialize-instance, un estándar para actualizar objetos existentes cuando cambia la definición de una clase

Clúster, almacenamiento de archivos y escala operativa

  • Actualmente, los reinicios del servidor suelen ocurrir solo una vez cada 1 o 2 meses
  • Cuando hace falta reiniciar, realizan un reinicio rolling dentro del clúster Raft
  • Usan un clúster de 3 servidores por instalación, y esta configuración tolera la caída de 1 servidor
  • Por ahora no usan Kubernetes y consideran que todavía no lo necesitan
  • La implementación de Raft es una librería propia construida sobre bknr.datastore
    • Publicaron bknr.cluster como open source
    • Internamente usa Braft de Baidu
    • Braft maneja snapshots en segundo plano, así que el servidor puede seguir procesando solicitudes mientras se crea un snapshot
  • Los archivos de imagen o blobs que no deben guardarse en el datastore se almacenan en EFS compartido por los tres servidores
    • EFS es un NFS de alta disponibilidad
    • Consideran que es más fácil de usar que S3 porque no obliga a manejar condiciones de error por separado
    • También mejora la capacidad de prueba porque escribe a disco sin interactuar con servidores externos

Rendimiento actual y alcance de aplicación

  • Screenshotbot da soporte a algunos clientes enterprise grandes y a un cliente particularmente conocido
  • Se ejecuta en el CI de ese cliente y recibe cientos de solicitudes API por cada commit y Pull Request
  • Incluso con esa carga, una máquina de 4 núcleos y 16 GB alcanza para procesar las solicitudes
    • Los servidores de réplica usan máquinas similares y la mayor parte del tiempo están inactivos
    • El uso de CPU llega como máximo a alrededor del 20%
    • La mayor parte del uso de CPU proviene del procesamiento de imágenes
  • Es importante no diseñar suponiendo una escala mayor que la realmente necesaria
  • Si se elige Common Lisp, esta configuración relacionada puede usarse en Screenshotbot OSS

1 comentarios

 
GN⁺ 2024-08-11
Comentarios en Hacker News
  • Esta arquitectura es más o menos similar a cómo se construyeron Nomad, Consul y Vault de HashiCorp (soy uno de los mantenedores de Nomad). Sin duda es una arquitectura inusual, pero una vez que te acostumbras, la experiencia para desarrolladores es bastante buena
    El estado en memoria se puede mantener en la forma que quieras, así que puedes crear tú mismo funciones de indexación y consulta ajustadas a la aplicación. También podrías usar SQLite :memory: en la FSM de Raft, pero si puedes crear o encontrar un almacén transaccional en memoria (nosotros usamos nuestro propio go-memdb), entonces leer el estado se vuelve simplemente una llamada de función. También es sencillo evitar lecturas obsoletas o sesgo de escritura. Como cada objeto que escribes tiene un índice de Raft, puedes crear una API como “consulta el objeto foo al follower, pero espera al menos hasta el índice 123”. Esto elimina gran parte de la “magia” que normalmente se empuja a un RDBMS o a un almacenamiento externo
    Aun así, sería cuidadoso al elegir esta arquitectura para una startup nueva fuera del ámbito de “infraestructura”. Porque en la práctica estás construyendo tu propia base de datos. Hay que elegir muy bien o escribir por cuenta propia componentes básicos como RPC entre nodos, persistencia en disco y un almacén transaccional de estado en memoria. Las actualizaciones son especialmente difíciles, y el código nuevo podría intentar escribir en el log de Raft entidades que los nodos de la versión anterior no entienden. Peor aún, podría haber cambiado la forma de procesarlas y los nodos anteriores podrían interpretarlas mal. No hay almuerzo gratis

    • La parte de “también podrías usar SQLite :memory: en la FSM de Raft” fue de hecho el diseño base que usó rqlite durante sus primeros ~7 años. Pero rqlite se pasó a SQLite en disco, y con modo WAL y PRAGMA synchronous=OFF[2], fue igual de rápido o lo bastante cercano a escribir en RAM. Eso también permitió evitar las limitaciones de una base de datos SQLite :memory:, una de las cuales es el límite máximo de 2 GB. Ahora veo que debimos haber usado el modo en disco desde el principio
      Como rqlite usa la misma librería de Raft[3] que Nomad, puede que parte de esto ya te resulte familiar
      El problema de las actualizaciones sí existe de verdad. Me pregunto si en Nomad se topan con eso con frecuencia en producción. En los 10 años de desarrollo de rqlite, introducir un nuevo tipo de Raft Entry fue algo muy poco común, y solo hubo una ocasión en que un usuario real se encontró con ello. Una forma de manejarlo es desplegar primero una versión que entienda el tipo nuevo pero que nunca lo escriba, y una vez que esa versión esté completamente instalada, subir a otra que ya sí escriba el tipo nuevo. Pero nunca lo hemos hecho en la práctica, y también exige disciplina por parte del usuario final
      [1] https://www.rqlite.io
      [2] Puede sonar peligroso, pero en el diseño actual de rqlite la base de datos SQLite principal se reconstruye por completo desde el log de Raft al arrancar (el log de Raft hace fsync en cada escritura). Así que aunque la base de datos SQLite se corrompa por un corte de energía o algo similar, no importa demasiado porque la base de datos SQLite no es el almacenamiento autoritativo de datos en rqlite
      [3] https://github.com/hashicorp/raft
    • A diferencia de otras respuestas aquí, yo también estoy más abierto a la idea de mantener los datos en memoria. Cuando vi en el artículo que usan Common Lisp y hot reloading, pensé: “si es ese equipo, pueden hacer lo que quieran. Aunque no todos trabajan en ese equipo”
    • “Las actualizaciones son especialmente difíciles” es totalmente cierto. Pero eso no aplica a startups en la etapa Explore que no necesitan replicación, y nosotros también trabajamos así durante mucho tiempo. Justamente esa es la etapa en la que esta arquitectura es más útil para iterar el producto
      Eso sí, cuando en la etapa Expand empiezas a usar replicación, sí aparecen retos de ingeniería. Aun así, todos son problemas resolubles. También ayuda que en Common Lisp se puede recargar el código en caliente, lo que hace que algunas migraciones sean mucho más fáciles
  • Hace décadas, PG escribió que en Viaweb no usaban base de datos, y que le parecía extraño que las apps web terminaran siendo como un frontend de la base de datos, cuando las apps de escritorio no eran así[0]. HN tampoco usa base de datos
    Pero como hoy en día las apps modernas de escritorio y móviles suelen usar bases de datos, generalmente SQLite, eso ya no aplica tal cual. Esto se debe a que quedó claro que el almacenamiento y las consultas relacionales son bastante útiles en una gama muy amplia de aplicaciones
    [0] https://www.paulgraham.com/vwfaq.html

    • Después de leer el enlace, parece que “base de datos” no significa lo mismo para todo el mundo
      En vwfaq sigue hablando de leer datos desde disco, y también dice “iniciar un proceso que responderá a una petición HTTP”. Aquí, “base de datos” parece referirse a una arquitectura con un servidor separado que persiste los datos y con la que otro servidor tiene que comunicarse para recuperarlos
      Con esa definición, claro que SQLite queda fuera de la categoría. Y si ya estás leyendo datos desde disco, entonces estás usando una base de datos o implementaste tu propia capa de persistencia improvisada. La pregunta es si seguiría considerándose que usas una base de datos si al iniciar la app cargas los datos desde SQLite
      El problema con esa forma de pensar es que ignora que lo esencial de una base de datos es poder guardar y recuperar datos de una manera conveniente, sin preocuparte por detalles de bajo nivel. Guardar datos en una base de datos no significa necesariamente levantar una instancia de Postgres en alguna parte y traer los datos por la web. Si mantienes todos los datos en memoria y tienes un proceso que guarda snapshots en disco con una estructura de datos log-structured, felicidades: acabas de crear tu propia base de datos
    • Sin duda hubo influencia de los textos de PG. Nosotros también usamos Common Lisp, y en este campo es difícil evitar a PG. Aun así, parece que Viaweb no usaba logs transaccionales como bknr.datastore, y gracias a eso el desarrollo se volvió mucho más fluido
    • Cuando PG hizo Viaweb, ni siquiera SQLite era tan común como ahora. De hecho, para corregirlo: SQLite ni siquiera existía en ese momento. Si no existiera SQLite y las opciones fueran, en la práctica, solo almacenes clave-valor, entonces en la mayoría de los casos probablemente bastaría con usar el sistema de archivos
      En segundo lugar, en los últimos 20 años las consultas de RDBMS se han simplificado muchísimo. Aparecieron todo tipo de ORM y mapeadores de filas que reducen el código repetitivo
      También surgieron funciones avanzadas como la búsqueda de texto completo, útiles para apps de escritorio y móviles. Hoy en día usar un RDBMS en una app de escritorio es una buena decisión
    • ¿HN no usa base de datos? ¿Podrías explicar un poco más? Me sorprende bastante
    • En esa época el contexto era distinto. Hasta donde sé, Viaweb estaba compuesto por varias instancias de Common Lisp, y todo el estado de la sesión de los usuarios estaba en la memoria de cada máquina. Recuerdo haber leído en algún lado que incluso parchaban bugs en tiempo real mientras hablaban con usuarios en producción
      La web se volvió muchísimo más grande, y muchas de esas prácticas hoy ya no funcionarían. Si durante una llamada con un cliente yo empujara cambios en vivo a una máquina de producción con las exigencias actuales de testing, muchos aquí pondrían en duda mi salud mental
  • Entiendo las ganas de experimentar con algo interesante, pero hacer esto solo para no aprender lo más básico de MySQL o Postgres parece una pérdida enorme de tiempo. Podrías simplemente construir sobre eso y ya, especialmente si lo corres en una nube pública
    No me convence el argumento sobre el aumento de latencia por ida y vuelta ni el de los problemas de concurrencia. El segundo, además, tiene soluciones simples como tuning básico o aislar a los clientes ruidosos. En otros posts de su blog hablan de la posibilidad de agregar 10 millones de filas al día y de retos de indexación, pero eso literalmente no es nada. Incluso si fuera 10 veces más, no me parece justificación para ponerte a diseñar una solución a medida
    Hasta que de verdad haga falta, aplica lo de “peor es mejor”, y cuando ese momento llegue lo vas a saber con claridad. Para entonces también vas a conocer el cuello de botella, y podrás responder con más inteligencia que si sobrediseñas todo desde el principio

    • Si se tratara de un motor de base de datos basado en servidor, todavía podría plantearse el argumento de que se reducen llamadas de red. Me parece dudoso, pero al menos es posible
      Pero que el bloguero diga que no elige SQLite porque podría traer funciones innecesarias resulta desconcertante. Eso es absurdo y no justifica nada
      El texto se lee como si partiera de una mala solución para un problema imaginario, y luego se esforzara desesperadamente en encadenar argumentos rebuscados para rechazar la solución obvia
    • Lo que me pregunto es si el negocio habría tenido éxito si no hubieran gastado tiempo reinventando la rueda. Construir en público y escribir en el blog también sirve para dar a conocer el producto y demostrar capacidad técnica. Si hubieran usado tecnología aburrida que simplemente encaja bien entre sí, ¿habrían tenido menos cosas de qué hablar y, por tanto, menos efecto de difusión?
      Me pregunto si estoy equivocado, o si este esfuerzo adicional —controversial pero aparentemente innecesario— en realidad forma parte del producto y de la manera de triunfar en este espacio
  • Es raro que empiece con “no estoy hablando de algo como SQLite, donde los datos igual se serializan” y al final termine en un log de transacciones propio que igualmente necesita serialización y además replicación. La replicación de bases de datos, de hecho, ya funciona así
    Si toda la carga entra a un solo servidor, entonces simplemente ejecuta la base de datos en ese servidor y olvídate de una “arquitectura especial para reducir los viajes de ida y vuelta a la base de datos”. Si todos los datos caben en RAM, puedes usar un ramdisk para la base de datos si quieres, y replicar al almacenamiento persistente con herramientas estándar. En la práctica, eso es más simple

    • En general, se siente como que todo se resume en “hicimos nuestro propio SQLite + replicación con Raft”. Solo que sin la confiabilidad comprobada de SQLite ni su capacidad de volcar memoria a disco de forma eficiente
      Así que básicamente suena a https://litestream.io/. Puede que el failover sea más rápido gracias a una configuración explícita de Raft. No uso Litestream, así que no conozco las diferencias sutiles, pero suena muy parecido
      Aun dejando de lado esa simplificación excesiva, la idea en sí me gusta bastante y creo que el texto vende bien el concepto. En muchos sistemas, incluso si funciona de forma anómalamente exitosa, probablemente escalaría lo suficiente como para sostener la mayor parte o incluso todo el negocio, y el rendimiento sería ridículamente bueno comparado con casi cualquier alternativa
    • Tampoco creo que haga falta un ramdisk. La base de datos ya cachea todo en memoria, y solo las escrituras llegan al disco
      Basta con arrancar la base de datos en frío y ejecutar un select bastante grande dos veces
    • Es importante entender que toda startup pasa por tres etapas: Explore, Expand, Extract. Lo que es simple en una etapa no lo es en otra
      Las bases de datos transaccionales son simples en las etapas de Expand y Extract, pero en la etapa de Explore agregan carga porque te hacen enfocarte en problemas de infraestructura y no en el producto. En la etapa de Explore no hay clientes, así que tampoco hay datos, y la confiabilidad de los datos no importa
      Mantener todo en memoria con bknr.datastore (sin replicación) es simple en la etapa de Explore. Pero cuando entras en Expand, aparece la carga operativa de garantizar la consistencia de los datos
      Aun así, para cuando llegas a Expand, ya validaste el producto y además ya escribiste bastante código. Reescribir eso para una base de datos transaccional no tiene sentido; es más fácil agregar encima la replicación con Raft
    • De acuerdo. Rehacer el WAL significa también rehacer o ignorar todos los dolores de cabeza que vienen con él. Parece que recuperar desde el log tarda bastante, así que probablemente ni siquiera hayan llegado todavía a implementar bien el checkpointing del log
    • Los sistemas de trading, de forma bastante explícita, ponen todo en RAM en estructuras preasignadas. Todo depende de qué trade-off elijas
  • La lógica de este artículo me desconcierta. ¿Se supone que esto hace que las aplicaciones con estado sean más simples y más rápidas?
    La premisa es débil y las afirmaciones son exageradas. El autor exagera la dificultad de la serialización para hacer que una postura floja parezca más sólida

    • El post deja muy fuerte la sensación de “miren qué inteligentes somos”
      Normalmente es difícil trabajar con gente así. Qué bueno haber encontrado una startup que se va a hundir sin que yo tenga que lidiar con ellos
    • Y luego implementan serialización para escribir transacciones al log y replicarlas a otros nodos
  • Cuando empiezo un proyecto nuevo, la estructura de datos normalmente es una “lista de elementos con atributos”. Por ejemplo, ahora estoy escribiendo una app de fitness, y los datos son una lista de ejercicios; cada ejercicio tiene título, descripción, URL de video y otros atributos
    Normalmente empiezo poniendo esos elementos en archivos YAML dentro del directorio data. En realidad, es un dialecto YAML personalizado al que le quité las rarezas de YAML. Cada valor es una cadena y no hay conversiones mágicas de tipo. Crear un elemento nuevo es simplemente meter los datos con vim crunches.yaml, y editar o borrar también es muy fácil con esta estructura de datos
    Cuando el proyecto crece, normalmente creo un esquema de DB y muevo los elementos a MariaDB o SQLite
    Esta vez estoy pensando en mover los elementos (ejercicios) a una columna JSON en una DB SQLite. Guardaría todos los atributos de un elemento en un solo campo JSON y quiero escribir un pequeño explorador de DB que permita editar ese campo JSON como si fuera YAML. La idea es conservar la comodidad de editar datos legibles por humanos
    Escribir el explorador de DB parece bastante directo. Un poco de ncurses para recorrer tablas, elegir una, navegar filas e insertar o borrar filas. Para editar un campo, abriría Vim. Si el campo es JSON, lo convertiría a YAML antes de pasarlo a Vim, y cuando el usuario salga de Vim, lo volvería a convertir a JSON

  • Lo que se describía al inicio del texto era básicamente parecido a cómo funcionaban las máquinas NUMA (por ejemplo, SGI Altix o UV). Además, las ventajas que defendían eran baja latencia y la posibilidad de paralelizar trabajo con multithreading sobre grandes cantidades de RAM. El clustering surgió como una alternativa de bajo costo frente a máquinas de más de un millón de dólares. También hay similitudes con la persistencia de AS/400, donde la app simplemente escribía en memoria y eso se mapeaba de forma transparente a disco
    Ahora, con hardware barato, es como volver en el tiempo hacia las ventajas de una máquina NUMA en clúster. Hubo mejoras en el camino, y el artículo me pareció una lectura entretenida
    Otra técnica del pasado era eliminar el stack TCP/IP dentro del clúster para quitar problemas relacionados. Soluciones como Active Messages eran una capa delgada sobre el hardware. También existen diseños de routers de red con consistencia fuerte integrada. Pueden hacer bastantes cosas
    Si esto crece, también hay oportunidades de hardware. Del lado de CPU, SGI hizo dos cosas. Las máquinas NUMA escalaron la cantidad de CPU y RAM de un mismo sistema, y además conectaron FPGA directamente al bus de memoria para usarlos como aceleradores personalizados. Por último, algunos papers de ciencias de la computación modificaron el conjunto de instrucciones del procesador, la red interna del chip, etc., para eliminar o reducir los cuellos de botella del multithreading. Chips como OpenPiton aumentan la cantidad de núcleos con cores abiertos y personalizables (por ejemplo, 32)

  • La idea de “imagina qué cosas tan geniales podrías construir si no tuvieras que serializar los datos en consultas SQL” ya existe en implementaciones suficientemente maduras del modelo de actores[0], por ejemplo Akka Event Sourcing[1]. También aborda este otro problema
    “Pero la parte importante es cómo recuperarse cuando un proceso se cae. La respuesta es fácil: basta con tomar periódicamente snapshots de toda la RAM”
    Queda resuelto en esencia sin necesidad de crear una “nueva arquitectura para desarrollo web”. También hay intentos open source de explorar el protocolo RAFT usando actores aquí[2] y aquí[3]
    0 - https://en.wikipedia.org/wiki/History_of_the_Actor_model
    1 - https://doc.akka.io/docs/akka/current/typed/persistence.html
    2 - https://github.com/Michael-Dratch/RAFT_Implementation
    3 - https://github.com/invkrh/akka-raft

  • Mi primer pensamiento fue: “Ah, así era como yo lo hacía al programar en Common Lisp; qué divertido que alguien redescubriera esa técnica”
    Pero no, solo resulta que había más gente de Lisp

  • “La RAM es muy barata” puede ser uno de los mayores malentendidos entre desarrolladores
    Los SSD han mejorado entre 100 y 10000 veces en rendimiento o IOPS, y el rendimiento por dólar de los vCPU también ha subido entre 20 y 50 veces. Pasamos de 45/32nm a 5nm/3nm, y el número de instrucciones por ciclo también ha aumentado muchísimo
    Pero el precio de la RAM casi no ha bajado tanto como el de los CPU o SSD. Puede que sea mucho más rápida, que puedas instalar mucha más memoria con chips de mayor densidad y que los canales hayan pasado de dual a 8 o incluso 12. Pero si miras los precios spot de DRAM entre 2008 y 2022, el precio mínimo de la DRAM fue prácticamente el mismo tres veces, alrededor de 2.8 dólares por GB. En ese mismo periodo, por ciclo subió y bajó hasta 6~8 dólares por GB. Es decir, si compraste DRAM en un mínimo o un máximo durante estos últimos 15 años, ignorando la inflación, el precio habría sido más o menos similar, dentro de un margen aproximado de ±10~20%
    No fue sino hasta mediados de 2022 que se rompió la barrera de 2.8 dólares por GB y cayó cerca de 1 dólar por GB, para luego estabilizarse alrededor de 2 dólares por GB en DDR5
    Ahora puedes poner 4 TB de RAM en un solo servidor. Eso no significa que la DRAM sea absurdamente barata. Lo que pasa es que el desarrollador promedio o el de big tech gana mucho más que en 2010, por eso siente que la RAM es mucho más accesible. En realidad, incluso en el punto más bajo de los últimos 15 años, la caída del precio de la DRAM apenas supera un poco el doble. Y es muy probable que el precio de la DRAM vuelva a dispararse en 1~2 años

    • También se puede interpretar de otra manera. La capacidad máxima de RAM por nodo individual ha aumentado de forma drástica en las últimas décadas
      Como ejemplo sencillo, si hace 20 años cierto nodo estaba limitado a 16 GB de RAM, habrías necesitado 256 nodos para tener 4 TB de RAM en el sistema, sin contar la sobrecarga de cada OS
      Hoy un solo nodo puede contener esos 4 TB completos en un solo chasis
      Puede que el costo total de los chips de RAM no haya cambiado, pero el costo de usar realmente esa RAM en un sistema físico ha bajado de forma drástica
    • Al mismo tiempo, muchos desarrolladores recurren demasiado rápido a los sistemas distribuidos incluso cuando bastaría con comprar más RAM. Probablemente el autor también iba por ahí
    • Gracias por los datos. Estoy de acuerdo con la otra respuesta. En vez de decir que era “barata”, probablemente habría sido mejor decir que ahora es más fácil conseguir máquinas con cientos de GB de RAM