antonio leandro

dados e armazenamento

SILK: Preventing Latency Spikes in Log-Structured Merge Key-Value Stores

paper · Oana Balmau, Florin Dinu, Willy Zwaenepoel, et al. · · ~50 min de leitura do original

a tese

reduzir o custo do trabalho interno de um lsm store não conserta a cauda: a p99 vem da interferência entre flush, compaction e cliente, e o que falta é um escalonador de i/o

o que fica

  1. O pico de latência nasce do buffer em memória enchendo e bloqueando writes; os reads ficam presos atrás desses writes na fila, então leitura e escrita sofrem spikes ao mesmo tempo e do mesmo tamanho, apesar de terem caminhos diferentes.
  2. Limitar a banda das operações internas adia o problema em vez de resolvê-lo: as compactions represadas se acumulam e mais tarde rodam todas juntas, disputando exatamente a banda que foi limitada.
  3. Nem toda operação interna vale o mesmo: flush e compaction de L0 para L1 bloqueiam o cliente se atrasarem, enquanto compactions dos níveis altos podem esperar sem consequência imediata.
  4. Só pode existir uma compaction L0 para L1 por vez, e dividir a banda igualmente entre compactions paralelas faz justamente a mais crítica virar a mais lenta.
  5. O número de threads de compaction deve ser derivado da banda do disco, não da contagem de cores: para um drive de 200 MB/s, quatro threads internas.
  6. Teste curto esconde o problema: o TRIAD passou cerca de 1.000 segundos sem spikes e o PebblesDB cerca de 8 horas antes de a compaction do último nível parar o sistema.

o problema

Um LSM key-value store aceita write rápido porque não coloca o dado no lugar certo na hora. A atualização é absorvida por um componente em memória, tipicamente de algumas dezenas de MB, e mais tarde escrita para o disco como está — o flush, que aterrissa em L0. Como L0 admite arquivos com key-range sobreposto, alguém precisa arrumar a casa depois: a compaction lê SSTables de um nível, funde com os do nível seguinte que têm key-range em comum e descarta os valores velhos. Boa parte da pesquisa recente ataca o custo dessa arrumação — separar chave de valor, escolher melhor quando compactar, adiar a fusão para o último nível. Tudo isso mira throughput.

Os autores medem outra coisa: o p99 calculado a cada intervalo de um segundo. Comparando RocksDB com uma versão dele sem flush nem compaction, sob YCSB 50:50 a 18 Kops/s, a cauda fica de duas a quatro ordens de grandeza pior — e o estrago não aparece no p50 nem no p90. Dois roteiros produzem o pico. No primeiro, L0 enche: como só roda uma compaction L0 para L1 por vez e a banda é dividida igualmente entre todas as compactions paralelas, a única que limpa L0 é a mais lenta, L0 bate o limite de SSTables e os flushes param. No segundo, L0 nem enche: sete compactions L1 para L2 coincidem, o flush que costuma levar 1 ou 2 segundos leva 5, o componente em memória lota e os writes travam.

a ideia

Limitar a banda das internas, a receita padrão em produção, não resolve: adiar compaction aumenta a chance de muitas rodarem juntas depois. É o mesmo efeito que os autores observam nos sistemas que reduzem trabalho interno — o TRIAD fica cerca de 1.000 segundos limpo e depois passa a apresentar spikes frequentes; o PebblesDB, que só compacta no último nível, dá latência excelente até a compaction chegar, e aí para o sistema inteiro por horas.

O que falta não é gastar menos I/O, é decidir quem usa o I/O e quando. O SILK trata carga do cliente e trabalho interno como disputa pelo mesmo recurso e coloca um escalonador entre eles, apoiado num fato da carga real: ela não é linha reta. Na carga de produção da Nutanix, picos de cerca de 20 mil requisições por segundo alternam com vales de centenas ou menos, e o vale típico dura de 5 a 20 segundos. O escalonador vive desses vales.

como funciona

Três mecanismos. Primeiro, alocação oportunista: uma thread mede a cada 10 ms a banda C consumida pelo cliente e ajusta a banda das internas para I = T − C − ε, onde T é o total do dispositivo. O limite só é reajustado quando a diferença passa de 10 MB/s, para não pagar overhead com flutuação pequena.

Segundo, prioridade. O SILK mantém dois pools de threads internas: um de alta prioridade, só para flush, que sempre tem acesso à banda interna e a um piso configurável — 50 MB/s na avaliação, contra 200 MB/s de banda total. Compaction de L0 para L1 vem em seguida; compactions acima de L1 ficam no pool de baixa prioridade e podem ser pausadas, individualmente ou o pool inteiro.

Terceiro, preempção. Quando uma compaction L0 para L1 precisa rodar e todas as threads de compaction estão ocupadas com níveis altos, uma delas é interrompida — a vítima é escolhida ao acaso — e a L0 para L1 é promovida para o pool de alta prioridade, dividindo o piso com o flush. Ela nunca é pausada, mesmo quando o SILK zera a banda das demais. Compaction L0 para L0 recebe o mesmo tratamento. Se o trabalho parcial da compaction preemptada for invalidado, ele é jogado fora. O breakdown mostra que nenhuma das técnicas segura a carga sozinha: só banda dinâmica deixa a operação urgente lenta quando aparece compaction grande; só prioridade preserva a estrutura perto da memória mas volta a sofrer interferência.

o que isso custou

O ganho depende de existir vale. Sob pico contínuo sintético, o SILK também degrada: com 90% de writes, por volta de 500 segundos de experimento (300 de pico); com 50%, por volta de 700 (500 de pico). A carga de produção usada tem pico máximo de cerca de 400 segundos, então o sistema cabe nela, mas o limite é explícito.

O throughput paga até 7% na distribuição uniforme e até 4% na zipfian. No YCSB E zipfian, a mediana fica 5% pior que a do TRIAD. Em cargas dominadas por leitura e scan o ganho de cauda cai para o mínimo de 5% — as duas ordens de grandeza são de write-heavy. A preempção descarta trabalho, e a escolha aleatória da vítima é admitidamente uma política provisória; alocar banda por urgência da compaction ficou como ideia não explorada. A implementação atual usa dois componentes de memória e uma única thread de flush. E os autores não aplicaram o escalonador ao PebblesDB, por não conhecerem o código e porque o consumo de memória dele não cabia no ambiente. As medições rodaram com compressão e commit log desligados.

onde isso aparece hoje

O SILK é código aberto e foi implementado como extensão de dois sistemas, RocksDB e TRIAD, o que é o argumento dos autores de que a ideia é portável para qualquer store construído sobre a LSM-tree. A linha de fundo — trabalho de manutenção interferindo em requisição de cliente — é a mesma que aparece no relato de prioridades de engenharia do RocksDB e no uso do RocksDB como engine de produção descrito no MyRocks. E a razão de o p99 importar mais que a média, quando uma requisição se abre em muitas e a mais lenta define o tempo total, é o argumento de The Tail at Scale.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·