O segredo para acelerar servidores NUMA gigantes: o HQ Spinlock e a “fila inteligente” do Linux

Um novo RFC do kernel propõe o HQ Spinlock, um lock NUMA-aware que reduz cache bouncing e promete grandes ganhos em servidores gigantes sob alta contenda.

Escrito por
Emanuel Negromonte
Emanuel Negromonte é Jornalista, Mestre em Tecnologia da Informação e atualmente cursa a segunda graduação em Engenharia de Software. Com 14 anos de experiência escrevendo sobre...

Um novo RFC na LKML propõe uma mudança ambiciosa em um dos pontos mais sensíveis do Linux Kernel quando o assunto é escala: o comportamento do qspinlock sob contenda extrema. A série, enviada por Anatoly Stepanov (Huawei), apresenta o HQ Spinlock (Hierarchical Queued NUMA-aware spinlock), um spinlock com fila hierárquica projetado para reduzir tráfego cross-NUMA em servidores multi-socket enormes, comuns em HPC e infraestrutura de alta densidade. A ideia central é simples de explicar e difícil de implementar bem: fazer o “bastão” do lock circular de forma mais inteligente, respeitando a geografia do processador para melhorar Linux HQ spinlock performance onde hoje o sistema sofre.

Antes de mergulhar no HQ, vale um lembrete rápido do que está em jogo. O qspinlock é excelente no caso comum de baixa disputa (fast path), mas quando muitos threads entram no slow path, ele usa uma mecânica de fila (queued spinlock) com handoff no estilo MCS. Essa fila traz ordem, mas pode cobrar caro em máquinas NUMA gigantes.

O problema da escala em NUMA

Em NUMA (Non-Uniform Memory Access), nem toda memória e nem todo cache estão “igualmente perto” de cada core. Em servidores com múltiplos soquetes, atravessar nós NUMA significa pagar latência e consumir interconexão entre sockets. Quando muitos cores tentam pegar o mesmo spinlock, a pior coisa que pode acontecer é a linha de cache que representa o estado do lock ficar “quicando” entre nós, o famoso cache-line bouncing.

Aqui entra a analogia da fila do banheiro. Imagine uma festa gigante em um prédio com vários andares (nós NUMA), mas existe só uma chave para o banheiro VIP (o lock). No modelo atual, a chave pode acabar viajando de um andar para outro a cada troca, fazendo pessoas correrem escada acima e escada abaixo. A escada é o tráfego cross-NUMA. Quanto mais gente disputa, mais tempo se perde na correria e menos gente “usa o banheiro” por minuto. Em termos técnicos: contenda alta vira transferência de cache-line entre nós e o throughput de lock/unlock despenca.

O RFC destaca duas fontes típicas desse custo: o momento em que contenders fazem enqueue atualizando uma estrutura compartilhada e o handoff MCS quando o próximo contender está em outro nó NUMA, forçando uma transferência cara entre soquetes.

Como o HQ Spinlock resolve a fila

O HQ Spinlock introduz uma hierarquia de duas camadas. Em vez de tratar todos os contenders como parte de uma única fila global, ele organiza o acesso em dois níveis:

  1. Fila local FIFO por nó NUMA: cada nó mantém sua própria fila de contenders.
  2. Fila global de nós (lista encadeada): as filas de nós NUMA são ligadas em uma estrutura tipo lista, preservando a ordem FIFO entre nós. Quando um nó esvazia, ele sai da lista.

A consequência prática é que o handoff passa a ser “em duas etapas”. Primeiro vem o local handoff, que tenta passar o lock para alguém do mesmo nó (mesma “geografia” de cache). Só quando a fila local não tem mais ninguém é que ocorre o remote handoff, entregando o lock para o próximo nó na lista. Se o remote handoff chega ao fim, ele volta para a cabeça da lista, promovendo rotação entre nós.

Esse desenho conversa com ideias clássicas da literatura de locks, e o próprio RFC descreve o HQ como uma combinação de cohort-locking (Dave Dice) com o queued spinlock do Linux. O objetivo é reduzir cross-NUMA traffic sem jogar fora as propriedades úteis do modelo em fila.

Há, porém, um risco óbvio: se o lock sempre “prefere a casa”, um nó pode dominar e outros podem esperar demais. Para evitar starvation, a proposta usa handoff thresholds, limitando por quanto tempo o lock pode continuar fazendo handoff local antes de dar chance a outro nó.

Metadados dinâmicos e alternância de modo

Um desafio citado como central foi manter “o mesmo tamanho” da estrutura do qspinlock e ainda assim associar um lock às filas NUMA corretas. A saída foi um conceito de dynamic lock metadata: o lock pode ser “bindado” a metadados NUMA apenas quando cai no slow path e “unbindado” quando é totalmente liberado. Na prática, isso evita reservar metadados para cada lock do kernel de antemão. Só paga o custo quem realmente entra em alta contenda, o que é importante porque locks estão em todo lugar no kernel.

Além disso, o HQ suporta dynamic mode switching. Ele começa no modo padrão (qspinlock) e, quando a contenda fica alta o suficiente, muda para o modo NUMA-aware e permanece nele até o lock ser completamente liberado. Se não der para ativar o modo HQ por algum motivo, há fallback para o qspinlock padrão. Para testes em subsistemas específicos, o RFC sugere a troca de inicialização para spin_lock_init_hq(), usando um bit no lock para distinguir os tipos.

Ganhos de até 186%: o que dizem os benchmarks

Os resultados apresentados são fortes principalmente em cenários sintéticos de contenda máxima. No locktorture (60s, single lock), em um AMD EPYC 9654 (192 cores com SMT, 2 sockets), os ganhos crescem conforme aumenta o “NPS” (nodes per socket) e o número de contendores. Esse detalhe é crucial: quanto mais nós NUMA por soquete, mais “fragmentada” fica a topologia e maior a chance de handoffs cruzarem domínios NUMA, aumentando o custo do cache bouncing. É exatamente onde uma política de handoff local tende a brilhar. No cenário mais agressivo mostrado (12 NPS), o ganho chega a 186% com 384 contendores.

O RFC também traz um “fairness factor” na tabela do EPYC, girando em torno de ~0,51 a ~0,61, sugerindo que os ganhos não vieram simplesmente de”furar fila” de forma descontrolada, mas de reduzir custo de handoff mantendo um nível razoável de justiça, apoiado pelos thresholds anti-starvation.

Em ARM64, no Kunpeng 920 (96 cores, 2 sockets, 4 nós NUMA), o locktorture também mostra saltos expressivos, chegando a 158% com 96 contendores.

Em workloads mais reais, os ganhos são menores, mas relevantes. Em Memcached com memtier (1:1 R/W), o EPYC mostra até 10% de ganho de throughput e até 8% de redução de latência em cargas altas; no Kunpeng, até 8% de ganho e até 8% de redução de latência. Em Nginx com wrk no Kunpeng, em um teste focado em contenda de spinlock via lockref (single file), o throughput sobe até 78% com 96 workers, e os ganhos aparecem de forma marcante também a partir de 64 workers.

Limitações e próximos passos

O RFC não esconde o tradeoff: com o esquema simples de detecção de contenda, houve degradação em baixa contenda (menos de 8 contendores) em comparação ao qspinlock. Ou seja, o HQ parece ser uma ferramenta voltada para o “pior caso” de servidores enormes e hot locks muito disputados. Os autores dizem estar trabalhando em um método de detecção mais sofisticado para evitar pagar overhead quando o prédio está vazio e ninguém está correndo pela escada.

Como é um RFC, o caminho natural agora é discussão técnica intensa: justiça, complexidade, custos de metadados, estabilidade, e quais subsistemas realmente se beneficiam. A proposta é promissora, mas ainda está no estágio de convencer a comunidade de que o ganho em máquinas gigantes compensa o risco de complexidade adicional no locking.

Compartilhe este artigo