A lição descrita neste guia pode ser aplicada a outras implementações de armazenamento de objetos com suas próprias funções de tabela dedicadas, como GCS e Azure Blob storage.
Mecanismos de inserção (nó único)
Tamanho do bloco de inserção
INSERT INTO SELECT, o ClickHouse recebe uma porção de dados e ① forma, a partir dos dados recebidos, (pelo menos) um bloco de inserção em memória (por chave de particionamento). Os dados do bloco são ordenados, e otimizações específicas do motor de tabela são aplicadas. Em seguida, os dados são compactados e ② gravados no armazenamento do banco de dados na forma de uma nova parte de dados.
O tamanho do bloco de inserção afeta tanto o uso de E/S de arquivos em disco quanto o uso de memória de um servidor ClickHouse. Blocos de inserção maiores consomem mais memória, mas geram partes iniciais maiores e em menor quantidade. Quanto menos partes o ClickHouse precisar criar para carregar um grande volume de dados, menor será a E/S de arquivos em disco e menor será a necessidade de mesclagens automáticas em segundo plano.
Ao usar uma consulta INSERT INTO SELECT em combinação com um motor de tabela de integração ou uma função de tabela, os dados são extraídos pelo servidor ClickHouse:
Até que os dados sejam totalmente carregados, o servidor executa um loop:
min_insert_block_size_rows(padrão:1048545linhas)min_insert_block_size_bytes(padrão:256 MiB)
min_insert_block_size_bytes indica o tamanho do bloco não compactado em memória (e não o tamanho da parte compactada em disco). Além disso, observe que os blocos e partes criados raramente contêm exatamente a quantidade configurada de linhas ou bytes, porque o ClickHouse faz streaming e processa os dados em blocos de linhas. Portanto, essas configurações especificam limites mínimos.
Atenção às mesclagens
MergeTree).
Paralelismo de inserção
s3 permitem especificar conjuntos de nomes de arquivos a serem carregados por meio de padrões glob. Quando um padrão glob corresponde a vários arquivos existentes, o ClickHouse pode paralelizar as leituras entre esses arquivos e dentro de cada arquivo e inserir os dados em paralelo em uma tabela, utilizando threads de insert executadas em paralelo (por servidor):
Até que todos os dados de todos os arquivos sejam processados, cada thread de insert executa um loop:
max_insert_threads. O valor padrão é 1 no ClickHouse open-source e 4 no ClickHouse Cloud.
Com um grande número de arquivos, o processamento paralelo por várias threads de insert funciona bem. Ele pode saturar completamente tanto os núcleos de CPU disponíveis quanto a largura de banda da rede (para downloads paralelos de arquivos). Em cenários em que apenas alguns arquivos grandes serão carregados em uma tabela, o ClickHouse estabelece automaticamente um alto nível de paralelismo no processamento de dados e otimiza o uso da largura de banda da rede criando threads de leitura adicionais por thread de insert para ler (baixar) em paralelo mais intervalos distintos dentro de arquivos grandes.
Para a função s3 e a tabela s3, o download paralelo de um arquivo individual é determinado pelos valores max_download_threads e max_download_buffer_size. Os arquivos só serão baixados em paralelo se o tamanho deles for maior que 2 * max_download_buffer_size. Por padrão, max_download_buffer_size é definido como 10MiB. Em alguns casos, você pode aumentar com segurança esse tamanho de buffer para 50 MB (max_download_buffer_size=52428800), com o objetivo de garantir que cada arquivo seja baixado por uma única thread. Isso pode reduzir o tempo que cada thread gasta fazendo chamadas ao S3 e, assim, também diminuir o tempo de espera no S3. Além disso, para arquivos pequenos demais para leitura paralela, o ClickHouse faz automaticamente a pré-busca de dados, pré-lendo esses arquivos de forma assíncrona para aumentar a taxa de transferência.
Medição de desempenho
Impacto da capacidade do hardware
- o tamanho inicial das partes suportado
- o nível possível de paralelismo de inserção
- a taxa de transferência das mesclagens de partes em segundo plano
Localidade regional
Formatos
s3 e o engine S3. Ao ler arquivos brutos, alguns desses formatos têm vantagens específicas:
- Formatos com nomes de coluna codificados, como Native, Parquet, CSVWithNames e TabSeparatedWithNames, tornam as consultas menos verbosas, já que o usuário não precisa especificar o nome da coluna na função
s3. Os nomes das colunas permitem inferir essa informação. - Os formatos diferem em desempenho em termos de taxas de leitura e gravação. Native e Parquet são os formatos mais adequados para desempenho de leitura, pois já são orientados a colunas e mais compactos. O formato Native também se beneficia do alinhamento com a forma como o ClickHouse armazena dados na memória, reduzindo assim a sobrecarga de processamento à medida que os dados são transmitidos para o ClickHouse.
- O tamanho do bloco frequentemente afeta a latência das leituras em arquivos grandes. Isso fica muito evidente se você apenas amostrar os dados, por exemplo, retornando as N primeiras linhas. No caso de formatos como CSV e TSV, os arquivos precisam ser analisados para retornar um conjunto de linhas. Por isso, formatos como Native e Parquet permitem uma amostragem mais rápida.
- Cada formato de compressão traz prós e contras, geralmente equilibrando o nível de compressão e a velocidade, com ênfase no desempenho de compressão ou descompressão. Ao comprimir arquivos brutos como CSV ou TSV, o lz4 oferece o melhor desempenho de descompressão, em troca de um nível de compressão menor. O Gzip normalmente comprime melhor, à custa de velocidades de leitura um pouco mais lentas. O Xz leva isso ainda mais longe, geralmente oferecendo a melhor compressão, mas com o pior desempenho de compressão e descompressão. Ao exportar, Gz e lz4 oferecem velocidades de compressão comparáveis. Equilibre isso com a velocidade da sua conexão. Quaisquer ganhos com compressão ou descompressão mais rápidas serão facilmente anulados por uma conexão mais lenta com seus buckets do S3.
- Formatos como Native ou Parquet normalmente não justificam a sobrecarga da compressão. Qualquer redução no tamanho dos dados provavelmente será mínima, já que esses formatos são inerentemente compactos. O tempo gasto para comprimir e descomprimir raramente compensará o tempo de transferência pela rede, especialmente porque o S3 está disponível globalmente com maior largura de banda de rede.
Conjunto de dados de exemplo
SELECT, em que grandes volumes de dados são retornados ao cliente, use o formato Null nas consultas ou direcione os resultados para o motor Null. Isso deve evitar que o cliente fique sobrecarregado com dados e que a rede fique saturada.
Ao executar consultas de leitura, a consulta inicial muitas vezes pode parecer mais lenta do que quando a mesma consulta é repetida. Isso pode ser atribuído tanto ao cache do próprio S3 quanto ao ClickHouse Schema Inference Cache. Ele armazena o esquema inferido dos arquivos e permite pular a etapa de inferência em acessos subsequentes, reduzindo assim o tempo de execução da consulta.
Usando threads para leituras
- Em geral, o valor padrão de
max_threadsé suficiente, ou seja, o número de núcleos. Se a quantidade de memória usada por uma consulta for alta e precisar ser reduzida, ou se oLIMITdos resultados for baixo, esse valor pode ser diminuído. Usuários com bastante memória podem querer testar um aumento desse valor para obter maior throughput de leitura no S3. Normalmente, isso só traz benefício em máquinas com menos núcleos, ou seja, < 10. O ganho de paralelização adicional geralmente diminui à medida que outros recursos passam a ser gargalos, por exemplo, contenção de rede e CPU. - Versões do ClickHouse anteriores à 22.3.1 só paralelizavam leituras entre vários arquivos ao usar a função
s3ou o motor de tabelaS3. Isso exigia que o usuário garantisse que os arquivos fossem divididos em fragmentos no S3 e lidos usando um padrão glob para alcançar o melhor desempenho de leitura. Versões mais recentes agora paralelizam downloads dentro de um mesmo arquivo. - Em cenários com poucas threads, pode ser vantajoso definir
remote_filesystem_read_methodcomo “read” para fazer com que a leitura dos arquivos do S3 ocorra de forma síncrona. - Para a função
s3e a tabela, o download paralelo de um arquivo individual é determinado pelos valores demax_download_threadsemax_download_buffer_size. Emboramax_download_threadscontrole o número de threads usadas, os arquivos só serão baixados em paralelo se seu tamanho for maior que 2 *max_download_buffer_size. Por padrão,max_download_buffer_sizeé definido como 10MiB. Em alguns casos, você pode aumentar com segurança esse tamanho de buffer para 50 MB (max_download_buffer_size=52428800), com o objetivo de garantir que arquivos menores sejam baixados por apenas uma thread. Isso pode reduzir o tempo que cada thread gasta fazendo chamadas ao S3 e, assim, também diminuir o tempo de espera no S3. Veja esta postagem do blog para um exemplo disso.
system.query_log.
Considere nossa consulta anterior: dobrar max_threads para 16 (o valor padrão de max_threads é o número de núcleos em um nó) melhora em 2x o desempenho da consulta de leitura, ao custo de maior uso de memória. Aumentar ainda mais max_threads traz ganhos decrescentes, como mostrado.
Ajustando threads e o tamanho do bloco de inserção para inserts
- Quanto maior configurarmos o tamanho do bloco de inserção, menos partes o ClickHouse terá de criar, e menos I/O de arquivos em disco e mesclagens em segundo plano serão necessários.
- Quanto maior configurarmos o número de threads paralelas de insert, mais rapidamente os dados serão processados.
min_insert_block_size_rows como 0 (para desativar o limite baseado em linhas), ao mesmo tempo em que define max_insert_threads com o valor escolhido e min_insert_block_size_bytes com o resultado calculado pela fórmula acima.
Usando esta fórmula com nosso exemplo anterior do Stack Overflow.
max_insert_threads=4(8 núcleos por nó)peak_memory_usage_in_bytes- 32 GiB (100% dos recursos do nó) ou34359738368bytes.min_insert_block_size_bytes=34359738368/(3*4) = 2863311530
33%. Fica a cargo do leitor verificar se é possível melhorar ainda mais o desempenho em um único nó.
Escalonamento de recursos e nós
Escalonamento vertical
Nós individuais também podem ter o desempenho limitado pela rede e por solicitações GET ao S3, o que impede o escalonamento vertical linear do desempenho.
Escalonamento horizontal
s3Cluster, conforme descrito em Utilizando clusters. Isso permite distribuir as leituras entre os nós.
O servidor que recebe inicialmente a consulta de insert primeiro resolve o padrão glob e, em seguida, distribui dinamicamente o processamento de cada arquivo correspondente entre si e os outros servidores.
Repetimos nossa consulta de leitura anterior, distribuindo a carga de trabalho entre 3 nós e ajustando a consulta para usar s3Cluster. Isso é feito automaticamente no ClickHouse Cloud ao fazer referência ao cluster default.
Como observado em Utilizando clusters, esse trabalho é distribuído no nível dos arquivos. Para se beneficiar desse recurso, você precisará de um número suficiente de arquivos, ou seja, pelo menos > o número de nós.
s3Cluster, as inserções ocorrerão no nó iniciador. Isso significa que, embora as leituras ocorram em cada nó, as linhas resultantes serão roteadas para o iniciador para distribuição. Em cenários de alta taxa de transferência, isso pode se tornar um gargalo. Para resolver isso, defina o parâmetro parallel_distributed_insert_select para a função s3cluster.
Definir isso como parallel_distributed_insert_select=2 garante que SELECT e INSERT sejam executados em cada shard, de/para a tabela subjacente da engine Distributed em cada nó.
Ajustes adicionais
Desativar a desduplicação
INSERT INTO SELECT a partir do armazenamento de objetos. Ao desativar essa funcionalidade no momento do insert, podemos melhorar o desempenho, como mostrado abaixo:
Otimização na inserção
optimize_on_insert controla se as partes de dados são mescladas durante o processo de inserção. Quando está habilitada (optimize_on_insert = 1 por padrão), partes pequenas são mescladas em partes maiores à medida que são inseridas, melhorando o desempenho das consultas ao reduzir o número de partes que precisam ser lidas. No entanto, essa mesclagem adiciona sobrecarga ao processo de inserção, o que pode deixar inserções com alta vazão mais lentas.
Desabilitar essa configuração (optimize_on_insert = 0) faz com que a mesclagem durante as inserções seja ignorada, permitindo que os dados sejam gravados mais rapidamente, especialmente ao lidar com inserções pequenas e frequentes. O processo de mesclagem é adiado para segundo plano, permitindo melhor desempenho de inserção, mas aumentando temporariamente o número de partes pequenas, o que pode deixar as consultas mais lentas até que a mesclagem em segundo plano seja concluída. Essa configuração é ideal quando o desempenho de inserção é prioridade e o processo de mesclagem em segundo plano pode cuidar da otimização de forma eficiente depois. Como mostrado abaixo, desabilitar essa configuração pode melhorar a vazão de inserção:
Notas diversas
- Em cenários com pouca memória, considere reduzir
max_insert_delayed_streams_for_parallel_writese estiver inserindo no S3.