El mito: SQLite es para apps móviles y prototipos. La realidad: miles de aplicaciones productivas (desde Tailscale hasta Fly.io, Expensify o pequeños SaaS) lo usan como BD principal. La combinación de SQLite + WAL mode + Litestream + (opcionalmente) LiteFS hace posible escalar en escenarios donde la respuesta por defecto sería PostgreSQL. Este artículo cubre cómo, cuándo y sus límites reales.

Puntos clave

  • WAL mode convierte SQLite en un motor con lecturas concurrentes sin bloqueo de escrituras, el cambio que hace viable el uso en servidor.

  • Litestream stream el WAL a S3 en near-realtime: durabilidad comparable a Postgres sin servidor de BD separado.

  • LiteFS (de Fly.io) replica SQLite entre múltiples nodos con consistencia fuerte mediante leasing.

  • SQLite gana sobre Postgres en apps con 1 proceso, queries simples y hasta ~100 GB de datos, donde la latencia cero a BD importa.

  • SQLite no escala bien con múltiples procesos escribiendo simultáneamente ni con queries analíticos masivos (ahí entra DuckDB).

Por qué SQLite es viable en servidor

Las asunciones tradicionales están obsoletas:

  • "No escala": falso. SQLite maneja miles de writes/s y cientos de miles de reads/s en hardware modesto.

  • "Concurrent writes no": cierto históricamente, pero WAL mode mitiga mucho.

  • "No hay replicación": Litestream, LiteFS y rqlite resuelven.

  • "No hay backup tool": Litestream stream a cualquier almacenamiento S3 compatible.

Para apps con 1 proceso / 1 instancia, SQLite es espectacularmente productivo.

WAL mode: el cambio fundamental

Write-Ahead Logging, habilitado con un PRAGMA:

PRAGMA journal_mode = WAL;

Ventajas:

  • Lecturas concurrentes no bloquean escrituras.

  • Escrituras concurrentes se serializan pero con menos contención.

  • Mejor durabilidad vs journal_mode tradicional.

  • Checkpointing incremental de WAL a BD.

Casi todo despliegue serio de SQLite usa WAL. Es el default razonable.

Pragmas recomendados para producción

La diferencia entre los defaults y una configuración optimizada puede ser de 3-10×:

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;    -- vs FULL: trade-off durabilidad/perf
PRAGMA cache_size = -64000;     -- 64 MB cache
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;     -- 5s antes de SQLITE_BUSY
PRAGMA temp_store = MEMORY;
PRAGMA mmap_size = 268435456;   -- 256 MB memory-mapped

Litestream: replicación a S3

Litestream[1] stream el WAL a S3 (o cualquier compatible) en near-realtime:

# /etc/litestream.yml
dbs:
  - path: /data/app.db
    replicas:
      - type: s3
        bucket: my-backups
        path: app.db
        region: eu-west-1
        access-key-id: ${AWS_ACCESS_KEY_ID}
        secret-access-key: ${AWS_SECRET_ACCESS_KEY}

Restore trivial en caso de desastre:

litestream restore -o /data/app.db s3://my-backups/app.db

Para backup productivo, Litestream hace SQLite casi comparable a Postgres en durabilidad sin necesitar un servidor de BD separado. Este patrón encaja perfectamente con Deno y otras plataformas edge donde la simplicidad operacional es crítica.

LiteFS: replicación multi-nodo

LiteFS[2] (de Fly.io) lleva más lejos: réplica completa de SQLite entre nodos usando FUSE filesystem.

  • Primary + replicas: writes al primary, reads en cualquier nodo.

  • Consistencia fuerte con leasing distribuido.

  • Failover automatizado.

Útil para aplicaciones que requieren alta disponibilidad o distribución geográfica.

rqlite: SQLite distribuida con Raft

rqlite[3] es otro enfoque: SQLite con Raft para consenso distribuido. Para casos de 3+ nodos con failover automático y replicación estricta, rqlite es más robusto que LiteFS aunque más complejo de operar.

Casos reales

  • Tailscale: coordination server en SQLite + Litestream.

  • Expensify: Bedrock (wrapper distribuido) sobre SQLite, millones de usuarios.

  • PocketBase: BaaS completo basado en SQLite.

  • Linear y Notion: SQLite en cliente vía WASM.

Patrón común: un nodo de aplicación + SQLite local + Litestream offsite.

Cuándo SQLite gana vs Postgres

SQLite es la mejor elección cuando:

  • App con 1 proceso (VM de instancia única, Lambda monolítico).

  • Queries simples a moderadas, sin cross-server joins.

  • Volumen de datos hasta ~100 GB cómodamente, ~1 TB con disciplina.

  • Latencia cero a la BD (está en el mismo proceso).

  • Despliegue simple sin servidor de BD separado.

  • Backup como cp del archivo, la operación más simple posible.

Cuándo SQLite no basta

Con honestidad:

  • Múltiples procesos escribiendo: la serialización de writes es el bottleneck.

  • Múltiples instancias de app: no puedes shard SQLite entre servers sin complejidad.

  • Queries analíticos masivos: usa DuckDB (SQLite es OLTP, DuckDB es OLAP). Ver también SQLite vs DuckDB: cuándo usar cada uno.

  • Extensiones Postgres-específicas (pgvector serio, PostGIS).

  • Roles y permisos a nivel BD: SQLite no los tiene.

Para ~1000 writes/s sostenidos, SQLite maneja. Por encima de ~10k writes/s sostenidos, considera Postgres.

Conclusión

SQLite es una opción seria para aplicaciones productivas single-instance. Con WAL mode, pragmas optimizados, Litestream y opcionalmente LiteFS o rqlite para HA, cubre casos en los que la respuesta por defecto sería PostgreSQL. La ventaja operativa es enorme: sin servidor de BD, sin red, sin permisos, sin backup complejo. Para la enorme mayoría de apps pequeñas a medianas, empezar con SQLite y crecer hacia Postgres solo si realmente hace falta es la estrategia pragmática correcta.

Este artículo también está disponible en inglés: SQLite in production: not just for mobile

Fuentes:

  1. SQLite.org, Write-Ahead Logging (WAL)[4]
  2. Litestream, replicación continua a S3 para SQLite[1]
  3. Fly.io, LiteFS: replicación multi-nodo de SQLite[2]
  4. rqlite, SQLite distribuida con consenso Raft[3]

Preguntas frecuentes

¿Hasta qué volumen de datos y tráfico aguanta SQLite como base de datos principal?

En hardware modesto maneja miles de escrituras por segundo y cientos de miles de lecturas por segundo. En volumen, funciona cómodamente hasta ~100 GB y llega a ~1 TB con disciplina. Eso exige que la app sea de un solo proceso (VM de instancia única o Lambda monolítico), con queries simples a moderadas y sin cross-server joins. La ventaja es la latencia cero, porque la base de datos vive en el mismo proceso, y un despliegue sin servidor de BD separado.

¿Cómo hago backup y replicación de SQLite en producción sin perder durabilidad?

Litestream hace streaming del WAL a S3 o cualquier almacenamiento compatible en near-realtime, con un litestream.yml que declara la ruta de la base de datos, el bucket y la región. La restauración se hace con litestream restore; así la durabilidad queda casi comparable a Postgres sin servidor de BD. Si necesitas más de un nodo, LiteFS (de Fly.io) replica con FUSE, escrituras al primary y lecturas en cualquier réplica. rqlite usa consenso Raft para clusters de 3+ nodos, más robusto pero más complejo de operar.

¿Cuándo SQLite no es suficiente y conviene pasar a PostgreSQL?

Cuando más de un proceso escribe a la vez, porque la serialización de writes se convierte en el cuello de botella. Cuando hay múltiples instancias de la app, ya que no puedes shardear SQLite entre servidores sin complejidad. Cuando necesitas extensiones específicas de Postgres como pgvector serio o PostGIS, o cuando requieres roles y permisos a nivel de base de datos, que SQLite no tiene. Para queries analíticos masivos la alternativa no es Postgres sino DuckDB: SQLite es OLTP y DuckDB es OLAP.

Fuentes

  1. Litestream
  2. LiteFS
  3. rqlite
  4. SQLite.org, Write-Ahead Logging (WAL)