- Se demostró experimentalmente que la estructura de escritor único y la naturaleza embebida de SQLite pueden, de hecho, mejorar la escalabilidad y el rendimiento
- En las mismas condiciones, Postgres cayó hasta 348 TPS con latencia de red, mientras que SQLite, al eliminar la red, alcanzó 44,096 TPS
- Aprovechando el modelo de escritor único con procesamiento por lotes y transacciones granulares basadas en SAVEPOINT, registró hasta 186,157 TPS y 102,545 TPS en una configuración estable
- La ley de Amdahl explica el cuello de botella de las bases de datos basadas en red, y SQLite mantiene una alta eficiencia al evitarlo
- Estos resultados destacan la viabilidad de usar SQLite en entornos locales y la importancia de eliminar los cuellos de botella de red
Estructura de SQLite y entorno del experimento
- SQLite no tiene MVCC y solo permite un escritor, pero esta estructura, de hecho, hace posible una alta escalabilidad
- Como base de datos embebida, no tiene sobrecarga de red
- El benchmark se realizó en una MacBook Pro (2021) con Apple M1 Pro y 16 GB de memoria
- El experimento no buscó una optimización perfecta, sino mostrar que es posible lograr un alto rendimiento de escritura incluso en condiciones comunes
Definición de TPS y ejemplo de transacción
- TPS no significa simplemente velocidad de escritura, sino transacciones interactivas (Interactive Transaction)
- Ejemplo: en una transferencia entre cuentas, varias consultas y código de aplicación se ejecutan dentro de una sola transacción
- Las transacciones pueden revertir el estado cuando ocurre un error, por lo que cumplen un papel clave para mantener la consistencia
Configuración del benchmark
- Se usaron virtual threads basados en Clojure para simular solicitudes concurrentes a gran escala
- Postgres se configuró con un pool de conexiones basado en HikariCP, mientras que SQLite usó un solo escritor y tantas conexiones de lectura como núcleos
- Ambas bases de datos utilizaron una tabla
account simple con los campos id, balance, e insertaron mil millones de filas
- La actividad de los usuarios sigue una distribución de ley de potencia (0.9995), con aproximadamente 100 mil usuarios activos
Rendimiento de la base de datos en red (Postgres)
- En el mismo servidor, Postgres alcanzó 13,756 TPS
- Al agregar 5 ms de latencia de red, cayó a 1,214 TPS, y con 10 ms a 702 TPS
- Tras aplicar aislamiento serializable, bajó a 660 TPS, y con consultas adicionales descendió a 348 TPS
- Esto muestra, de acuerdo con la ley de Amdahl, que el cuello de botella de red limita el rendimiento total
- Cuando aumenta la latencia de red, la contención de locks de transacción se intensifica y deja de escalar
Ventajas embebidas de SQLite
- Al eliminar la red, SQLite alcanzó 44,096 TPS
- Al desaparecer el cuello de botella de red, se minimiza el impacto de la ley de Amdahl
- Al aplicar procesamiento por lotes (batch processing) aprovechando su estructura de escritor único, subió hasta 186,157 TPS
- El ajuste dinámico del tamaño del lote optimiza automáticamente la latencia (latency) y el rendimiento (throughput)
Transacciones granulares mediante SAVEPOINT
- Para evitar que falle una transacción individual dentro de un lote, se aplicaron transacciones anidadas con SAVEPOINT
- Si ocurre un fallo, solo se revierte esa transacción y se mantiene el lote completo
- Incluso con este enfoque, se mantuvieron 121,922 TPS
Prueba de carga mixta de lectura/escritura
- El 75% de las solicitudes fueron lecturas y el 25% escrituras
- Se utilizó un pool de hilos de lectura separado para aislar las solicitudes de lectura y evitar que interfieran con las escrituras
- Como resultado, se alcanzaron 102,545 TPS
Resumen comparativo de rendimiento
| Condición |
Postgres |
SQLite |
| Sin red |
13,756 |
44,096 |
| 5 ms de latencia |
1,214 |
n/a |
| 10 ms de latencia |
702 |
n/a |
| 10 ms + serializable |
660 |
n/a |
| Procesamiento por lotes |
n/a |
186,157 |
| Lotes + SAVEPOINT |
n/a |
121,922 |
| Lotes + SAVEPOINT + lecturas |
n/a |
102,545 |
Conclusión
- SQLite logra TPS mucho más altos que las bases de datos basadas en red gracias a su modelo de escritor único y estructura embebida
- Maximiza la eficiencia al evitar los límites de cuello de botella de red que plantea la ley de Amdahl
- Todo el código está publicado en GitHub, y junto con ello se presentan materiales relacionados sobre la ley de Amdahl, ley de potencia y casos de escalado de SQLite
- SQLite es una opción muy efectiva para procesamiento de transacciones de alto rendimiento en entornos locales
2 comentarios
Entonces, si se va a usar solo en un entorno local sin recurrir a un servidor externo, la idea es que no hace falta pagar el impuesto de la red, ¿no? (VFS vs Socket)
Opiniones en Hacker News
Estoy construyendo un servidor ORM/CRUD híbrido basado en protobuf sobre SQLite
El código y la explicación están en GitHub - accretional/collector
Permite 5–15 ms de downtime durante respaldos en tiempo real, encolar cientos de solicitudes de lectura/escritura, latencia total de CRUD de alrededor de 1 ms, e incluso respaldo en streaming basado en WAL
Antes solo usaba Postgres y Spanner, pero si a Collector le agregan particionado, creo que no volvería a usar Postgres
La desventaja es que todos los datos y operaciones tienen que caber en una sola máquina
Si usas una instancia u-24tb1.112xlarge de AWS (448 vcore, 24 TB de RAM, 64 TB de EBS), tienes bastante margen
El artículo enfatiza la eficiencia de SQLite, pero siento que no queda claro contra qué se está comparando
Eso pasa porque parte de una arquitectura con servidores separados y luego mide el rendimiento de una BD embebida local
En las mismas condiciones, un Postgres local bien afinado también podría lograr un rendimiento parecido
Limitar Postgres a 8 conexiones podría ser el cuello de botella
Estaría bien publicar también el uso de CPU e hilos, y volver a probar con un pool de conexiones más grande
Si lo subes a 64 conexiones, el throughput podría aumentar 8 veces. Hay que escalar la configuración del cliente hasta llegar al límite
La clave es reconocer cuándo la latencia de red es el cuello de botella
En muchas cargas de trabajo, una BD local común supera a una BD remota excelente
Lo importante no es “qué BD es mejor”, sino “si realmente necesitas cruzar el límite de la red”
Las BD en red tienen la ventaja de facilitar el redespliegue de la app
Puedes levantar una instancia nueva y apagar la anterior, logrando un despliegue casi sin downtime
Si SQLite está en la misma instancia, al reemplazarla tienes que volver a levantar la BD, así que se vuelve más complejo. Me pregunto si han sufrido este tipo de problema en producción
Durante una migración puede haber downtime. Gracias a Litestream, ahora la replicación y los respaldos son mucho más fáciles
El autor configuró
PRAGMA synchronous="normal", lo que significa que no hace fsync en cada operaciónPara una comparación justa debería usar
"full""normal"también está bien. Si se corta la energía, se pierde durabilidad, pero la consistencia de la transacción se mantieneMe pregunto cómo sería una configuración de HA (alta disponibilidad) con SQLite
Como mínimo debería poder hacerse failover automático
Ahora mismo estoy dudando entre Postgres y SQLite (incluyendo litestream).
Mi app tolera algo de downtime, así que escalar verticalmente en una sola máquina me resulta más simple y barato
En Marmot GitHub acaban de agregar un mecanismo de replicación basado en gossip
Me pregunto si hay casos reales de SQLite llevado al límite en producción
Me gustaría saber más o menos cuál es el límite de usuarios para SQLite vs Postgres en una webapp o entorno de e-commerce típico
Con las actualizaciones recientes, SQLite permite lecturas concurrentes pero sigue aceptando una sola escritura
Quisiera saber en qué casos eso se vuelve un problema y si, pensando en escalar, conviene más empezar directamente con Postgres