- Ben Johnson, creador de BoltDB (una base de datos key-value embebida), ahora está desarrollando Litestream en FlyIO
- La estructura sensata de una aplicación full-stack es n-Tier: servidor de aplicaciones + servidor de base de datos
→ En esta arquitectura, SQLite se usaba solo para pruebas unitarias, pero ahora ya puede usarse perfectamente como capa de datos y persistencia - Litestream es un proyecto open source que hace posible usar SQLite en aplicaciones full-stack mediante replicación
Breve historia de las bases de datos de aplicación
-
50 años no es tanto tiempo, pero la forma en que el software administra datos ha cambiado enormemente
→ En los años 70 apareció el "modelo de Codd", que definía lo que era una base de datos relacional
→ Todos los datos están en tablas, con CRUD, esquemas, lenguaje SQL, etc.
→ En los 80 y 90 explotó la cantidad de bases de datos SQL como Oracle/DB2/Postgres/MySQL
→ Las bases de datos XML de los 2000 no fueron buenas, y al mismo tiempo surgieron excelentes bases de datos columnares
→ En los 2010 se lanzaron grandes proyectos open source de bases de datos distribuidas, y ahora cualquiera puede armar un clúster y consultar datos a escala de terabytes -
A medida que las bases de datos evolucionaron, también avanzaron las estrategias para conectarlas con las aplicaciones
→ Después de Codd, se separaron por tiers
→ Primero estaba el tier de base de datos
→ Luego vinieron el tier de caché con memcached y Redis
→ Después el tier de trabajos en segundo plano (Sidekiq), el tier de ruteo (PgBouncer), el tier de distribución, etc.
→ Muchos tutoriales hablan como si todo fuera 3-Tier, pero como nunca sabes cuántos tiers terminarán entrando, aquí lo llamamos "n-Tier" -
En estos 50 años también vimos cómo CPU, memoria y disco se volvieron cientos de veces más rápidos y baratos
→ La palabra que realmente definió la innovación en bases de datos en los 2010 fue "big data"
→ Pero con las mejoras de hardware, en 2020 ese concepto ya era más difícil de sostener
→ En 1996 administrar una base de datos de 1GB era algo enorme, pero en 2022 hasta una laptop o una t3.micro pueden manejarlo sin problema -
Cuando pensamos en nuevas arquitecturas de bases de datos, quedamos hipnotizados por los límites de escalabilidad
→ Si no puede manejar petabytes, o al menos terabytes, ni siquiera entra en la conversación
→ Pero la mayoría de las aplicaciones, incluso si tienen éxito, difícilmente llegan a ver terabytes de datos
→ Estamos usando un martillo neumático para clavar un clavo
El dulce lanzamiento de SQLite
- Hay una base de datos que refleja muy bien esta tendencia
- Es una de las bases de datos SQL más famosas del mundo, es un formato oficial de preservación de la Biblioteca del Congreso de EE. UU., es conocida por su fiabilidad, por una suite de pruebas de tamaño difícil de imaginar, y además su rendimiento es excelente
- A estas alturas ni haría falta decir el nombre... pero para quien está levantando la mano al fondo... sí, estamos hablando de SQLite
- SQLite es una base de datos embebida. En una arquitectura por tiers tradicional ni siquiera existe como un tier: es simplemente una librería enlazada al proceso de tu servidor de aplicaciones
→ Una "aplicación de proceso único" que corre sola, sin depender de otro servidor
- Como yo construyo bases de datos, me interesan este tipo de aplicaciones
- Yo creé BoltDB, una base de datos key/value embebida muy conocida en el ecosistema de Go
- BoltDB es estable y ofrece un rendimiento como de carrito de juguete con nitro, justo lo que esperas de una base de datos in-process
- Pero BoltDB tiene limitaciones
→ Como el esquema se define en código Go, las migraciones de base de datos son difíciles. Tú mismo tienes que crear las herramientas. Ni siquiera hay REPL
- Si tienes cuidado, este tipo de base de datos puede darte un rendimiento increíble
- Pero probablemente no querrías operar una base de datos así para uso general
- Me puse a pensar qué habría que hacer para que BoltDB pudiera usarse en más aplicaciones, y la conclusión a la que llegué fue: "SQLite fue hecha exactamente para eso"
- SQLite también tiene restricciones, claro. La mayor es que una aplicación de proceso único tiene un SPOF (Single Point of Failure): si pierdes el servidor, pierdes también la base de datos. No es un defecto de SQLite, es simplemente parte de su diseño
Entra Litestream
- Hay dos grandes razones por las que mucha gente no usa SQLite por defecto
→ Primero, la resiliencia ante errores de almacenamiento
→ Segundo, la concurrencia a gran escala - Litestream tiene algo que decir sobre ambos problemas
- Litestream funciona controlando el journaling del modo WAL (Write Ahead Log) de SQLite
- En modo WAL, las operaciones de escritura se agregan a un archivo de log separado del archivo principal de base de datos de SQLite
- Los readers revisan tanto el archivo WAL como la base principal para satisfacer las consultas
- Normalmente SQLite ejecuta automáticamente checkpoints de las páginas del WAL hacia la base principal
- Litestream se mete en ese punto de interrupción: abre una transacción infinita de lectura para impedir el checkpoint automático, captura y replica directamente las actualizaciones del WAL, y luego dispara sus propios checkpoints
Lo más importante que hay que entender sobre Litestream es que esto sigue siendo simplemente SQLite. La aplicación usa SQLite estándar; no añade dependencias, no analiza queries ni actúa como proxy. Solo aprovecha las funciones de journaling y concurrencia que SQLite ya tiene. En la mayoría de los casos, tu código ni siquiera necesita enterarse de que Litestream existe
- Suena complejo, pero en realidad es muy simple. Cuando lo usas, te das cuenta de que simplemente "just works"
$ litestream replicate fruits.db s3://my-bukkit:9000/fruits.db
$ litestream restore -o fruits-replica.db s3://my-bukkit:9000/fruits.db
- Normalmente la gente lo usa para replicar una base de datos SQLite y guardarla en S3
- Eso trae grandes ventajas operativas. Tu base de datos se vuelve resiliente y fácil de mover o migrar
- Pero con Litestream se puede hacer más
- En la siguiente versión será posible hacer replicación en tiempo real entre bases de datos SQLite, lo que permitirá configurar réplicas de lectura distribuidas y una base de datos líder de escritura
→ Las réplicas de lectura podrán capturar escrituras y redirigirlas al líder
→ Como muchas aplicaciones son read-heavy, este setup podrá darles una base de datos escalable globalmente
Deberías tomarte más en serio esta opción: usar SQLite como base de datos de aplicación
- Uno de mis primeros trabajos en TI fue como DBA de Oracle a inicios de los 2000
- Pasé muchísimo tiempo leyendo libros y documentación para aprender Oracle
- El manual del administrador tenía casi mil páginas, y era solo uno entre cientos de documentos
- En esa época, aprender qué hacer para optimizar queries o mejorar escrituras marcaba una gran diferencia
- Los discos duros leían unas decenas de megabytes por segundo, así que aprovechar mejores índices podía convertir una query de 5 minutos en una de 30 segundos
- Pero la optimización de bases de datos cada vez importa menos para la aplicación promedio
- Si tienes una base de datos de 1GB, un disco NVMe puede cargar todo en memoria en menos de un segundo
- A mí me encanta optimizar queries SQL, pero para muchos desarrolladores de aplicaciones esto se está volviendo una habilidad en extinción
- Incluso queries mal afinadas pueden ejecutarse en menos de un segundo en muchas bases de datos
- Postgres moderno es un milagro. He aprendido muchísimo leyendo ese código durante años
- El optimizador de consultas, las políticas de seguridad por fila, seis tipos de índices, etc.
- Si necesitas esas funciones, entonces sí, las necesitas; pero la mayoría no
- Y si no necesitas esas funciones de Postgres, entonces viene la carga operativa
- Aunque no uses múltiples cuentas, igual tienes que configurar autenticación basada en host y abrir el firewall
- Más funciones significan más documentación, así que se vuelve más difícil entender el software que realmente estás operando
- La documentación de Postgre14 tiene casi 3 mil páginas
- SQLite tiene un subconjunto de las funciones de Postgres. Pero suele cubrir el 99.9% de lo que yo quiero
- Excelente soporte de SQL, funciones de ventana, CTE, búsqueda de texto completo, soporte de JSON, etc.
- Y si algo le falta, como los datos están al lado de mi aplicación, el costo de traerlos y procesarlos es muy bajo
- Mientras tanto, los problemas realmente complejos que sí tenemos que resolver no los solucionan las funciones centrales de una base de datos
- Lo que quiero optimizar, en cambio, es latencia y experiencia de desarrollador
- Por eso, una gran razón para considerar SQLite seriamente es que operarla es realmente simple
- Puedes dejar de perder tiempo diseñando una capa de base de datos y dedicarlo a escribir código de aplicación
- Pero hay otro problema
La luz es demasiado lenta: The Light is Too Damn Slow
- Empezamos a toparnos con límites teóricos. En el vacío, la luz recorre 186 millas en 1 milisegundo (la distancia de ida y vuelta entre Filadelfia y Nueva York)
- Si además agregas switches de red, firewalls y capas de protocolo de aplicación, todo se vuelve aún más lento
- Dentro de una sola región de AWS, la sobrecarga de latencia de una query a Postgres puede llegar hasta 1 milisegundo
- Eso no significa que Postgres sea lento; significa que ya chocamos con el límite de la velocidad a la que puede moverse la información
- Las aplicaciones modernas procesan requests HTTP, y antes incluso de terminar varias consultas a base de datos y la lógica de negocio o el renderizado, ya consumieron 10ms
- Hay un número mágico en la latencia de aplicaciones: una respuesta por debajo de 100ms se siente casi instantánea
- Las aplicaciones ágiles y responsivas crean usuarios felices
- 100ms parece mucho, pero se va rapidísimo sin darte cuenta
- Ese umbral de 100ms es tan importante que la gente prerenderiza páginas y las sube a un CDN para reducir latencia
- Nos conviene mover los datos más cerca de la aplicación. ¿Qué tan cerca? Muy, muy cerca
- SQLite no solo está en la misma máquina que tu aplicación, sino dentro del mismo proceso de tu aplicación
- Si pones los datos al lado de la aplicación, puedes ver que la latencia por query baja a 10~20 microsegundos (μ)
- Es decir, entre 50 y 100 veces más rápido que una query a Postgres dentro de la misma región
- Pero hay más. Eliminamos de forma efectiva la latencia por query. Nuestra aplicación es más rápida y además más simple
- Podemos dividir queries grandes en consultas más pequeñas y manejables, y dedicar más tiempo a construir nuevas funciones en vez de obsesionarnos con patrones N+1
- Minimizar la latencia no es solo para producción. Hacer pruebas de integración con una base de datos cliente/servidor tradicional suele alargarse varios minutos en local, y el dolor sigue incluso cuando lo mandas a CI
- Si reduces el loop de feedback entre cambiar código y terminar las pruebas, ahorras tiempo y mantienes mejor el enfoque mientras desarrollas
- Con SQLite, un cambio de una sola línea puede ejecutarse en memoria y correr pruebas de integración en cuestión de segundos
Pequeña, rápida, confiable y distribuida globalmente: elige las 4
- Litestream es distribuida, replicada y, lo más importante, fácil de entender
- En serio, "pruébala una vez". Hay muy poco que necesites aprender
- Mi argumento es este:
- Si construyes una replicación estable y fácil de usar para SQLite, las aplicaciones full-stack operadas solo sobre SQLite se vuelven una opción muy atractiva
- En la época en que se escribían tutoriales como "hacer un blog con Rails", esta opción se pasaba por alto, pero el SQLite de hoy puede soportar la carga de escritura de la mayoría de las aplicaciones y, mediante réplicas, balancear lecturas desde muchísimas instancias
- Litestream también tiene limitaciones
- Como fue hecho para aplicaciones de nodo único, no funciona bien en plataformas serverless ni en despliegues rolling
- Como hay que restaurar todos los cambios de manera secuencial, restaurar la base de datos puede tardar varios minutos
- Estamos preparando replicación en tiempo real, pero el modelo de proceso separado tiene limitaciones en el control fino de las garantías de replicación
- Podemos hacerlo mejor
- Lo que he hecho durante el último año ha sido definir el núcleo de Litestream y enfocarme en la corrección
- Estoy satisfecho con el punto al que hemos llegado
- Comenzó como una herramienta simple de backup por streaming, pero poco a poco está evolucionando hacia una base de datos estable y distribuida
- Mi trabajo en Fly.io es hacer que esto sea más rápido y más fluido
- Independientemente de Fly.io, Litestream seguirá recibiendo muchas más mejoras
- Litestream encontró un nuevo hogar en Fly.io, pero seguirá siendo un proyecto open source
- Mi plan para los próximos años es hacerlo más útil sin importar dónde se ejecuten las aplicaciones, y ver hasta dónde puede llegar el modelo de SQLite
6 comentarios
Me dieron ganas de volver a leerlo bien una vez más.
Había pensado algo parecido, pero esto es mucho más serio y está desarrollado a fondo. Me impresionó mientras lo leía. También me dieron ganas de probar Litestream.
Sería mejor si se pudieran hacer consultas remotas... snif snif
Me recuerda al momento en que Elixir empezó a llamar la atención. Es una herramienta que ofrece bases de datos distribuidas embebidas y orquestación a nivel del lenguaje, aunque no estoy muy seguro de si ese es el futuro.
¡Lo leí con gusto!
Iba a leerlo por encima y resumirlo, pero mientras lo hacía se puso interesante y terminé extendiéndome.
Litestream - herramienta de replicación en streaming para SQLite
Creo que está bueno verlo junto con la pregunta ¿Alguien ha usado SQLite como base de datos primaria?.
Parece que también podría haber una conexión con Cloudflare presenta D1, una base de datos SQL para Workers, que se publicó hace unos días.
También revisen los comentarios en HN https://news.ycombinator.com/item?id=31318708