SQLite y DuckDB: cuándo cada una es la opción correcta
Índice de contenidos
- El punto en común: embedded
- Diferencia estructural: filas vs columnas
- Cuándo elegir SQLite
- Cuándo elegir DuckDB
- Python: las dos en la misma sesión
- Usarlas juntas
- Limitaciones reales
- Benchmarks orientativos
- El veredicto rápido
- Conclusión
- Preguntas frecuentes
- ¿Puedo consultar mi base SQLite desde DuckDB sin montar un ETL?
- ¿Cuánto más rápida es DuckDB que SQLite en consultas analíticas?
- ¿Sirve SQLite si varios procesos escriben a la vez en la misma base?
- Fuentes
SQLite y DuckDB son bases de datos embedded: ninguna requiere servidor, las dos trabajan sobre un archivo local. Pero su arquitectura es distinta. SQLite organiza datos por filas y destaca en transacciones cortas (OLTP); DuckDB organiza por columnas y brilla en análisis sobre grandes volúmenes (OLAP). Elegir la correcta, o combinarlas, es una ventaja técnica real.
SQLite[1] y DuckDB[2] comparten algo llamativo: las dos son bases de datos embebidas: una biblioteca 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 infraestructura. Elegir mal entre ellas tiene coste real, y combinarlas cuando tiene sentido multiplica el valor de ambas.
El punto en común: embedded
Las dos descartan el modelo cliente-servidor. Tu aplicación importa la biblioteca y ejecuta SQL sobre un archivo. No hay systemd, ni puertos, ni usuarios, ni replicación como aspecto central. Esto reduce drásticamente la complejidad operativa:
-
SQLite: un archivo
.db(más algunos WAL temporales). Dumps, copias y migraciones soncp. -
DuckDB: un archivo
.duckdb(o en memoria, o directamente sobre Parquet).
Este patrón embedded es perfecto para apps móviles, desktop, servicios de una sola instancia, notebooks analíticos y pipelines ETL locales. Es el anti-patrón para sistemas con decenas de servicios concurrentes contra la misma base de datos.
Diferencia estructural: filas vs columnas
La separación no es cosmética. Es arquitectural:
-
SQLite guarda datos por filas. Cada fila es un bloque contiguo. Perfecto para
SELECT * FROM users WHERE id = 42: leer una fila entera es rápido. -
DuckDB guarda datos por columnas. Cada columna es un bloque contiguo. Perfecto para
SELECT AVG(amount) FROM transactions WHERE year=2023: recorre 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 y desktop. SQLite es el estándar de facto en estas plataformas.
-
Configuración y persistencia de una app: Firefox, Chrome y decenas de aplicaciones lo usan así.
-
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 vía sql.js[3] o similares.
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 con agregados.
-
Consultar archivos Parquet/CSV directamente sin importar:
SELECT * FROM 'datos.parquet'funciona de serie. -
Reemplazar pandas en pipelines medianos, más eficiente en memoria y con SQL nativo.
-
Análisis ad-hoc en notebooks: Jupyter + DuckDB es una combinación muy potente.
-
Consolidar bases OLTP para reports sin tocar producción (CDC + DuckDB).
-
Procesamiento vectorizado sobre datos tabulares sin infraestructura distribuida.
DuckDB es, en la práctica, lo que pandas-con-SQL-nativo debería haber sido.
Python: las dos en la misma sesión
Ambas son triviales desde Python:
# SQLite — OLTP, persistencia transaccional
import sqlite3
con = sqlite3.connect('app.db')
con.execute("CREATE TABLE users (id INT, name TEXT)")
# DuckDB — OLAP, análisis rápido
import duckdb
con = duckdb.connect('analytics.duckdb')
# Consultar Parquet directamente, sin importar
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 y producen sin overhead serio. Esta característica encaja bien con pipelines de datos que también usan Kafka para streaming de eventos.
Usarlas juntas
Combinarlas es un patrón muy productivo:
-
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 la extensión
sqlite_scanner:SELECT * FROM sqlite_scan('app.db', 'users'). Sin ETL manual. -
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" reduce complejidad mientras mantiene cada motor en su terreno óptimo.
Limitaciones reales
SQLite no es para:
-
Escrituras concurrentes altas; incluso con WAL, el write lock es global.
-
Múltiples procesos escribiendo simultáneamente (pueden aparecer race conditions sutiles).
-
Bases de datos muy grandes (>100 GB): técnicamente posible pero operacionalmente incómodo.
-
Replicación integrada: herramientas como Litestream[4] cubren este hueco.
DuckDB no es para:
-
OLTP real: sin locking fino, la concurrencia está diseñada para análisis.
-
Latencia sub-milisegundo (es rápido pero optimizado para queries analíticos).
-
Cargas con muchas escrituras concurrentes (ese no es su caso de uso).
Intentar que cada una haga lo del otro es hacerse daño innecesariamente.
Benchmarks orientativos
Con los debidos asteriscos (los benchmarks de BD son mentirosos sin contexto), órdenes de magnitud:
-
Aggregate sobre 1 millón de filas con GROUP BY: SQLite tarda unos 15 segundos. DuckDB lo hace en 1 segundo.
-
Scan de 100 millones de filas con filtro selectivo: SQLite va lento. DuckDB lo termina en segundos.
-
Transacciones concurrentes (con WAL): SQLite alcanza unas 1.000 transacciones por segundo. DuckDB no está pensado para esto.
Cada una es 10x o 100x más rápida que la otra en el terreno correcto.
El veredicto rápido
Cómo decidir en diez segundos:
-
¿Transacciones cortas, alta integridad, un proceso activo? SQLite.
-
¿Queries analíticos sobre grandes volúmenes, pocas escrituras? DuckDB.
-
¿App móvil, desktop o servicio simple? SQLite.
-
¿Notebooks, ETL local, reemplazar pandas? DuckDB.
-
¿Ambas cosas? Úsalas juntas.
Cualquier duda honesta se resuelve mirando la query principal: si es casi siempre un WHERE id=?, SQLite. Si es un GROUP BY sobre mucha fila, DuckDB. Para gestión de datos más amplia, ver también las bases de datos vectoriales cuando la búsqueda semántica entra en la ecuación.
Conclusión
SQLite y DuckDB son herramientas complementarias, no competidoras. Ambas representan la filosofía "embedded importa" aplicada a problemas diferentes. 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. Combinarlas cuando tiene sentido multiplica el valor de ambas.
Fuentes:
- SQLite — When to Use SQLite[5]
- DuckDB — Why DuckDB[6]
- Litestream — Streaming Replication for SQLite[4]
- sql.js — SQLite compiled to WebAssembly[3]
Preguntas frecuentes
¿Puedo consultar mi base SQLite desde DuckDB sin montar un ETL?
Sí. DuckDB lee SQLite directamente con la extensión sqlite_scanner: SELECT * FROM sqlite_scan('app.db', 'users'), sin ETL manual. Es la base del patrón «SQLite para caliente + DuckDB para frío»: la app escribe transacciones en SQLite y un job periódico vuelca los cambios a DuckDB o Parquet para análisis, o archiva los datos antiguos a Parquet para que DuckDB consulte históricos sin inflar la base de producción.
¿Cuánto más rápida es DuckDB que SQLite en consultas analíticas?
En órdenes de magnitud, y con los debidos asteriscos porque los benchmarks de bases de datos engañan sin contexto: un agregado con GROUP BY sobre 1 millón de filas tarda unos 15 segundos en SQLite y 1 segundo en DuckDB, y un scan de 100 millones de filas con filtro selectivo lo termina DuckDB en segundos. La tornas se invierten en OLTP: con WAL, SQLite alcanza unas 1.000 transacciones por segundo, algo para lo que DuckDB no está pensado.
¿Sirve SQLite si varios procesos escriben a la vez en la misma base?
No es su terreno. Incluso con WAL el write lock es global, así que las escrituras concurrentes altas se serializan, y con múltiples procesos escribiendo simultáneamente pueden aparecer race conditions sutiles. Tampoco es cómodo operacionalmente por encima de 100 GB ni trae replicación integrada, hueco que cubren herramientas como Litestream. Para un servicio web de un solo nodo con lectura/escritura mixta moderada sigue siendo la opción correcta.