Compactação de dados no Apache Ignite. Experiência do Sber

Compactação de dados no Apache Ignite. Experiência do SberAo lidar com grandes volumes de dados, pode surgir o problema agudo da falta de espaço em disco. Uma das soluções para esse problema é a compactação, que permite aumentar os volumes de armazenamento com o mesmo equipamento. Neste artigo, vamos explorar como funciona a compactação de dados no Apache Ignite. Serão descritas apenas as técnicas de compactação implementadas dentro do produto. Outras técnicas de compactação de dados (via rede, na memória), tanto as implementadas quanto as não implementadas, ficarão de fora.

Assim, com o modo de persistência ativado, como resultado das alterações de dados nos caches, o Ignite começa a gravar no disco:

  1. Conteúdo dos caches
  2. Registro de gravação antecipada (Write Ahead Log, WAL)

Para a compactação do WAL, existe há algum tempo um mecanismo chamado WAL compaction. Na recém-lançada versão 2.8 do Apache Ignite, surgiram mais dois mecanismos que permitem comprimir dados no disco: compactação de página de disco para comprimir o conteúdo dos caches e compactação de instantâneo de página WAL para comprimir algumas entradas do WAL. Mais detalhes sobre esses três mecanismos abaixo.

Compactação de página de disco

Como funciona

Para começar, vamos rapidamente abordar como o Ignite armazena dados. A armazenamento utiliza memória paginada. O tamanho da página é definido no início do nó e não pode ser alterado posteriormente; além disso, o tamanho da página deve ser uma potência de dois e múltiplo do tamanho do bloco do sistema de arquivos. As páginas são carregadas na RAM do disco conforme necessário, e o tamanho dos dados em disco pode exceder o volume de RAM alocada. Em caso de falta de espaço na RAM para carregar uma página do disco, páginas antigas, já não utilizadas, serão expulsas da RAM.

No disco, os dados são armazenados da seguinte forma: para cada partição de cada grupo de cache, é criado um arquivo separado, nesse arquivo, em ordem crescente de índice, as páginas são dispostas uma após a outra. O identificador completo da página contém o identificador do grupo de cache, o número da partição e o índice da página no arquivo. Assim, pelo identificador completo da página, podemos determinar de forma inequívoca o arquivo e o deslocamento interno do arquivo para cada página. Informações mais detalhadas sobre a arquitetura da memória paginada podem ser encontradas em um artigo no Apache Ignite Wiki: Ignite Persistent Store — por trás dos bastidores.

O mecanismo de compressão de página de disco, como pode ser deduzido pelo nome, opera em nível de página. Quando esse mecanismo é ativado, o trabalho com os dados na RAM é realizado sem qualquer compressão, mas no momento da gravação das páginas da RAM para o disco, ocorre a compressão.

No entanto, comprimir cada página individualmente não resolve o problema, é necessário reduzir o tamanho dos arquivos finais com dados. Se o tamanho da página deixar de ser fixo, não podemos mais escrever as páginas em um arquivo uma após a outra, pois isso pode gerar uma série de problemas:

  • Não seremos capazes de calcular o offset onde a página está localizada no arquivo usando o índice da página.
  • Não está claro o que fazer com as páginas que não estão no final do arquivo e que mudam de tamanho. Se o tamanho da página diminui, o espaço que foi liberado é perdido. Se o tamanho da página aumenta, um novo local precisa ser encontrado no arquivo.
  • Se a página se deslocar por um número de bytes que não seja múltiplo do tamanho do bloco do sistema de arquivos, a leitura ou gravação exigirá o acesso a um bloco adicional do sistema de arquivos, o que pode levar à degradação do desempenho.

Para evitar resolver esses problemas em seu nível, a compressão de página de disco no Apache Ignite utiliza um mecanismo do sistema de arquivos conhecido como arquivos esparsos. Um arquivo esparso é aquele em que algumas regiões preenchidas com zeros podem ser marcadas como "buracos". Assim, blocos do sistema de arquivos não são alocados para armazenar esses buracos, resultando em economia de espaço no disco.

É lógico que, para liberar um bloco do sistema de arquivos, o tamanho do buraco deve ser maior ou igual ao bloco do sistema de arquivos, o que impõe uma restrição adicional ao tamanho da página no Apache Ignite: para que a compressão tenha algum efeito, é necessário que o tamanho da página seja estritamente maior que o tamanho do bloco do sistema de arquivos. Se o tamanho da página for igual ao tamanho do bloco, nunca conseguiremos liberar um único bloco, pois, para liberar um único bloco, a página comprimida deve ocupar 0 bytes. Se o tamanho da página for igual ao tamanho de 2 ou 4 blocos, já poderemos liberar pelo menos um bloco se nossa página se comprimir em pelo menos 50% ou 75%, respectivamente.

Assim, a descrição final do funcionamento do mecanismo é a seguinte: ao gravar uma página no disco, é realizada uma tentativa de compactar a página. Se o tamanho da página compactada permitir a liberação de um ou mais blocos do sistema de arquivos, a página é gravada de forma compactada, e no lugar dos blocos liberados é criada uma "brecha" (é executada uma chamada de sistema fallocate() com a flag "punch hole"). Se o tamanho da página compactada não permitir a liberação de blocos, a página é mantida como está, em formato não compactado. Todos os offsets das páginas são considerados da mesma forma que sem compressão, multiplicando o índice da página pelo tamanho da página. Nenhum realocamento das páginas é necessário. Os offsets das páginas e como sem compressão atingem as bordas dos blocos do sistema de arquivos.

Compactação de dados no Apache Ignite. Experiência do Sber

Na implementação atual, o Ignite pode trabalhar com arquivos sparse apenas no sistema operacional Linux, portanto a compressão de páginas no disco pode ser ativada somente ao utilizar o Ignite nesse sistema operacional.

Os algoritmos de compressão que podem ser usados para compressão de páginas no disco são: ZSTD, LZ4, Snappy. Além disso, existe um modo de operação (SKIP_GARBAGE), onde apenas o espaço não utilizado na página é descartado sem aplicar compressão nos dados restantes, o que permite reduzir a carga no CPU em comparação com os algoritmos mencionados anteriormente.

Impacto no desempenho

Infelizmente, não realizei medições reais de desempenho em ambientes reais, pois não planejamos usar esse mecanismo em produção, mas podemos discutir teoricamente onde perderemos e onde ganharemos.

Para isso, precisamos lembrar como ocorre a leitura e a gravação de páginas ao acessá-las:

  • Durante a operação de leitura, primeiro realiza-se a busca na RAM; se a busca falhar, a página é carregada na RAM a partir do disco pelo mesmo fluxo que realiza a leitura.
  • Ao realizar a operação de gravação, a página na RAM é marcada como suja, e a gravação física da página no disco não ocorre imediatamente no fluxo que está gravando. Todas as páginas sujas são salvas no disco posteriormente, no processo de checkpoint, por fluxos separados.

Assim, o impacto nas operações de leitura é:

  • Positivo (disk IO), devido à redução no número de blocos lidos do sistema de arquivos.
  • Negativo (CPU), devido à carga adicional necessária para o funcionamento do sistema operacional com arquivos sparse. Também podem surgir implicitamente operações de I/O adicionais para salvar uma estrutura de arquivo sparse mais complexa (infelizmente, não estou familiarizado com todos os detalhes do funcionamento de arquivos sparse).
  • Negativo (CPU), devido à necessidade de descompressão das páginas.
  • Não há influência nas operações de gravação.
  • Influência no processo de checkpoint (aqui é tudo semelhante às operações de leitura):
  • Positivo (disk I/O), devido à redução do número de blocos gravados no sistema de arquivos.
  • Negativo (CPU, talvez disk I/O), devido ao trabalho com arquivos sparse.
  • Negativo (CPU), devido à necessidade de compactação das páginas.

Qual lado da balança irá pender? Isso depende muito do ambiente, mas eu inclino-me a acreditar que a compressão de páginas no disco levará a uma degradação de desempenho na maioria dos sistemas. Além disso, os testes em outros SGBDs que utilizam uma abordagem semelhante com arquivos sparse mostram uma queda de desempenho quando a compressão está ativada.

Como habilitar e configurar

Como já mencionado acima, a versão mínima do Apache Ignite que suporta a compressão de páginas no disco é a 2.8 e é suportada apenas pelo sistema operacional Linux. A ativação e configuração são feitas da seguinte forma:

  • No class-path deve haver o módulo ignite-compression. Por padrão, ele está no pacote do Apache Ignite na diretório libs/optional e não está incluído no class-path. Você pode simplesmente mover o diretório para um nível acima em libs e assim, ao executar através do ignite.sh, ele será incluído automaticamente.
  • A persistência deve estar ativada (Ativada através de DataRegionConfiguration.setPersistenceEnabled(true)).
  • O tamanho da página deve ser maior que o tamanho do bloco do sistema de arquivos (pode ser definido com DataStorageConfiguration.setPageSize() ).
  • Para cada cache, cujos dados precisam ser comprimidos, é necessário configurar o método de compressão na configuração e (opcionalmente) o nível de compressão (métodos CacheConfiguration.setDiskPageCompression(), CacheConfiguration.setDiskPageCompressionLevel()).

Compaction WAL

Como funciona

O que é WAL e para que serve? Muito resumidamente: é um diário onde ficam registrados todos os eventos que, no final, alteram o armazenamento em páginas. Sua principal finalidade é permitir a recuperação em caso de falha. Qualquer operação, antes de devolver o controle ao usuário, deve primeiro registrar um evento no WAL, para que, em caso de falha, seja possível reproduzir o diário e restaurar todas as operações que o usuário recebeu uma resposta bem-sucedida, mesmo que essas operações não tenham conseguido ser refletidas no armazenamento em páginas no disco (como já foi mencionado, a gravação real no armazenamento em páginas é realizada em um processo chamado 'checkpoint', com algum atraso por diferentes threads).

As gravações no WAL são divididas em lógicas e físicas. As lógicas são as próprias chaves e valores. As físicas refletem as alterações nas páginas do armazenamento em páginas. Enquanto as gravações lógicas podem ser úteis em outros casos, as gravações físicas são necessárias apenas para a recuperação em caso de falha e apenas precisam das gravações desde o último checkpoint bem-sucedido. Aqui não iremos nos aprofundar em detalhes e explicar por que isso funciona desse jeito, mas quem estiver interessado pode consultar o artigo mencionado no Apache Ignite Wiki: Ignite Persistent Store — por trás dos bastidores.

Para uma gravação lógica, muitas vezes há várias gravações físicas. Ou seja, por exemplo, uma operação de put em cache afeta várias páginas na memória em páginas (a página com os próprios dados, páginas com índices, páginas com free-lists). Em alguns testes sintéticos, percebi que as gravações físicas ocupavam até 90% do volume do arquivo WAL. Ao mesmo tempo, elas são necessárias por um período muito curto (por padrão, o intervalo entre checkpoints é de 3 minutos). Seria lógico descartar esses dados após a perda de sua relevância. É exatamente isso que o mecanismo de compactação do WAL realiza, eliminando gravações físicas e comprimindo as gravações lógicas restantes com o auxílio de zip, reduzindo significativamente o tamanho do arquivo (às vezes em dezenas de vezes).

Fisicamente, o WAL consiste em vários segmentos (por padrão, 10) de tamanho fixo (por padrão, 64MB), que são sobrescritos em um ciclo. Assim que o segmento atual é preenchido, o próximo segmento é designado como o atual, e o segmento preenchido é copiado para um arquivo separado por um fluxo distinto. A compactação do WAL já funciona com segmentos arquivados. Além disso, por meio de um fluxo separado, ele monitora a execução de checkpoints e inicia a compactação nos segmentos arquivados, cujas gravações físicas já não são necessárias.

Compactação de dados no Apache Ignite. Experiência do Sber

Impacto no desempenho

Como a compactação do WAL opera em um fluxo separado, não deve haver influência direta nas operações em execução. No entanto, ela ainda impõe uma carga adicional ao CPU (compactação) e ao disco (leitura de cada segmento do WAL do arquivo e gravação dos segmentos compactados), portanto, se o sistema estiver operando no limite, isso também resultará em degradação de desempenho.

Como habilitar e configurar

A compactação do WAL pode ser ativada por meio da propriedade WalCompactionEnabled em DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Além disso, usando o método DataStorageConfiguration.setWalCompactionLevel(), é possível definir o nível de compactação, se o valor padrão (BEST_SPEED) não for satisfatório.

Compressão de snapshot da página WAL

Como funciona

Já foi determinado anteriormente que as gravações WAL se dividem em lógicas e físicas. Para cada alteração em cada página na memória de páginas, uma gravação física WAL é gerada. As gravações físicas, por sua vez, também se dividem em 2 subtipos: gravação de instantânea de página e gravação delta. Sempre que alteramos algo na página e a traduzimos de um estado limpo para sujo, uma cópia completa dessa página é armazenada no WAL (gravação de instantânea de página — snapshot da página). Mesmo que tenhamos mudado apenas um byte, no WAL será mantida uma gravação um pouco maior que o tamanho da página. Se alterarmos algo em uma página já suja, uma gravação delta é gerada no WAL, a qual reflete apenas as mudanças em relação ao estado anterior da página, e não a página inteira. Como a limpeza do estado das páginas de sujo para limpo ocorre durante o processo de checkpoint, imediatamente após o início do checkpoint, praticamente todas as gravações físicas consistirão apenas em snapshots de páginas (uma vez que todas as páginas imediatamente após o início do checkpoint estão limpas), então, à medida que nos aproximamos do próximo checkpoint, a proporção de gravações delta começa a crescer e é redefinida no início do próximo checkpoint. Medições em alguns testes sintéticos mostraram que a proporção de snapshots de páginas no volume total de gravações físicas atinge 90%.

A ideia da compressão de snapshots de página WAL consiste em comprimir os snapshots de páginas usando uma ferramenta já pronta para a compressão de páginas (ver compressão de páginas em disco). Nesse caso, as gravações WAL são mantidas sequencialmente em modo append-only, e não há necessidade de vincular as gravações aos limites dos blocos do sistema de arquivos, portanto, aqui, ao contrário do mecanismo de compressão de páginas em disco, não precisamos de arquivos sparses, o que significa que esse mecanismo funcionará não apenas em sistemas operacionais Linux. Além disso, não importa o quão eficientemente conseguimos comprimir a página. Mesmo que liberemos 1 byte, isso já é um resultado positivo, e podemos armazenar dados comprimidos no WAL, ao contrário da compressão de páginas em disco, onde armazenamos uma página comprimida apenas se liberamos mais de 1 bloco do sistema de arquivos.

As páginas são dados bem compactáveis, e sua participação no volume total de WAL é muito alta; assim, mantendo o formato do arquivo WAL, podemos obter uma redução significativa em seu tamanho. A compressão, incluindo registros lógicos, exigiria uma mudança de formato e perda de compatibilidade, por exemplo, para consumidores externos interessados em registros lógicos, sem trazer uma diminuição significativa no volume do arquivo.

Assim como na compressão de página de disco, para a compressão de instantâneos de página WAL podem ser utilizados os algoritmos de compressão ZSTD, LZ4, Snappy, bem como o modo SKIP_GARBAGE.

Impacto no desempenho

Como não é difícil perceber, a ativação direta da compressão de instantâneos de página WAL afeta apenas os fluxos que gravam dados na memória de página, ou seja, aqueles que modificam dados nos caches. A leitura de registros físicos WAL ocorre apenas uma vez, no momento da inicialização do nó após uma falha (e somente no caso de uma falha durante o checkpoint).

Isso afeta os fluxos que modificam dados da seguinte forma: obtemos um efeito negativo (CPU) devido à necessidade de comprimir a página antes de gravá-la no disco e um efeito positivo (disco IO) devido à redução da quantidade de dados gravados. Assim, aqui tudo é simples: se o desempenho do sistema é limitado pela CPU, obtemos uma pequena degradação; se for pelo disco, obtemos um aumento.

Indiretamente, a redução no tamanho do WAL também impacta (positivamente) os fluxos que arquivam segmentos WAL e nos fluxos de compactação WAL.

Testes de desempenho reais em nosso ambiente com dados sintéticos mostraram um pequeno aumento (a taxa de transferência aumentou em 10%-15%, a latência diminuiu em 10%-15%).

Como habilitar e configurar

A versão mínima do Apache Ignite: 2.8. A ativação e configuração são realizadas da seguinte forma:

  • No class-path deve haver o módulo ignite-compression. Por padrão, ele está no pacote do Apache Ignite na diretório libs/optional e não está incluído no class-path. Você pode simplesmente mover o diretório para um nível acima em libs e assim, ao executar através do ignite.sh, ele será incluído automaticamente.
  • A persistência deve estar ativada (Ativada através de DataRegionConfiguration.setPersistenceEnabled(true)).
  • O modo de compressão deve ser definido através do método DataStorageConfiguration.setWalPageCompression(), por padrão a compressão está desativada (modo DISABLED).
  • Opcionalmente, é possível definir o nível de compressão usando o método DataStorageConfiguration.setWalPageCompression(), os valores permitidos para cada um dos modos estão disponíveis na javadoc para o método.

Conclusão

Os mecanismos de compressão de dados discutidos no Apache Ignite podem ser utilizados de forma independente, mas também são permitidas combinações de todos eles. Compreender os princípios de funcionamento permitirá determinar quão adequados são para suas necessidades em seu ambiente e o que será necessário sacrificar ao utilizá-los. A compressão de página em disco é destinada a comprimir o armazenamento principal e pode oferecer um nível médio de compressão. A compressão de instantâneos de página WAL proporcionará um nível médio de compressão para os arquivos WAL, possivelmente até aumentando o desempenho. A compactação do WAL não afetará positivamente o desempenho, mas reduzirá ao máximo o tamanho dos arquivos WAL, eliminando registros físicos.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster