- Lichess cambió la verificación periódica de RAID de su servidor de tablebases Syzygy de 7 piezas a una verificación de integridad al leer, porque la carga de solicitudes impedía completar las revisiones periódicas
- Para migrar una tablebase de 17 TiB sin un downtime prolongado, preparó un servidor nuevo y lo validó reproduciendo logs de solicitudes reales en un entorno con 32 GiB de RAM, 2×201 GiB NVMe y 6×5.46 TiB HDD
- Al reproducir 1 millón de solicitudes registradas en producción con 12 clientes paralelos, se vio que el cuello de botella clave no era la respuesta promedio, sino la latencia de cola percibida por los usuarios
- En la implementación,
pread(2) resultó mejor que mmap para el manejo de errores y la latencia de cola, mientras que hints de acceso aleatorio como POSIX_FADV_RANDOM y MADV_RANDOM fueron mayormente contraproducentes
- En los SSD limitados se colocaron prefijos de tablas y se paralelizaron los probes dentro de cada solicitud para reducir los accesos a discos lentos; luego se verificó que las mejoras de benchmark también se reflejaran en los tiempos de respuesta en producción
Cambio de verificación completa de RAID a verificación al leer
- El servidor de tablebases Syzygy de 7 piezas de Lichess tenía dificultades para terminar las verificaciones periódicas de integridad de RAID mientras recibía muchas solicitudes de tablebase
- La nueva configuración usa dm-integrity sobre LVM para no revisar periódicamente todos los bloques de datos, sino verificar cada bloque cuando se lee
- Para migrar la tablebase de 17 TiB sin varias horas de downtime, se configuró un servidor nuevo por separado
- Antes del cambio real, pudieron ejecutar benchmarks controlados sobre la tablebase completa
- Luego cambiaron al servidor nuevo y retiraron el servidor anterior
Configuración del servidor nuevo
- La RAM se mantuvo igual que antes: 32 GiB
- En almacenamiento se agregaron 2×201 GiB NVMe, que no existían en el servidor anterior, y el espacio restante de los discos de 476 GiB se reservó para el SO y el espacio de trabajo
- Los HDD aumentaron de 5 unidades a 6×5.46 TiB HDD
- El sistema operativo es Debian bookworm, con un kernel de la serie
Linux 6.1.0-21-amd64
- El planificador de I/O predeterminado era
none para NVMe y mq-deadline para HDD
Configuración de RAID 5 y monitoreo
- RAID 5 se ajusta bien a un servidor de tablebases porque puede recuperarse ante la falla de un solo disco y distribuir lecturas aleatorias entre varios discos
- La configuración inicial fue la siguiente
lvcreate --type raid5 --raidintegrity y --raidintegrityblocksize 512 --name tables --size 21T vg-hdd
- El rendimiento de las pruebas iniciales fue aceptable, pero sin monitoreo podrían haber pasado por alto que algunos discos no participaban al mismo nivel
- Si se omite
--stripes, no se usan de forma predeterminada todos los volúmenes físicos
- El monitoreo de la actividad de lectura por disco fue necesario para detectar una configuración RAID incorrecta
Cuellos de botella vistos con logs de solicitudes reales
- En condiciones normales, el servidor recibe entre 10 y 35 solicitudes por segundo
- Se registraron 1 millón de solicitudes en producción y, en el escenario elegido, 12 clientes paralelos las enviaron secuencialmente
- Las tablas se abren de forma diferida y las cachés de la aplicación y del SO se llenan gradualmente
- Los primeros 800 mil tiempos de respuesta se excluyeron como warm-up
- Después se analizaron los tiempos de respuesta de 200 mil solicitudes
- El tiempo de respuesta promedio era lo suficientemente rápido, pero la latencia de cola era alta y se convirtió en el foco de la optimización
- El gráfico ECDF muestra el porcentaje de solicitudes más rápidas que cada tiempo de respuesta, con el eje x en escala logarítmica
- En el gráfico se sumaron 30 ms a cada tiempo de respuesta para reflejar el ping time de 30 ms del cliente
- Esto se hizo para que el eje x logarítmico no exagerara diferencias de unos pocos milisegundos en el tramo bajo
pread(2) resultó mejor que mmap
- shakmaty-syzygy, la implementación de las tablebases Syzygy, ofrece interfaces para cambiar la forma de abrir y leer archivos de tablas
- Había dos candidatos principales
mmap: mapea el archivo de la tabla en memoria, y las lecturas de disco ocurren de forma transparente al acceder a esa región de memoria
pread(2): hace una llamada al sistema en cada lectura y reporta errores de lectura mediante el valor de retorno
mmap no requiere llamadas al sistema adicionales después del mapeo, pero como las lecturas parecen accesos normales a memoria, los errores deben manejarse mediante mecanismos out-of-band como señales
- En la implementación del servidor, el manejo de errores más robusto ya bastaba para justificar el uso de
pread, y en los benchmarks pread también tuvo mejor rendimiento en los escenarios de interés
- Una posible causa es que el acceso a un único bloque de datos mapeado en memoria puede cruzar un límite de página y terminar provocando dos lecturas de disco
- No hace falta aplicar
pread de inmediato a motores de ajedrez
- El uso de tablebases en partidas entre motores suele ocurrir cuando todas las tablas WDL pueden estar en almacenamiento lo bastante rápido
- En ese caso, los tiempos de respuesta típicos ni siquiera aparecerían en ese gráfico, y el mapeo de memoria es mejor para reducir el overhead de llamadas al sistema
Efectos contraproducentes de los hints de acceso aleatorio
posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM) y el hint equivalente para mapas de memoria terminaron siendo mayormente contraproducentes
POSIX_FADV_RANDOM le indica al SO que el acceso al archivo es aleatorio y que el read-ahead automático probablemente no será útil, como hint para reducir la presión sobre la page cache
- Los patrones de acceso a tablebases cuando las personas analizan finales pueden no ser tan aleatorios como se esperaba
- En motores de ajedrez, los probes pueden estar más dispersos entre distintos finales posibles, por lo que el resultado podría cambiar
Prefijos de tablas para colocar en SSD limitado
- Un table probe primero codifica la posición como un índice entero usando la información de codificación del header de la tabla
- Luego debe encontrar el bloque de datos comprimido que contiene el resultado de ese índice
- Syzygy proporciona una sparse block length list que apunta cerca de la entrada correcta, y después se usa la block length list para encontrar el bloque de datos correspondiente
- Los tamaños de las secciones de tablas son los siguientes
| Table section |
WDL |
DTZ |
Total |
| Headers and sparse block length lists |
38 GiB |
9 GiB |
47 GiB |
| Block length lists |
274 GiB |
64 GiB |
339 GiB |
| Compressed data blocks |
8433 GiB |
8458 GiB |
16891 GiB |
- También podría usarse el espacio SSD como una capa de caché adaptativa para cachear entradas hot de listas y bloques de datos
- Con el objetivo de reducir la latencia de cola, y considerando el peor caso, resultó adecuado colocar en SSD la sparse block length list y la block length list
- Esta disposición puede limitar a un máximo de 1 lectura desde discos lentos por cada table probe, sin importar si los datos son hot o cold
- En ese servidor no había suficiente espacio SSD para hacer espejado RAID 1 y, al ser una optimización selectiva, se renunció a la redundancia y se usó RAID 0
Paralelización de probes dentro de una solicitud
- Una solicitud típica de tablebase desde un motor de ajedrez pide un único valor WDL
- En la interfaz de usuario se busca mostrar los valores DTZ para todos los movimientos
- Incluyendo la resolución de capturas dentro de Syzygy, una solicitud promedio genera 23 probes WDL y 70 probes DTZ
- La implementación inicial paralelizaba el procesamiento de solicitudes, pero ejecutaba secuencialmente los probes dentro de cada solicitud
- Una paralelización más fina genera overhead en los tramos de baja latencia, pero reduce mucho la latencia de cola
- Aunque el disco no pueda procesar físicamente muchas lecturas paralelas, aumenta la probabilidad de que el planificador de I/O organice las lecturas para terminar antes cada solicitud
- Este enfoque permite planificar mejor el orden de los accesos relacionados al disco, reduciendo el tiempo hasta que el cabezal llega al sector de la siguiente solicitud
Verificación en producción y datos originales
- Se verificó mediante gráficos de tiempo de respuesta que las optimizaciones del escenario de benchmark también ayudaran en producción
- Los datos originales están publicados en lila-tablebase-bench
1 comentarios
Opiniones en Hacker News
Lichess es un servicio que uno no puede evitar admirar en silencio, como un buen vino. Es realmente excelente para la comunidad de ajedrez, y al usarlo todos los días me siguen impresionando sus funciones y su rendimiento.
Es aún más sorprendente si se sabe que es un equipo de 1 a 2 personas con un presupuesto limitado.
Me pareció interesante la parte donde mostraban la ECDF sumando 30 ms a cada tiempo de respuesta.
Agregar una constante puede parecer artificial, pero en la práctica es una forma de ver los resultados desde la perspectiva de un cliente con 30 ms de ping, y hace que el eje x en escala logarítmica no exagere diferencias de unos pocos ms en el rango bajo. Quizá sea una técnica estándar, pero me pareció un truco bastante inteligente.
Me pregunto si realmente era necesario reducir costos, o si había alguna otra razón por la que no podían simplemente poner un SSD de 20 TB en una sola máquina y listo. Un SSD de 4 TB ronda los 300 dólares, y las unidades SFF de HP o Dell tampoco son mucho más caras.
Probablemente les interesaban las pruebas y la optimización en sí; desde una perspectiva de producto, yo habría usado ese tiempo limitado en otros proyectos.
También vale la pena mencionar que la organización está basada en Francia, aunque no sé cómo influye eso en los costos.
Para justificar mejor una inversión de tiempo que esa, tendría que haber proyectos con una experiencia de usuario aún peor, o tratarse de una organización con fines de lucro con oportunidades de ganar dinero en otro lado y que admita que no le importa demasiado el dolor de sus clientes.
Esto parece más bien el resultado de combinar hardware demasiado potente y barato con gente floja que solo quiere decir “hasta aquí por hoy”. También existe eso de sentir orgullo por el propio trabajo, ¿no?
Hay varias decisiones cuestionables en esta optimización. La razón para optimizar era que había demasiada actividad de entrada/salida y la verificación RAID no lograba completarse.
Por lo que dice el artículo, no queda claro si alguna vez terminó realmente la verificación RAID sobre los 17 TiB de datos. En su lugar desactivaron la verificación RAID periódica y cambiaron a verificar errores por página al leer los datos, pero ambos métodos no son equivalentes, y si los datos son importantes deberían usarse ambos.
Si uno descubre la corrupción solo cuando intenta leer los datos, la corrupción antigua puede permanecer ahí y quizá ya se haya superado el período de retención de backups, haciendo imposible recuperar el original. A esto se suma el cambio a RAID 0: aunque sea la opción más rápida, implica confiar bastante en que esa configuración NVMe soporte esa carga.
Espero que tengan buenos backups. Una buena solución sería levantar un servidor temporal, restaurar el backup y hacer una verificación completa de los datos; si funciona, también se validan el procedimiento de backup/restauración y la integridad de los archivos. Aun así, deberían asegurar margen para terminar la verificación RAID en el servidor principal, y sería mejor no usar RAID 0 por rendimiento.
Es un dataset disponible libremente, solo que bastante grande. https://en.wikipedia.org/wiki/Endgame_tablebase lo explica mejor. Por eso tampoco hacen backups.
También existe lishogi, pero todavía es pequeño y no está en una escala que requiera este tipo de optimización.
Entre las variantes de ajedrez, el shogi es la más divertida; el xiangqi no tanto.
Me pregunto si se puede interpretar lichess como una lich femenina, algo como baron/baroness.
Estrictamente, un lich masculino sería “werlich”, uno femenino sería “wiflich”, y el plural llevaría “-en”. Pero para los no muertos el género suele ser irrelevante, así que la forma neutra se usa abrumadoramente más.
“lichess” es una combinación extraña de raíces alemanas y francesas, por lo que naturalmente no se distingue de otras palabras del inglés.
No es una comparación justa, pero la calidad de ingeniería del equipo de Lichess realmente impresiona. Su principal competidor presume su migración a GCP, pero sufrió caídas repetidas a medida que creció su popularidad, y calculo que tiene unas 100 veces más personal.
El punto débil de Lichess era la app móvil, pero la v2 rehecha en Flutter ya está bastante buena aunque todavía esté en beta.
Y también hay que recordar que Thibault ni siquiera cobra 60 mil dólares al año como remuneración.
Eso sí, me da curiosidad cómo se pronuncia Lichess: ¿Lie chess, Le chess o League chess?