SQLite y DuckDB: cuándo cada una es la opción correcta

Sala de servidores con luces azules y racks alineados

SQLite y DuckDB comparten algo llamativo: las dos son bases de datos embebidas — una librería que vive en tu proceso, sin servidor separado. Y sin embargo resuelven problemas distintos. SQLite es el rey de transacciones pequeñas y persistencia local; DuckDB es el rey del análisis columnar rápido sin infra. Elegir mal entre ellas es un error con coste.

Este artículo es una comparación honesta, con criterios para decidir y escenarios donde cada una gana — y cuándo se pueden (deben) usar juntas.

El punto en común: embedded

Las dos descartan el modelo cliente-servidor. Tu aplicación importa la librería y ejecuta consultas SQL sobre un archivo. No hay systemd, ni puertos, ni usuarios, ni replicación como aspecto central. Esto reduce brutalmente la complejidad operativa.

  • SQLite: un archivo .db (más algunos WAL temporales). Dumps, copias, migraciones son cp.
  • DuckDB: un archivo .duckdb (o en memoria, o Parquet directamente).

Este patrón embedded es perfecto para: apps móviles, desktop, servicios de una sola instancia, notebooks analíticos, pipelines ETL locales. Es el anti-patrón para: sistemas con decenas de servicios concurrentes contra la misma BD.

Diferencia estructural: filas vs columnas

La separación no es cosmética. Es de arquitectura:

  • SQLite guarda datos por filas. Cada fila es un bloque contiguo. Perfecto para: SELECT * FROM users WHERE id = 42 — leer una fila entera rápido.
  • DuckDB guarda datos por columnas. Cada columna es un bloque contiguo. Perfecto para: SELECT AVG(amount) FROM transactions WHERE year=2023 — leer pocos campos de muchas filas.

OLTP vs OLAP, en formato embedded.

Cuándo elegir SQLite

Escenarios donde SQLite es claramente correcto:

  • App móvil con BD local: iOS, Android, desktop. SQLite es el estándar de facto.
  • Configuración y persistencia de una app: Firefox, Chrome, decenas de apps lo usan.
  • Servicio web con un solo nodo y lectura/escritura mixta moderada.
  • Prototipo o MVP antes de migrar a PostgreSQL.
  • Backup durable y transaccional local con ACID completo.
  • WASM en el navegador via sql.js o similar.

SQLite hace bien lo que debe hacer: transacciones pequeñas, alta integridad, baja latencia por operación.

Cuándo elegir DuckDB

Escenarios donde DuckDB brilla:

  • Análisis de logs: millones de filas, queries agregados.
  • Consultar archivos Parquet/CSV directamente sin importar. SELECT * FROM 'datos.parquet' funciona.
  • Reemplazar pandas en pipelines medianos (memoria eficiente, SQL nativo).
  • Análisis ad-hoc en notebooks: Jupyter + DuckDB es potente.
  • Consolidar bases OLTP para reports sin tocar producción (CDC + DuckDB).
  • Procesamiento vectorizado sobre datos tabulares sin infraestructura distribuida.

DuckDB es lo que Pandas-con-SQL-nativo debería haber sido.

Benchmarks orientativos

Con los debidos asteriscos (benchmarks de BD son mentirosos sin contexto), órdenes de magnitud:

  • Insert individual de 1M filas: SQLite ~20s (batched). DuckDB: tarda más — no es su caso.
  • Aggregate sobre 1M filas con GROUP BY: SQLite ~15s. DuckDB ~1s.
  • Scan de 100M filas columnares con filtro selectivo: SQLite memoria lenta. DuckDB segundos.
  • Transacciones concurrentes: SQLite (con WAL) ~1000 tx/s. DuckDB no está diseñado para esto.

Cada uno es 10x-100x más rápido que el otro en el terreno correcto.

Integración en Python

Ambas son trivial de usar desde Python:

# SQLite
import sqlite3
con = sqlite3.connect('app.db')
con.execute("CREATE TABLE users (id INT, name TEXT)")

# DuckDB
import duckdb
con = duckdb.connect('analytics.duckdb')
# Consultar Parquet directamente
df = con.execute("SELECT * FROM 'events.parquet' WHERE type='signup'").df()

DuckDB tiene integración Python especialmente pulida: DataFrames de pandas y Polars se consumen/producen sin overhead serio.

Usarlas juntas

Combinarlas es patrón común:

  • OLTP en SQLite, OLAP en DuckDB. La app escribe en SQLite; un job periódico vuelca cambios a DuckDB/Parquet para análisis.
  • DuckDB lee SQLite directamente con el extension sqlite_scanner. SELECT * FROM sqlite_scan('app.db', 'users'). Sin ETL.
  • Archivar datos antiguos de SQLite a Parquet. DuckDB mantiene queries sobre históricos sin inflar la BD de producción.

El patrón “SQLite para caliente + DuckDB para frío” es muy productivo.

Limitaciones reales

Seamos honestos:

SQLite no es para:

  • Escrituras concurrentes altas — incluso con WAL, el write lock es global.
  • Múltiples procesos escribiendo — hay race conditions sutiles.
  • BDs gigantes (>100GB) — técnicamente posible, operacionalmente incómodo.
  • Replicación built-in — herramientas como Litestream ayudan.

DuckDB no es para:

  • OLTP real — no hay locking fino, concurrencia pensada para análisis.
  • Latencia sub-milisegundo — es rápido pero optimizado para queries analíticos.
  • BDs con muchas writes concurrentes — falla al caso.

Intentar que cada una haga lo del otro es hacerse daño.

El veredicto

Cómo decidir en 10 segundos:

  • ¿Transacciones cortas, alta integridad, un proceso activo? SQLite.
  • ¿Queries analíticos sobre grandes volúmenes, pocas writes? DuckDB.
  • ¿App móvil / desktop / servicio simple? SQLite.
  • ¿Notebooks, ETL local, reemplazar pandas? DuckDB.
  • ¿Ambas cosas? Usarlas juntas.

Cualquier duda honesta se resuelve mirando la query principal: si es un WHERE id=? casi siempre, SQLite. Si es un GROUP BY sobre mucha fila, DuckDB.

Conclusión

SQLite y DuckDB son herramientas complementarias, no competidoras. Ambas representan la filosofía “embedded importa” aplicada a diferentes problemas. Para OLTP local, SQLite es imbatible por simplicidad y robustez. Para OLAP sin infraestructura, DuckDB ha hecho obsoletas muchas soluciones complejas. Conocer las dos y aplicar la correcta es una ventaja técnica real — y combinarlas cuando tiene sentido multiplica el valor de ambas.

Síguenos en jacar.es para más sobre bases de datos, arquitecturas de datos y herramientas embebidas.

Entradas relacionadas