lobste.rs cambia de MariaDB a SQLite
(lobste.rs)- lobste.rs cambió de MariaDB a SQLite y, después del pico de tráfico del lunes, siguió operando con estabilidad; además confirmó una menor utilización de CPU y memoria, junto con una sensación de respuesta más rápida
- Al apagar el VPS dedicado a MariaDB, el costo del VPS se reduce a la mitad, y también se da por concluida la tarea de migración iniciada en 2019
- En el primer despliegue se hizo rollback porque, aun con tráfico de solo lectura, todos los CPU llegaron al 100%; luego de corregir dos full table scans y un problema N+1 en tablas grandes, el redeploy fue exitoso
- Mediante funciones definidas por el usuario en SQLite se implementaron
regexp,ifystddev, pero también hubo que resolver limitaciones de compatibilidad como la falta de soporte para unsigned bigint yNOCASE, que solo maneja ASCII - Para cambiar la base de datos subyacente sin acceder a la base de datos de producción, se necesita datos de prueba de tamaño realista, una suite de pruebas, checklists de despliegue y rollback, y colaboración estrecha
De la discusión de 2019 al cambio a SQLite
- La discusión sobre la migración comenzó en 2019 con el issue #539
- En ese momento se propuso MySQL como alternativa considerando la compatibilidad con MariaDB, pero ya estaba en marcha una discusión sobre migrar a PostgreSQL
- En 2025, Rahul mencionó la adquisición de MariaDB por K1, lo que llevó a continuar la discusión sobre los detalles de una migración a PostgreSQL
- En febrero, Rahul preguntó si lobsters podía ejecutarse sobre SQLite, y desde junio de 2025 la participación en el proyecto se volvió más activa
- El primer PR de agosto de 2025 quedó cerrado por GitHub como PR antiguo después de que el trabajo se interrumpiera, y ya no pudo reabrirse
- El segundo PR incluyó pruebas de rendimiento, un script propio de migración entre DB y debugging relacionado con la integridad de los datos
- Como ninguno de los scripts existentes para convertir MariaDB/MySQL→SQLite cumplía con los requisitos, se escribió uno aparte
Fallo del primer despliegue y corrección de cuellos de botella de rendimiento
- El primer despliegue del 21 de febrero se realizó preparando una checklist de despliegue, pero justo después del despliegue, incluso con tráfico en modo de solo lectura, todos los CPU llegaron al 100%
- Como no se logró identificar la causa, se hizo rollback
- Al no poder acceder a la DB de producción, era difícil reproducir de antemano problemas reales de rendimiento
- Dos días después del fallo se abrió el tercer y último PR, donde se corrigieron problemas relacionados con la búsqueda y se añadió un script de generación masiva de datos para crear en local un dataset equivalente a la mitad de los datos reales de lobsters
- Generar esos datos tomó una semana
- El cuello de botella del primer despliegue provenía de dos queries que provocaban full table scans en tablas grandes y de un problema N+1 separado
- Se aplicaron cambio 1, cambio 2 y cambio 3 para resolverlo
- También se agregó la mañana del despliegue un slow query log para poder rastrear problemas adicionales de rendimiento
Resultado del redeploy y reducción de costos operativos
- En el segundo despliegue del 11 de julio se prepararon nuevamente las checklists de despliegue y rollback, y después del cambio el sitio siguió funcionando con normalidad, con uso estable de CPU y memoria
- Mientras se monitoreaban las métricas del sitio y el IRC, los problemas reportados se atendieron de inmediato con arreglo 1 y arreglo 2
- No hubo problemas ni siquiera durante el pico de tráfico del lunes, y se confirmó una menor utilización de CPU y memoria, además de una respuesta percibida como más rápida
- Al apagar el VPS de MariaDB, el costo del VPS se reduce a la mitad, y el issue de migración también queda cerrado
Limitaciones de SQLite frente a MariaDB
- Aprovechando el soporte de funciones definidas por el usuario (UDF) del gem de SQLite, se implementaron en SQLite
regexp,ifystddev, que no existían de forma nativa- Esto evitó tener que añadir demasiados workarounds para las migraciones SQL
- SQLite no soporta el tipo unsigned bigint que se usaba para algunos ID en MariaDB, así que se cambió a bigint
- Las reglas de ordenamiento también son más limitadas que en MariaDB
- En MariaDB se usaba
utf8mb4_general_ci, pero en SQLite se aplicóNOCASE NOCASEno soporta case folding completo para UTF y solo maneja caracteres ASCII
- En MariaDB se usaba
- Para las tablas de búsqueda de texto completo en SQLite, el enfoque recomendado y no el predeterminado, Contentless-Delete Tables, resultó ser el más adecuado
- La configuración PRAGMA por defecto de Rails funcionó correctamente en lobsters
- Como las migraciones de base de datos difieren según la DB, las migraciones existentes se movieron al directorio old_migrations para que
db:migratesiguiera funcionando
- Como las migraciones de base de datos difieren según la DB, las migraciones existentes se movieron al directorio old_migrations para que
Lecciones aprendidas sobre pruebas y colaboración
- En la base de código de lobsters se revisaron el parser de búsqueda y heinous_inline_partials, un hack para acelerar el renderizado
- Gracias a la suite de pruebas, se pudo verificar la exactitud del cambio a SQLite sin necesidad de pruebas manuales masivas
- La buena comunicación entre los participantes fue decisiva para el éxito de la migración
- Cambiar la base de datos subyacente sin acceso a la DB de producción fue extremadamente difícil
- Si se repitiera el trabajo, habría que preparar por adelantado un volumen de datos realista
- Si durante las pruebas se hubiera podido configurar un fallo automático al detectar full table scans, el problema de rendimiento del primer despliegue se habría encontrado antes
- Hacen falta herramientas que permitan crear fácilmente datasets similares a producción sin tener que armarlos manualmente ni esperar una semana
1 comentarios
Comentarios en Lobste.rs
@thomas0 se encargó meticulosamente de la gran migración, @355E3B ayudó con la planificación y la operación, y gracias a los colaboradores relacionados con #539 y a #lobsters, incluso el tiempo en modo de solo lectura se pudo pasar de forma amena
Originalmente en #539 se planeaba usar PostgreSQL, pero la persona que realmente apoyó el trabajo usó SQLite y se quiso evitar una solución más grande y compleja de lo que la demanda esperada requería, así que se eligió SQLite. PostgreSQL suele ser la opción predeterminada habitual, pero tiene la carga de operar, ajustar y mantener un servicio aparte
Sorprendentemente, después de la migración disminuyó tanto el uso de CPU como el de RAM, y hay gráficos detallados en la parte final de #539
Cada vez que veo la configuración predeterminada de SQLite me sorprendo. Sería bueno que hubiera un modo para proyectos nuevos que ofreciera por defecto WAL, claves foráneas y
synchronous=NORMAL, entre otras cosas, y parece que también habría más valores predeterminados por mejorarMe da curiosidad la estrategia de respaldo de la base de datos SQLite. Quisiera saber si usan herramientas como litestream o algún otro método
Me da curiosidad cómo maneja lobste.rs las operaciones de actualización
SQLite ya tiene la función iif, pero también es interesante en sí mismo operar funciones definidas por el usuario en esa misma área
Yo pensaba que SQLite no podía soportar el mismo nivel de concurrencia que PostgreSQL o MySQL, pero al ver el documento general, me pregunto si ahora puede usarse desde varios procesos, aunque habría que entender que lobste.rs debe ejecutarse en un solo servidor
También quisiera saber si los procesos se sincronizan dentro de SQLite mediante memoria compartida o algo como
mmap, permitiendo solo una escritura concurrente pero varias lecturas, y si las escrituras en espera van pasando por la cola por turnosPara esperar escrituras no parece usarse una cola explícita en memoria compartida, sino más bien esperar un momento y reintentar. No está del todo claro si usar SQLite en varias máquinas es completamente imposible, pero como explícitamente no se recomienda el enfoque simple de compartir el archivo por NFS, entenderlo como un solo servidor es una aproximación razonable, y hoy en día una sola máquina puede manejar una escala considerable
busy_timeoutfuncione Como referencia: https://fractaledmind.github.io/2024/04/… y https://kerkour.com/sqlite-for-servers y https://www.sqlite.org/lang_transaction.html#immediate; puedes ver más detalle en la parte deSQLITE_BUSYdel segundo enlace y en el tercer enlaceEs una actualización impresionante, con los detalles muy bien organizados en el PR, el issue y el Gist. Me da curiosidad si este cambio produjo diferencias funcionales, como en la búsqueda del sitio, y cuánto ocupa actualmente en disco el archivo de SQLite
Recuerdo que en algún lugar se dijo que el archivo de SQLite pesaba 3.8 GB