Artículos en profundidad sobre la tecnología que da forma al futuro.

SQLite distribuido: 5 mitos probados tras 12 años

Doce años de SQLite distribuido (Raft, Litestream, Turso, D1): probamos cinco creencias sobre consistencia, replicación y cuándo usar Postgres.

Un anillo de pequeños archivadores conectados por hilos de luz, uno de ellos brillando como líder
Doce años de SQLite distribuido: muchos diseños, una idea central que no deja de reinventarse.

El SQLite distribuido pasó de ser el proyecto de fin de semana de una persona a una categoría de producto respaldada por capital de riesgo sin que la mayoría de ingenieros notara la década intermedia. En septiembre de 2014, Philip O'Toole puso un log de Raft delante de SQLite, replicó cada escritura en un clúster y subió el código a GitHub como rqlite. Doce años después existe todo un ecosistema (dqlite, Litestream, LiteFS, cr-sqlite, libSQL/Turso, Cloudflare D1) y buena parte de lo que la gente cree al respecto está equivocado de maneras interesantes.

He ejecutado bases de datos en clúster en producción y he limpiado los desastres que provocan sus modos de fallo, así que te ahorro algo de dolor: las discusiones sobre SQLite distribuido casi nunca tratan sobre SQLite. Tratan sobre qué pregunta estás haciendo en realidad: ¿qué pasa si la máquina muere?, ¿qué pasa si la máquina muere y no puedo esperar?, o ¿qué pasa si no hay ninguna máquina? Aquí van cinco mitos, puestos a prueba contra doce años de historia.

Mito 1: el SQLite distribuido es una idea reciente

La ola actual de textos sobre SQLite replicado arranca hacia 2021, cuando Litestream y el movimiento «SQLite es suficiente» cobraron fuerza, y Turso y Cloudflare D1 llegaron con financiación y anuncios de lanzamiento. Así se extendió la creencia de que alguien listo acababa de darse cuenta de que SQLite podía replicarse. Pero el primer sistema de Raft sobre SQLite se publicó en 2014, y ni siquiera fue la primera replicación de SQLite: Expensify lo venía haciendo internamente con un protocolo tipo Paxos desde hacía años, antes de publicar Bedrock en 2016, y ActorDB dio a cada actor de Erlang su propia base de datos SQLite replicada con Raft por la misma época.

¿Por qué importa más allá del reconocimiento? Porque un diseño que sobrevive doce años de reinvenciones independientes dice algo. Al menos ocho equipos distintos, con necesidades y protocolos diferentes, miraron el problema y llegaron a la misma arquitectura: SQLite como motor de almacenamiento y la replicación añadida por fuera. Nadie encontró motivo para dejar de hacerlo. Cuando ves ese patrón, una reimplementación convergente durante una década, estás ante una idea realmente buena y no ante un ciclo de hype. Compáralo con las categorías que explotan y desaparecen; esta fue acumulando valor.

También importa porque las decisiones de diseño originales siguen soportando carga. rqlite eligió ser un sistema CP en 2014 (las escrituras pasan por el líder, la mayoría debe confirmarlas y una minoría particionada rechaza escrituras) y nunca cambió de opinión. Esa única elección determinó la API HTTP, los niveles de consistencia de lectura y el comportamiento de los snapshots. Los equipos que tratan la consistencia como un interruptor de configuración que se decidirá más adelante suelen arrepentirse; la brecha entre la especificación que supusiste y la que implementaste es exactamente donde se crían los bugs distribuidos.

Mito 2: Raft sobre SQLite te da un Postgres distribuido barato

Este es el mito más caro de la lista. La propuesta suena tentadora: tomar la base de datos más desplegada del mundo, añadirle consenso y ahorrarte el peso operativo de un sistema cliente-servidor. Lo que obtienes en realidad es una base de datos con un único escritor en la que cada escritura paga un viaje de ida y vuelta por red a la mayoría de nodos, y donde la superficie SQL de SQLite (sin procedimientos almacenados, un sistema de tipos sencillo, primitivas de concurrencia limitadas) sigue siendo la misma superficie SQL de SQLite.

El coste honesto está en la ruta de escritura. Un commit local en SQLite son un par de fsync. Un commit replicado con Raft es: el líder añade la entrada a su log, la envía a los seguidores, espera a que una mayoría la persista, la aplica en SQLite y responde. En una LAN sana son quizá uno o dos milisegundos en lugar de decenas de microsegundos. Entre regiones, es la velocidad de la luz más el disco. Para cargas de lectura intensiva con tasas de escritura moderadas (almacenes de configuración, metadatos de service discovery, pequeños planos de control) eso está bien, y es exactamente el nicho para el que se construyó rqlite (su origen fue un sistema en clúster que necesitaba una pequeña cantidad de almacenamiento relacional, sin buena respuesta sobre qué nodo debía alojarlo). Para una carga OLTP intensa, es la herramienta equivocada.

Y luego está la realidad operativa que nadie pone en el README: la elección de líder es un modo de fallo que ahora te toca gestionar. La división de cerebros se evita con votación por mayoría, sí, pero eso significa que un clúster de tres nodos que pierde dos rechaza todas las escrituras. Has cambiado «la base de datos está caída porque el servidor murió» por «la base de datos está caída porque se cayó el quórum», y la segunda es más difícil de explicarle a tu guardia a las 3 de la mañana. Si no has pensado en pruebas al estilo Jepsen para el sistema que eliges, deberías: un comentarista de Hacker News preguntó con mucha intención si rqlite ha pasado por Jepsen, porque los sistemas que no lo han pasado suelen tener sorpresas al acecho. Mientras tanto, un ingeniero con experiencia en la misma discusión señaló que, con las herramientas actuales, ejecutar un Postgres de verdad se ha vuelto lo bastante fácil como para que «usa simplemente un servidor de base de datos» sea otra vez un valor por defecto legítimo. No le falta razón.

Una balanza sopesando un bloque sencillo contra una maraña de cadenas y engranajes
El consenso te compra conmutación por error; el precio se paga en cada escritura.

Mito 3: replicar significa consenso

Si hay una idea que realmente aporta la historia de doce años, es esta: el «SQLite replicado» no es un único diseño, sino tres esquinas de un espacio de diseño, y aparecieron en un orden revelador. Confundirlas es lo que lleva a los equipos a mantener un clúster de Raft para resolver un problema de copias de seguridad.

  • Consenso (CP). rqlite, dqlite. Un líder, consistencia fuerte, un clúster de verdad. Responde a: «¿qué pasa si la máquina muere y no puedo esperar a restaurarla?». Coste: latencia de escritura y operaciones de quórum.
  • Streaming (un solo escritor). Litestream, y más tarde LiteFS. Sin consenso en absoluto: el WAL de SQLite se transmite a almacenamiento de objetos, lo que da recuperación ante desastres casi continua para un único nodo. Responde a: «¿qué pasa si la máquina muere?». Coste: una pequeña ventana de posible pérdida de datos y un tiempo de restauración de segundos a minutos.
  • Fusión (AP). cr-sqlite y el movimiento local-first. CRDTs como extensión de SQLite, con muchos escritores divergiendo y fusionándose sin líder. Responde a: «¿qué pasa si no existe ninguna máquina central?». Coste: consistencia eventual y una semántica de fusión que tienes que diseñar.

El argumento de Ben Johnson a favor de Litestream merece respeto, porque acertaba en la mayoría de aplicaciones: funcionan en una sola máquina y necesitan una forma de recuperarse cuando esa máquina muere, no un clúster. La primera pregunta («¿qué pasa si la máquina muere?») es mucho más común que la segunda («y no puedo esperar a restaurarla»). He visto equipos desplegar un clúster Raft de tres nodos para proteger una base de datos cuyo RTO era «en algún momento antes de la comida». Eso no es ingeniería de fiabilidad, es culto a la carga. Y como cierre redondo: el enfoque de streaming de WAL de Litestream acabó inspirando cómo el propio rqlite gestiona el WAL en la v8; la esquina de streaming retroalimentó la de consenso.

Así que antes de elegir una herramienta, escribe la pregunta. Literalmente. «Cuando el nodo X muera, necesitamos que las escrituras se reanuden en N segundos, y aceptamos perder los últimos M segundos de datos confirmados.» Si M son unos pocos segundos y N es un minuto, Litestream a S3 es tu respuesta y nunca te despertará por un problema de quórum. Si M es cero y N son segundos, estás en territorio de consenso y lo pagarás en cada escritura. Es la misma disciplina que cualquier modelado de amenazas: nombra el fallo antes de comprar la defensa, igual que al abordar sistemas que resisten la catástrofe, decidiendo de antemano qué catástrofe estás resistiendo.

Mito 4: el «SQLite en el edge» gestionado elimina los compromisos

El dinero de esta categoría fue a las plataformas: Turso recibió financiación de capital de riesgo y fue adquirida por Supabase; Cloudflare lanzó D1 dentro de su red edge. La propuesta es que alguien más se encarga ahora de las partes difíciles de lo distribuido. A veces es cierto. A veces es marketing, y el secreto incómodo se filtra: un desarrollador de Cloudflare admitió públicamente que D1 es básicamente un envoltorio alrededor de un Durable Object singleton que expone su base de datos SQLite, y que muchas veces te conviene usar directamente el almacenamiento de Durable Objects. Eso no es una base de datos distribuida en sentido de consenso; es un único archivo SQLite con una plataforma alrededor. Los usuarios que ejecutan D1 en producción informan de una fiabilidad acorde.

Nada de esto significa que las opciones gestionadas sean malas. Significa que los compromisos no desaparecieron: se trasladaron detrás de una API en la que no puedes mirar. Cuando ejecutas rqlite tú mismo, sabes exactamente qué significa CP: tres nodos, un líder, confirmaciones por mayoría, y una minoría particionada que rechaza escrituras. Cuando compras «SQLite en el edge», tienes que hacerle las mismas preguntas a una caja negra: ¿dónde está el escritor?, ¿qué pasa con una partición?, ¿cuál es mi ventana real de durabilidad? Si la respuesta del proveedor es vaga, tu revisión de incidentes no va a ser clara.

La replicación como servicio no elimina la pregunta sobre consistencia. Solo traslada la respuesta a la infraestructura de otro, donde no puedes auditarla.

Conviene interiorizar el patrón histórico: los sistemas de código abierto (rqlite, dqlite, Litestream) llevaron la idea durante ocho años antes de que nadie la vendiera, y el capital fluyó hacia quien convirtió la idea en un servicio con factura. Ese es el destino habitual de la infraestructura. Comprar suele ser lo correcto, pero hazlo con los ojos abiertos sobre cuál de las tres esquinas estás obteniendo realmente.

Una bola de nieve con una cabaña cálida en su interior, sobre un campo brumoso lleno de racks de servidores
SQLite gestionado en el edge: una base de datos acogedora envuelta en una plataforma que no puedes ver por dentro.

Mito 5: la consistencia fuerte es una mejora gratuita sobre las copias de seguridad

La gente plantea la elección como copias de seguridad (débiles, baratas) frente a consenso (fuerte, caro pero estrictamente mejor). No es así. La consistencia fuerte con Raft es un producto distinto, no una versión mejor del mismo producto, e impone impuestos continuos que las copias de seguridad nunca cobran.

Impuesto uno: el rendimiento. Un profesional que usó ambos señaló que rqlite te da garantías de «almacenado en más de un lugar», pero es mucho más lento que una base de datos de un solo nodo; eso es el viaje de ida y vuelta del consenso, y nunca desaparece. Impuesto dos: la semántica sutil. Si replicas enviando sentencias SQL, heredas problemas de determinismo: funciones como random() o datetime('now') pueden divergir entre nodos a menos que la capa las reescriba, por eso algunas implementaciones solo soportan un subconjunto del SQL de SQLite. Si replicas a nivel de WAL, te libras de eso, pero te acoplas al formato en disco y a la disposición de páginas de SQLite. No hay ubicación de la capa sin algún compromiso. Impuesto tres: las transacciones distribuidas son realmente difíciles. Un ingeniero describió cómo evolucionó un diseño en el que cada sentencia de una transacción era un comando Raft separado (más o menos correcto, pero lento) hacia otro en el que el nodo que inicia ejecuta toda la transacción localmente, la revierte y envía un único comando de commit con el lote completo. Esa es la clase de lección dura que solo aprendes operando estos sistemas.

Aquí tienes una ilustración mínima de la diferencia en la ruta de escritura, usando la API HTTP de rqlite:

# Local SQLite: one process, one fsync, microseconds.
sqlite3 app.db "INSERT INTO kv VALUES ('flag', 'on')"
# rqlite: this HTTP call hides a Raft round trip —
# leader log append, replication to a majority, then apply.
curl -XPOST 'localhost:4001/db/execute?pretty' \
-H 'Content-Type: application/json' \
-d '["INSERT INTO kv VALUES (?, ?)", "flag", "on"]'
# The guarantee changed: the write now survives the loss
# of any single node. So did the latency budget.

Y fíjate en lo que no obtuviste: escalado de lecturas. Raft replica el log para que los seguidores puedan tomar el relevo; no hace que los seguidores sirvan lecturas con consistencia fuerte por defecto (rqlite ofrece niveles de consistencia de lectura precisamente porque esto es un compromiso). Si viniste por «distribuido» esperando más capacidad de lectura, lo que querías eran réplicas de lectura, no consenso.

Lo que de verdad enseñan doce años de Raft sobre SQLite

La historia de la categoría es una prueba inusualmente limpia de cómo maduran las ideas de infraestructura. Primero alguien construye la solución con principios para una necesidad concreta (rqlite, a partir de un problema real de clustering en un trabajo real). Luego otros la reconstruyen para sus restricciones de empaquetado: dqlite de Canonical es una biblioteca que se incrusta porque Canonical necesitaba una base de datos dentro de sus productos, mientras que rqlite es un servidor que ejecutas junto a cualquier producto. Después alguien sostiene que el caso común es más pequeño de lo que supone la solución con principios (Litestream), demuestra que tenía razón, y todo el campo se recalibra. Por último, las plataformas lo envuelven y lo venden. Si trabajas en infraestructura el tiempo suficiente, verás esta película otra vez: los agentes LLM están reaprendiendo lecciones de sistemas distribuidos de la misma forma ahora mismo.

Mi recomendación sin rodeos, tras todo esto: empieza con SQLite en un solo nodo más streaming del WAL a almacenamiento de objetos. Es asombroso hasta dónde llega, y tu historia de fallo es una restauración, no un postmortem de consenso. Pasa a un sistema basado en Raft solo cuando puedas enunciar, en una frase, el requisito de disponibilidad que el streaming no puede cumplir, y acepta la factura de latencia de escritura y operaciones de quórum que viene con él. Recurre a Postgres en cuanto necesites su superficie SQL o un rendimiento con múltiples escritores. Y si nada de esto encaja, el mundo más amplio de bases de datos embebibles también tiene opciones, desde bases de datos de grafos embebibles hasta la esquina de CRDTs local-first.

El resumen que desmonta los mitos: el SQLite distribuido no es nuevo, no es un Postgres barato, no es un único diseño, no lo vuelve seguro un envoltorio gestionado y no es una mejora gratuita sobre las copias de seguridad. Es un espacio de diseño de doce años, bien cartografiado, con tres esquinas, cada una correcta para una pregunta distinta. Los ingenieros que salen escaldados son los que nunca escribieron la pregunta.