Como o MariaDB 11.8 ficou até 3x mais rápido em cargas em memória
Uma regressão escondida desde o 10.5 fazia o InnoDB disputar um mutex global na LRU. Entenda a causa, a correção e os ganhos de quase 3x no MariaDB 11.8.

Resumo para quem tem pressa: desde o MariaDB 10.5, cargas de trabalho que cabem inteiras na memória (o famoso in-memory) passaram a pagar um "pedágio" escondido no gerenciamento da lista LRU do InnoDB. A MariaDB Foundation investigou, encontrou a origem e corrigiu. Resultado: em um dos testes, o MariaDB 11.8 saltou de cerca de 20 mil para 57 mil consultas por segundo — quase 3x. Bora entender o porquê, no meu jeito: sem enrolação, mas sem pular o detalhe que importa.
Primeiro, o básico: buffer pool e LRU
O buffer pool é o cache de páginas do InnoDB na memória. Como memória é finita, alguém precisa decidir quem sai quando falta espaço. Esse alguém é a lista LRU (least recently used): páginas usadas recentemente ficam na parte "jovem"; as esquecidas descem para a parte "velha" e são candidatas a despejo.
Quando uma página velha é acessada de novo, ela é promovida para a parte jovem. Para fazer isso, o servidor precisa travar um mutex global, o buf_pool.mutex. Guarde essa informação: ela é o vilão da história.
O detalhe que mudou no 10.5
No MariaDB 10.5, o buffer pool passou a ter uma única instância (antes era possível particioná-lo com innodb_buffer_pool_instances, herança do MySQL 5.5). A expectativa, razoável, era que cargas em memória quase não precisariam mexer na LRU.
Só que no 10.4 havia um "relógio" interno, o freed_page_clock, que só começava a andar quando o servidor realmente despejava páginas. Enquanto ele estivesse em zero, o InnoDB nem tentava promover nada — afinal, se ninguém está sendo despejado, não há por que reorganizar a fila.
No 10.5, melhorias legítimas no tratamento de páginas liberadas (para evitar gravar lixo em disco e acelerar a recuperação) tiveram um efeito colateral: o relógio passou a andar muito antes, por exemplo quando o purge liberava páginas de undo. Uma vez fora do zero, ele nunca voltava. Resultado: mesmo com o buffer pool 80% vazio, praticamente todo acesso a uma página "velha" disputava o mutex global.
Como o problema apareceu
Quem soou o alarme foi Mark Callaghan, que roda sysbench em cada versão do MariaDB e publica tudo. Em um teste com SELECT usando um IN de 100 valores na chave primária, 40 threads e 48 núcleos, o 10.4 fazia cerca de 73 mil QPS, e as versões a partir do 10.5 ficavam na faixa de 26 a 30 mil. Uma exceção curiosa — o 10.8.8, rápido "por acidente" porque um bug deixava o buffer pool cheio — ajudou a confirmar a teoria.
-- Formato da consulta usada no teste (simplificado)
SELECT id FROM sbtest1
WHERE id IN (17, 42, 1337, /* ... 100 valores ... */ 98765);
A prova de laboratório
Para sair da teoria, foram compiladas três variantes: a original, uma que nunca promove páginas e outra em que o relógio nunca anda. As duas "gambiarras" quase triplicaram o throughput, e o contador de páginas promovidas caiu de mais de 100 milhões para zero. Bingo.
Curiosidade útil: dá para reproduzir o efeito sem recompilar, com um valor gigante em innodb_old_blocks_time:
-- Paliativo para versões sem a correção (use só se os dados cabem no buffer pool!)
SET GLOBAL innodb_old_blocks_time = 2147483647;
Atenção: isso praticamente desliga a promoção. Se seus dados não cabem na memória, páginas quentes podem ser despejadas como se fossem de um table scan. É remendo, não solução.
A solução escolhida (e por que ela vai além do mínimo)
A correção mínima seria devolver ao relógio o comportamento do 10.4. Mas o time preferiu atacar a raiz, continuando uma ideia antiga de Marko Mäkelä: tirar o mutex do caminho do acesso.
- No acesso: sem mutex. A página só recebe uma marca
PROMOTE(via operação atômica) se cumprir a regra doinnodb_old_blocks_time. - Na varredura: threads que já seguram o mutex por outros motivos (page cleaner, despejo, flush) movem as páginas marcadas, liberando o mutex a cada 512 blocos.
- Novo relógio: o
freed_page_clocksaiu; entrou um contador de segundos atualizado em segundo plano. Consequência prática:innodb_old_blocks_timepassa a ter resolução de 1 segundo.
É a mesma direção do PostgreSQL com seu clock-sweep: o custo de decidir "quem é recente" sai do acesso e vai para o momento do despejo. E a resistência a table scans foi preservada, com teste automatizado novo para garantir.
Os números
| Servidor | QPS | p95 (ms) | Trocas de contexto / consulta |
|---|---|---|---|
| 10.4.34 | 52.410 | 0,92 | 1,07 |
| 11.8 antes da correção | 20.326 | 2,52 | 28,8 |
| 11.8 com a correção | 57.052 | 0,95 | 1,16 |
Ou seja: o 11.8 corrigido ficou ~9% mais rápido que o próprio 10.4 neste teste, mesmo fazendo gerenciamento de LRU. A validação independente em outra máquina mostrou ganhos ainda maiores no 11.8, 12.3 e 13.1. E o medo de despejar páginas quentes? A taxa de acerto do cache ficou igual ou levemente melhor.
O que isso significa para você
- Se você roda MariaDB 10.5+ com dados que cabem no buffer pool e muita concorrência de leitura, atualizar para uma versão com a correção (ela foi para as séries LTS 11.8 e 12.3) pode render um ganho grande sem mudar uma linha da aplicação.
- Confira o contador de promoções antes e depois:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_made_young';
SHOW ENGINE INNODB STATUS\G -- seção BUFFER POOL AND MEMORY
- Teste em homologação com sua carga real. Benchmark dos outros é bússola, não GPS.
Quer ir mais fundo no motor? Veja os storage engines do MariaDB explicados e o hub MariaDB.
Créditos e agradecimento
Este texto é uma explicação em português baseada no excelente artigo publicado no blog da MariaDB Foundation por Roman Nozdrin, com a investigação de Alessandro Vetere. Meu muito obrigado ao Roman pelo artigo — trabalho de investigação de primeira, do tipo que faz a comunidade inteira aprender. Todos os números e o mérito técnico são deles.
Leia o original (em inglês): How MariaDB 11.8 became about 3× faster on some in-memory workloads, fixing a regression dating back to 10.5.