- Como as verificações periódicas de RAID do servidor de tablebase Syzygy de 7 peças estavam ficando para trás por causa da carga de requisições no Lichess, a equipe trocou o varrimento completo de blocos por verificação de integridade na leitura
- Para migrar a tablebase de 17TiB sem horas de indisponibilidade, prepararam um novo servidor e validaram a configuração reproduzindo logs reais de requisições em um ambiente com 32GiB de RAM, 2×201GiB NVMe e 6×5.46TiB HDD
- Ao reproduzir 1 milhão de requisições registradas em produção com 12 clientes em paralelo, ficou claro que o principal gargalo não era a resposta média, mas sim a latência de cauda percebida pelo usuário
- Do ponto de vista de implementação,
pread(2) se saiu melhor que mmap em tratamento de erros e latência de cauda, e dicas de acesso aleatório como POSIX_FADV_RANDOM e MADV_RANDOM em geral tiveram efeito contrário
- Com SSD limitado, colocaram nele os prefixos das tables e paralelizaram os probes internos de cada requisição para reduzir acessos lentos ao disco, além de verificar se as melhorias do benchmark também apareciam no tempo de resposta em produção
Mudança de verificação total do RAID para validação na leitura
- O servidor de tablebase Syzygy de 7 peças do Lichess tinha dificuldade para concluir as verificações periódicas de integridade do RAID enquanto recebia muitas requisições de tablebase
- A nova configuração usa dm-integrity on LVM para não verificar periodicamente todos os blocos de dados, validando cada bloco sempre que ele é lido
- Para migrar a tablebase de 17TiB sem várias horas de indisponibilidade, foi montado um novo servidor separado
- Isso permitiu executar benchmarks controlados sobre toda a tablebase antes da troca real
- Depois disso, o serviço foi migrado para o novo servidor e o antigo foi aposentado
Configuração do novo servidor
- A RAM foi mantida em 32GiB, igual à do servidor anterior
- No armazenamento, foram adicionados 2×201GiB NVMe, que não existiam no servidor anterior, e o espaço restante do disco de 476GiB foi reservado para o sistema operacional e área de trabalho
- Os HDDs passaram de 5 para 6×5.46TiB HDD
- O sistema operacional é Debian bookworm, com kernel da linha
Linux 6.1.0-21-amd64
- O escalonador de I/O padrão selecionado era
none no NVMe e mq-deadline nos HDDs
Configuração de RAID 5 e monitoramento
- O RAID 5 é adequado para o servidor de tablebase porque pode se recuperar de falha em um único disco e distribuir leituras aleatórias entre vários discos
- A configuração inicial era a seguinte
lvcreate --type raid5 --raidintegrity y --raidintegrityblocksize 512 --name tables --size 21T vg-hdd
- O desempenho inicial dos testes era bom, mas sem monitoramento seria fácil deixar passar o problema de alguns discos não participarem no mesmo nível que os outros
- Se
--stripes for omitido, nem todos os volumes físicos são usados por padrão
- O monitoramento da atividade de leitura por disco foi necessário para detectar a configuração incorreta do RAID
Gargalos observados em logs reais de requisição
- Em condições normais, o servidor recebe entre 10 e 35 requisições por segundo
- Foram registrados 1 milhão de requisições em produção, e no cenário escolhido 12 clientes em paralelo as reenviaram em sequência
- As tables são abertas de forma lazy, e os caches da aplicação e do sistema operacional são preenchidos gradualmente
- Os tempos de resposta das primeiras 800 mil foram excluídos como aquecimento
- Em seguida, foram analisados os tempos de resposta das 200 mil requisições restantes
- O tempo médio de resposta é suficientemente rápido, mas a latência de cauda alta virou o foco da otimização
- O gráfico ECDF mostra a proporção de requisições mais rápidas que cada tempo de resposta, e o eixo x está em escala logarítmica
- No gráfico, foram somados 30ms a cada tempo de resposta para refletir o ping time de 30ms do cliente
- Isso evita que a escala logarítmica do eixo x exagere diferenças de poucos milissegundos na faixa mais baixa
pread(2) teve vantagem sobre mmap
- A implementação Syzygy tablebase shakmaty-syzygy oferece uma interface que permite trocar tanto a forma de abrir os arquivos de table quanto a forma de leitura
- Havia dois candidatos principais
mmap: mapeia o arquivo da table na memória, e as leituras de disco acontecem de forma transparente ao acessar essa região
pread(2): faz uma system call a cada leitura e informa erros de leitura pelo valor de retorno
- O
mmap não exige system calls adicionais depois do mapeamento, mas como a leitura se parece com um acesso comum à memória, erros precisam ser tratados por mecanismos fora de banda, como sinais
- Na implementação do servidor, só o tratamento de erros mais robusto já justificava usar
pread, e os benchmarks também mostraram melhor desempenho do pread nos cenários de interesse
- Uma possível causa é que o acesso a um único bloco de dados mapeado em memória pode cruzar o limite de página e acabar exigindo duas leituras de disco
- Isso não significa que seja preciso aplicar
pread imediatamente a motores de xadrez
- Em partidas entre engines, o uso de tablebase normalmente acontece quando todas as WDL tables podem ficar em um dispositivo de armazenamento suficientemente rápido
- Nesse caso, a faixa típica de tempo de resposta nem aparece no gráfico, e o mapeamento de memória é melhor por reduzir o overhead de system calls
Efeito contrário das dicas de acesso aleatório
posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM) e a dica equivalente para memory map acabaram tendo, na maioria dos casos, efeito negativo
POSIX_FADV_RANDOM informa ao sistema operacional que o acesso ao arquivo é aleatório e que o read-ahead automático provavelmente não será útil, servindo como dica para reduzir pressão no page cache
- O padrão de acesso à tablebase quando pessoas analisam finais pode ser menos aleatório do que se imaginava
- Em engines de xadrez, os probes podem ficar mais espalhados entre finais diferentes, então o resultado pode mudar
Prefixos das tables colocados em SSD limitado
- Um probe na table primeiro codifica a posição como um índice inteiro com base nas informações de codificação do cabeçalho da table
- Depois, é preciso localizar o bloco de dados comprimido que contém o resultado desse índice
- O Syzygy fornece uma lista esparsa de comprimentos de blocos que aponta para perto da entrada correta, e em seguida usa a lista de comprimentos de blocos para localizar o bloco de dados relevante
- Os tamanhos das seções da table são os seguintes
| Table section |
WDL |
DTZ |
Total |
| Headers and sparse block length lists |
38GiB |
9GiB |
47GiB |
| Block length lists |
274GiB |
64GiB |
339GiB |
| Compressed data blocks |
8433GiB |
8458GiB |
16891GiB |
- Também seria possível usar o espaço em SSD como uma camada de cache adaptativa para armazenar entradas de lista frequentes e blocos de dados quentes
- Como o objetivo era reduzir a latência de cauda, fazia mais sentido considerar o pior caso e colocar no SSD a lista esparsa de comprimentos de blocos e a lista de comprimentos de blocos
- Com esse arranjo, independentemente de a table estar quente ou fria, cada probe na table fica limitado a no máximo 1 acesso lento ao disco
- Nesse servidor, não havia espaço suficiente em SSD para fazer espelhamento RAID 1, e como era uma otimização seletiva, a redundância foi sacrificada em favor de RAID 0
Paralelização dos probes internos de cada requisição
- Uma requisição típica de tablebase feita por um motor de xadrez pede um único valor WDL
- Na interface para o usuário, a intenção é mostrar os valores DTZ para todos os lances
- Incluindo a resolução interna de capturas no Syzygy, uma requisição média gera 23 probes WDL e 70 probes DTZ
- A implementação inicial paralelizava o processamento entre requisições, mas executava em sequência os probes dentro de cada requisição
- Uma paralelização mais fina aumenta o overhead na faixa de menor latência, mas reduz bastante a latência de cauda
- Mesmo que o disco não consiga processar fisicamente muitas leituras paralelas de verdade, o escalonador de I/O passa a ter mais chances de planejar as leituras para concluir cada requisição mais rápido
- Isso ajuda a ordenar melhor os acessos relacionados ao disco para reduzir o tempo até que a cabeça do disco alcance o setor necessário para a próxima requisição
Verificação em produção e dados brutos
- Foi usado um gráfico de tempos de resposta para verificar se as otimizações do cenário de benchmark também ajudavam no ambiente real de produção
- Os dados brutos estão publicados em lila-tablebase-bench
1 comentários
Comentários no Hacker News
Lichess é um serviço que dá vontade de admirar em silêncio, como um bom vinho. É realmente excelente para a comunidade de xadrez, e uso todos os dias continuando impressionado com os recursos e o desempenho
Fica ainda mais surpreendente quando se sabe que é uma equipe de apenas 1 a 2 pessoas com orçamento limitado
Achei interessante a parte que mostrava a ECDF com 30 ms adicionados a cada tempo de resposta
Adicionar uma constante pode parecer artificial, mas na prática é uma forma de ver o resultado da perspectiva de um cliente com ping de 30 ms, e o eixo x em escala logarítmica evita exagerar diferenças de alguns milissegundos nas faixas mais baixas. Talvez seja uma técnica padrão, mas pareceu um truque bem inteligente
Fiquei me perguntando se realmente era necessário cortar custos, ou se não havia outro motivo além disso para simplesmente colocar um SSD de 20 TB em uma máquina e encerrar o assunto. Até SSDs de 4 TB custam algo como 300 dólares, e drives SFF da HP ou Dell também não são muito mais caros
Provavelmente havia interesse no teste e na otimização em si, e do ponto de vista de produto talvez o tempo limitado tivesse sido melhor gasto em outros projetos
Não sei como o fato de a organização ser baseada na França afeta os custos, mas vale mencionar
Para justificar melhor o investimento de tempo do que isso, só se houvesse outro projeto com experiência de usuário ainda pior, ou se fosse uma organização com fins lucrativos que tivesse oportunidades de ganhar dinheiro em outro lugar e admitisse não ligar muito para o sofrimento do cliente
Parece resultado da combinação entre hardware poderoso e barato demais e gente preguiçosa que quer apenas dizer “por hoje chega”. Não existe também aquela ideia de ter orgulho do próprio trabalho?
Há algumas escolhas questionáveis nessa otimização. O motivo da otimização é que havia atividade de I/O demais e por isso a verificação de RAID não conseguia terminar
Pelo texto, não fica claro se a verificação de RAID sobre 17 TiB de dados já chegou a terminar alguma vez. Em vez disso, desativaram a verificação periódica de RAID e mudaram para checagem de erros por página ao ler os dados, mas as duas coisas não são equivalentes e, se os dados forem importantes, o ideal é usar ambas
Se a corrupção só for descoberta quando alguém tentar ler os dados, danos antigos podem continuar lá por muito tempo e talvez o original já não possa mais ser recuperado porque o prazo de retenção do backup passou. Também está implícito aqui que mudaram para RAID 0, que é de fato a opção mais rápida, mas isso pressupõe uma confiança considerável de que essa configuração de NVMe vai aguentar essa carga
Espero que os backups estejam bem feitos. Uma boa solução seria subir um servidor temporário, restaurar o backup e fazer uma verificação completa dos dados, e se der certo ainda validar junto os procedimentos de backup e restauração e a integridade dos arquivos. Mesmo assim, ainda deveria haver folga para concluir a verificação de RAID no servidor principal, e é melhor evitar usar RAID 0 por causa de desempenho
Trata-se de um conjunto de dados disponível livremente, só que grande. https://en.wikipedia.org/wiki/Endgame_tablebase explica melhor. Por isso também não fazem backup
Também existe o lishogi, mas ainda é pequeno demais para precisar desse tipo de otimização
Entre as variantes de xadrez, shogi é a mais divertida, enquanto xiangqi nem tanto
Fiquei pensando se lichess significaria algo como lich feminino, tipo baron/baroness
Tecnicamente, lich masculino seria “werlich”, lich feminino seria “wiflich”, e no plural recebe “-en”. Mas como para mortos-vivos o gênero geralmente é irrelevante, a forma neutra é de longe a mais usada
“lichess” é uma combinação estranha que mistura raízes do alemão e do francês, então naturalmente não se distingue de outras palavras do inglês
Não é uma comparação totalmente justa, mas a qualidade de engenharia da equipe do Lichess realmente impressiona. O principal concorrente se gabava da migração para a GCP e ainda assim sofreu interrupções repetidas com o aumento da popularidade, tendo provavelmente umas 100 vezes mais gente
O ponto fraco do Lichess era o app móvel, mas a v2 refeita em Flutter, mesmo ainda em beta, já é bem boa
E também vale lembrar que o Thibault recebe menos de 60 mil dólares por ano como remuneração
Só fico curioso sobre como se pronuncia Lichess. É Lie chess, Le chess ou League chess?