2 puntos por GN⁺ 2024-12-17 | Aún no hay comentarios. | Compartir por WhatsApp
  • En entornos serverless y edge, ejecutar varias instancias de SQLite juntas hace que la espera de I/O síncrona aumente la latencia de cola; investigadores de Helsinki y Cambridge probaron una forma de reducirla con I/O asíncrona y almacenamiento desagregado
  • io_uring de Linux permite que la aplicación siga haciendo otras tareas mientras hay solicitudes de I/O en curso mediante una cola de envío y una cola de finalización, lo que sirve como base para reducir el bloqueo de hilos
  • En SQLite, si durante la ejecución de sqlite3_step() una página del B-Tree necesaria no está en caché, se lee el disco con I/O síncrona como read() de POSIX, por lo que el hilo se detiene hasta que termina la I/O
  • En lugar de cambiar solo llamadas POSIX, los investigadores adaptaron la VM y el BTree al modelo de ejecución asíncrona en Limbo, un proyecto de reescritura basado en Rust
  • En los benchmarks, la latencia de cola p999 se redujo hasta 100 veces, pero p90 y p99 fueron casi iguales a SQLite, y la evaluación con múltiples readers/writers queda como trabajo futuro

Investigación para hacer SQLite más rápido

  • Investigadores de la University of Helsinki y de Cambridge abordan la aplicación de I/O asíncrona y almacenamiento desagregado a SQLite en “Serverless Runtime / Database Co-Design With Asynchronous I/O”
  • Este paper sirve de base para Limbo, un proyecto de reescritura de SQLite en Rust
  • Como es un paper de workshop, es breve y está enfocado en serverless y edge computing
  • La idea central es que, aunque SQLite ya es rápido, la latencia de cola en entornos multitenant puede reducirse aún más si se cambia el modelo de ejecución

Cómo io_uring reduce la espera de I/O

  • io_uring del kernel de Linux ofrece una interfaz de I/O asíncrona
  • Su nombre viene del ring buffer compartido entre espacio de usuario y espacio del kernel, lo que reduce la sobrecarga de copiar buffers entre ambos espacios
  • La aplicación puede enviar solicitudes de I/O y seguir haciendo otras tareas hasta que el OS notifique su finalización
  • El flujo de funcionamiento es el siguiente
    • La llamada al sistema io_uring_setup() configura dos regiones de memoria: la cola de envío y la cola de finalización
    • La aplicación coloca solicitudes de I/O en la cola de envío y usa io_uring_enter() para avisarle al OS que empiece a procesarlas
    • A diferencia de read() y write(), no bloquea el hilo y devuelve el control al espacio de usuario
    • La aplicación puede hacer otro trabajo y luego consultar periódicamente la cola de finalización para confirmar que la I/O terminó

El cuello de botella de I/O síncrona en la ejecución de consultas de SQLite

  • Una aplicación SQLite abre el archivo de base de datos con sqlite3_open(), proceso durante el cual se invoca I/O de bajo nivel del OS, como open de POSIX
  • sqlite3_prepare() convierte sentencias SQL como SELECT e INSERT en una secuencia de instrucciones de bytecode
  • sqlite3_step() ejecuta las instrucciones de bytecode hasta producir una fila para la consulta o hasta que termine la ejecución
    • Si hay una fila para leer, devuelve SQLITE_ROW
    • Si la sentencia termina, devuelve SQLITE_DONE
  • Durante la ejecución se invoca el pager del backend y se recorre el B-Tree que representa tablas y filas
  • Si una página necesaria del B-Tree no está en la caché de páginas de SQLite, ocurre un acceso a disco
    • SQLite lee el contenido de la página del disco a memoria con I/O síncrona como read de POSIX
    • Durante ese tiempo, sqlite3_step() bloquea el hilo del kernel
    • Para hacer trabajo concurrente mientras se espera la I/O, la aplicación necesita usar más hilos

Por qué se quiere embebeder SQL en serverless y edge

  • Si el cómputo serverless se ejecuta en el edge y la base de datos está en la nube, aparece el costo de ida y vuelta de red entre la función serverless y la nube
  • Una opción es colocar también los datos en el edge, pero se propone como mejor enfoque embebeder la base de datos dentro del runtime de edge
  • Cloudflare Workers ya logra algo de este estilo, pero expone una interfaz KV
  • KV no encaja bien en todos los dominios de problema
    • Mapear datos tabulares al modelo KV empeora la experiencia de desarrollo
    • También introduce costo de serialización y deserialización
  • SQL puede ser una mejor opción, y SQLite, al ser una base de datos embebida, puede incluirse directamente en el runtime serverless

Por qué no es fácil cambiar SQLite simplemente a io_uring

  • SQLite usa I/O síncrona basada en read() y write() tradicionales de POSIX
  • Aunque en aplicaciones pequeñas esto quizá no sea un gran problema, puede convertirse en un cuello de botella al ejecutar cientos de bases de datos SQLite en un mismo servidor
  • En entornos donde hay que maximizar la utilización de recursos del servidor, la I/O síncrona actúa como una limitación
  • SQLite también tiene problemas de concurrencia y multitenancy
    • Como la I/O es síncrona y bloqueante, las aplicaciones en la misma máquina compiten por los recursos
    • Como resultado, aumenta la latencia
  • No es fácil reemplazar las llamadas de I/O de POSIX por io_uring de forma directa
    • Las aplicaciones que usan I/O bloqueante deben rediseñarse para ajustarse al modelo de I/O asíncrona de io_uring
    • La librería SQLite debe poder devolver el control a la aplicación mientras la I/O sigue en curso
  • En vez de cambiar solo algunas llamadas de SQLite, los investigadores eligieron reescribir SQLite en Rust y usar io_uring

El modelo de ejecución asíncrona de Limbo

  • Limbo es un proyecto que reescribe SQLite en Rust, y modifica los componentes VM y BTree para soportar I/O asíncrona
  • Las instrucciones de bytecode síncronas se reemplazan por sus equivalentes asíncronos
  • Por ejemplo, la instrucción Next avanza el cursor y, si hace falta, trae la página siguiente
    • En la versión síncrona anterior, si ocurría I/O de disco, se bloqueaba hasta leer la página y devolverla al llamador
    • En la versión asíncrona, NextAsync se envía y retorna de inmediato
    • El llamador luego puede bloquearse o realizar otras tareas
  • La I/O asíncrona elimina bloqueos y mejora la concurrencia
  • Para aumentar aún más la utilización de recursos, también se propone almacenamiento desagregado, separando el motor de consultas y el motor de almacenamiento
  • Como referencia relacionada, también se enlaza Disaggregated Storage - a brief introduction

Resultados de benchmarks y preguntas pendientes

  • Los benchmarks simulan un runtime serverless multitenant
  • Cada tenant tiene su propia base de datos embebida
  • La cantidad de tenants varía de 1 a 100 en incrementos de 10
  • En SQLite se usa un hilo separado por tenant, y las consultas se ejecutan en cada hilo para medir el desempeño
  • La consulta ejecutada es SELECT * FROM users LIMIT 100, repetida 1000 veces
  • Limbo realiza el mismo experimento, pero usando corrutinas de Rust
  • Como resultado, la latencia de cola en p999 se reduce hasta 100 veces
  • La latencia de consultas de SQLite no se degradó gradualmente con el aumento del número de hilos
  • El trabajo sigue en curso y el paper deja varias preguntas abiertas
    • En Future Work se contemplan benchmarks adicionales con múltiples readers y writers
    • Las ventajas se notan sobre todo después de p999
    • El rendimiento en p90 y p99 es casi igual al de SQLite
  • El código de Limbo está disponible como open source
  • Limbo es actualmente un proyecto oficial de Turso, y también se publicó un texto de presentación

Aún no hay comentarios.

Aún no hay comentarios.