ClickHouse para usuários avançados em perguntas e respostas

Em abril, os engenheiros da Avito se reuniram em um encontro online com o principal desenvolvedor do ClickHouse, Alexey Milovidov, e Kirill Shvakov, desenvolvedor Golang da empresa Integros. Discutiram como utilizamos o sistema de gerenciamento de banco de dados e quais desafios enfrentamos.

Como resultado da reunião, compilamos um artigo com respostas de especialistas às nossas perguntas e às perguntas do público sobre backups, resharding de dados, dicionários externos, driver Golang e atualização de versões do ClickHouse. Ele pode ser útil para desenvolvedores que já estão trabalhando ativamente com o SGBD da Yandex e estão interessados no seu presente e futuro. Por padrão, as respostas de Alexey Milovidov, a menos que indicado de outra forma.

Cuidado, há muito texto a seguir. Esperamos que o conteúdo com as perguntas ajude você a se orientar.

ClickHouse para usuários avançados em perguntas e respostas

Conteúdo

Se você não quiser ler o texto, pode assistir à gravação da reunião em nosso canal do youtube. Os timestamps estão no primeiro comentário abaixo do vídeo.

O ClickHouse está em constante atualização, mas nossos dados não. O que fazer com isso?

O ClickHouse está sempre se atualizando, enquanto nossos dados que foram otimizados com a função final não estão sendo atualizados e estão armazenados em backup.

Vamos supor que tivemos algum problema e os dados foram perdidos. Decidimos fazer a recuperação, e descobrimos que as antigas partições armazenadas nos servidores de backup estão muito diferentes da versão do ClickHouse que estamos usando atualmente. O que fazer nessa situação, e isso é possível?

A situação em que você recuperou dados de um backup em um formato antigo, mas na nova versão eles não são conectados, não é possível. Nós garantimos que o formato dos dados no ClickHouse sempre permaneça retrocompatível. Isso é muito mais importante do que a retrocompatibilidade em termos de funcionalidade, caso o comportamento de alguma função raramente usada tenha mudado. Os dados armazenados no disco devem sempre ser legíveis pela nova versão do ClickHouse. Essa é uma regra.

Quais são as melhores práticas atualmente para backup de dados do ClickHouse?

Como fazer backups considerando que temos operações de otimização final, enormes bancos de dados de terabytes e dados que são atualizados, digamos, nos últimos três dias, e depois disso não há mais procedimentos com eles?

Podemos criar nossa própria solução e escrever um script no bash: colete esses backups de tal maneira. Pode ser que não seja necessário nenhuma solução improvisada, e a bicicleta já foi inventada há muito tempo?

Para começar, em relação às melhores práticas. Meus colegas sempre recomendam, em resposta a perguntas sobre backups, mencionar o serviço "Yandex.Cloud", onde essa tarefa já foi resolvida. Então, aproveite se tiver essa oportunidade.

Não há uma solução completa, cem por cento integrada ao ClickHouse, para backups. Existem algumas opções que podem ser utilizadas. Para obter uma solução completa, será necessário ou fazer um pouco manualmente ou criar wrappers na forma de scripts.

Começarei com as soluções mais simples e terminarei com as mais sofisticadas, dependendo do volume de dados e do tamanho do cluster. Quanto maior o cluster, mais complexa se torna a solução.

Se a tabela com os dados ocupar apenas alguns gigabytes, o backup pode ser feito da seguinte forma:

  1. Salvar a definição das tabelas, ou seja, os metadados — show create table.
  2. Fazer um dump usando o cliente ClickHouse — select * from table para um arquivo. Por padrão, você receberá um arquivo no formato TabSeparated. Se desejar algo mais eficiente, pode ser no formato Native.

Se o volume de dados for maior, o backup levará mais tempo e ocupará mais espaço. Isso é chamado de backup lógico e não está vinculado ao formato de dados do ClickHouse. Caso exista, em última instância, você poderá pegar o backup e carregá-lo no MySQL para recuperação.

Para casos mais avançados, o ClickHouse possui a capacidade embutida de criar snapshots de partições na sistema de arquivos local. Essa funcionalidade está disponível na forma de uma consulta alter table freeze partition. Ou simplesmente alter table freeze — isso cria um snapshot de toda a tabela.

O snapshot será criado de forma consistente para uma tabela em um shard, ou seja, não é possível criar um snapshot consistente para todo o cluster dessa forma. Mas para a maioria das tarefas, essa necessidade não existe, e é suficiente executar a consulta em cada shard para obter um snapshot consistente. Ele é criado na forma de hard links e, portanto, não ocupa espaço adicional. Em seguida, você copia esse snapshot para um servidor de backup ou para o armazenamento que utiliza para backups.

Restaurar esse backup é bastante fácil. Primeiro, você cria as tabelas com base nas definições de tabela existentes. Em seguida, copia os snapshots de partições salvos para Directory-Detached para essas tabelas e executa a consulta attach partition. Essa solução é bastante adequada para volumes de dados muito sérios.

Às vezes, é necessário algo ainda mais robusto — nos casos em que você tem dezenas ou até centenas de terabytes em cada servidor e centenas de servidores. Aqui há uma solução que eu observei com colegas do «Yandex.Metrica». Eu não recomendaria isso para todos — leia e decida por si mesmo se é adequado ou não.

Primeiro, você precisa criar vários servidores com grandes compartimentos de disco. Em seguida, nesses servidores, você deve configurar vários servidores ClickHouse e ajustá-los para que funcionem como mais uma réplica para os mesmos shards. Depois, utilize nesse servidores um sistema de arquivos ou uma ferramenta que permita criar snapshots. Aqui você tem duas opções. A primeira opção é snapshots LVM, a segunda opção é ZFS no Linux.

Após isso, é necessário criar um snapshot todos os dias, ele ocupará um certo espaço. Naturalmente, se os dados mudarem, o volume de espaço irá aumentar com o tempo. Esse snapshot pode ser acessado a qualquer momento para restaurar os dados, é uma solução curiosa. Além disso, deve-se limitar essas réplicas na configuração, para que elas não tentem se tornar líderes.

Será possível organizar um atraso controlado das réplicas em shards?

Neste ano, você planeja criar shards no ClickHouse. Será possível organizar um atraso controlado das réplicas nesses shards? Gostaríamos de usar isso para nos proteger contra cenários negativos com alterações e outras modificações.

É possível fazer algum rollback para as alterações? Por exemplo, em um shard existente, dizer que até aquele momento aplique as modificações, e a partir daquele momento pare de aplicar alterações?

Se uma equipe chegou ao nosso cluster e o quebrou, temos uma réplica condicionada com um atraso de uma hora, onde podemos dizer que sim, vamos usar essa réplica neste momento, mas não aplicaremos as últimas dez minutos de alterações nela?

Para começar, sobre o atraso controlado das réplicas. Esse pedido foi feito pelos usuários, e criamos uma issue no GitHub com o pedido: "Se alguém precisar disso, curta, dê um coração". Ninguém deu like, e a issue foi fechada. No entanto, já é possível obter essa funcionalidade configurando o ClickHouse, mas apenas a partir da versão 20.3.

ClickHouse realiza constantemente a fusão de dados em segundo plano - merge. Quando o merge é concluído, um conjunto de partes de dados é substituído por uma parte maior. Durante esse processo, as partes de dados que existiam anteriormente permanecem no disco por um certo período.

Primeiro, elas continuam a ser mantidas enquanto houver consultas select que as utilizem, a fim de garantir uma operação não bloqueante. Consultas select conseguem ler tranquilamente das partes antigas.

Em segundo lugar, existe um limite de tempo - as partes de dados antigas ficam no disco por oito minutos. Esse período de oito minutos pode ser ajustado e até mesmo transformado em um dia. Isso custará espaço no disco: dependendo do fluxo de dados, é possível que os dados do último dia não apenas dobrem, mas se tornem até cinco vezes maiores. Porém, em caso de um problema sério, você poderá parar o servidor ClickHouse e resolver tudo.

Agora surge a questão de como isso protege contra alters. Aqui é importante olhar mais a fundo, pois nas versões antigas do ClickHouse, o alter funcionava de forma que simplesmente mudava as partes. Há uma parte de dados com alguns arquivos, e fazemos, por exemplo, alter drop column. Nesse caso, essa coluna é fisicamente removida de todas as partes.

Mas a partir da versão 20.3, o mecanismo de alters foi completamente alterado, e agora as partes de dados são sempre imutáveis. Elas não mudam de forma alguma - os alters agora funcionam de forma semelhante aos merges. Em vez de mudar a parte no local, criamos uma nova. Na nova parte, os arquivos que não mudaram tornam-se hard links, e, se removemos alguma coluna, ela simplesmente não estará presente na nova parte. A parte antiga será removida por padrão após oito minutos, e pode-se ajustar as configurações mencionadas anteriormente.

O mesmo se aplica a alters do tipo mutações. Quando você faz alter delete ou alter update, ele não altera a parte, mas cria uma nova. E depois remove a antiga.

O que fazer se a estrutura da tabela mudou?

Como restaurar um backup que foi feito com o esquema antigo? E a segunda pergunta é sobre o caso de snapshots e ferramentas de sistema de arquivos. O Btrfs serve aqui em vez do ZFS no Linux LVM?

Se você fizer attach partition partições com uma estrutura diferente, o ClickHouse informará que isso não é permitido. A solução é a seguinte. Primeiro - crie uma tabela temporária do tipo MergeTree com a estrutura antiga, anexe os dados por meio de attach, faça uma solicitação alter. Depois, você pode copiar ou mover esses dados e fazer attach novamente, ou usar a solicitação alterar tabela mover partição.

Agora, a segunda pergunta é - é possível usar Btrfs? Para começar, se você tem LVM, basta ter instantâneas LVM, e o sistema de arquivos pode ser ext4, isso não importa. Com Btrfs tudo depende da sua experiência em usá-lo. É um sistema de arquivos maduro, mas ainda existem algumas suspeitas sobre como tudo funcionará na prática em um cenário específico. Eu não recomendaria usá-lo, a menos que você tenha Btrfs em produção.

Quais são as melhores práticas atuais para resharding de dados?

A questão sobre re-sharding é complexa e multifacetada. Aqui é possível responder de várias maneiras. Podemos abordar de um lado e dizer que no ClickHouse não há uma possibilidade integrada de re-sharding. Mas temo que essa resposta não satisfaça ninguém. Portanto, podemos abordar de outro lado e dizer que no ClickHouse existem várias maneiras de re-shardear dados.

Se o espaço no cluster estiver acabando ou ele não estiver lidando com a carga, você adiciona novos servidores. Mas esses servidores, por padrão, estão vazios, não há dados neles, sem carga. Você precisa redistribuir os dados para que eles fiquem uniformemente espalhados pelo novo cluster aumentado.

O primeiro método para fazer isso é copiar parte das partições para os novos servidores usando a solicitação alterar tabela buscar partição. Por exemplo, se você tinha partições por meses, você pega o primeiro mês de 2017 e copia para um novo servidor, depois - o terceiro mês copia para algum outro novo servidor. E você faz isso até que fique mais ou menos uniforme.

A transferência pode ser realizada apenas para aquelas partições que não mudam durante a gravação. Para partições recentes, a gravação precisará ser desativada, pois a transferência delas não é atômica. Caso contrário, você obterá duplicatas ou lacunas nos dados. No entanto, esse método é prático e funciona de forma bastante eficaz. Partições comprimidas já prontas são transmitidas pela rede, ou seja, os dados não são recomprimidos ou reencodificados.

Esse método tem uma desvantagem, que depende do esquema de sharding, se você estabeleceu um para esse esquema de sharding, qual era a sua chave de sharding. No seu exemplo para o caso com métricas, a chave de sharding é o hash do caminho. Quando você faz um select na tabela distribuída, ele vai diretamente para todos os shards do cluster e coleta os dados de lá.

Isso significa que, na prática, não importa quais dados estão em qual shard. O principal é que os dados de um determinado caminho estão em um shard, e qual exatamente não é relevante. Nesse caso, a migração de partições prontas se aplica perfeitamente, porque nas consultas select, tanto antes como depois do resharding, a estrutura de dados não importa - você também obterá os dados completos.

Mas há casos mais complicados. Se, a nível de lógica de aplicativo, você está contando com um esquema especial de sharding, onde esse cliente está em um determinado shard, e a consulta pode ser enviada diretamente para lá, e não para a tabela distribuída. Ou se você está usando uma versão suficientemente nova do ClickHouse e ativou a configuração optimize skip unused shards. Neste caso, durante a consulta select, a expressão na seção where será analisada e será calculado a quais shards é necessário ir de acordo com o esquema de sharding. Isso funciona desde que os dados estejam organizados de acordo com esse esquema de sharding. Se você os reorganizou manualmente, a correspondência pode mudar.

Portanto, esse é o método número um. E estou aguardando sua resposta, se esse método se adequa ou se devemos prosseguir.

Vladimir Kolobaev, administrador de sistemas líder na Avito: Alexey, o método que você mencionou não se aplica muito bem quando precisamos distribuir a carga, inclusive na leitura. Podemos pegar uma partição mensal e levar o mês anterior para outro nó, mas quando vier a solicitação por esses dados, estaremos sobrecarregando apenas ela. E gostaríamos de distribuir a carga por todo o cluster, porque, caso contrário, em algum momento toda a carga de leitura será processada por dois shards.

Alexey Milovidov: A resposta aqui é estranha - sim, é ruim, mas pode funcionar. Vou explicar como exatamente. Vale a pena olhar para o cenário de carga que está por trás dos seus dados. Se forem dados de monitoramento, pode-se quase garantir que a grande maioria das solicitações busca por dados recentes.

Você configurou novos servidores, transferiu partições antigas, mas também mudou a forma como os dados recentes são gravados. E os dados recentes estarão distribuídos por todo o cluster. Assim, em apenas cinco minutos, as solicitações dos últimos cinco minutos irão carregar uniformemente o cluster, e após um dia, as solicitações de um dia irão igualmente carregar o cluster. No entanto, as solicitações do mês anterior, infelizmente, irão apenas para parte dos servidores do cluster.

Mas, muitas vezes, você não terá solicitações especificamente para fevereiro de 2019. Provavelmente, se as solicitações vão para 2019, elas serão para todo o ano de 2019 - para um longo intervalo de tempo, e não para um pequeno intervalo. E tais solicitações também poderão carregar o cluster de forma uniforme. Mas, de modo geral, seu comentário está absolutamente correto de que essa é uma solução ad hoc, que não espalha os dados completamente de forma uniforme.

Tenho mais alguns pontos para responder à pergunta. Um deles é sobre como inicialmente criar um esquema de sharding para que a re-shardização seja menos dolorosa. Isso pode não ser sempre possível.

Por exemplo, você tem dados de monitoramento. Os dados de monitoramento crescem por três razões. A primeira é a acumulação de dados históricos. A segunda é o aumento do tráfego. E a terceira é o crescimento do número de coisas que estão sendo monitoradas. Novos microserviços e métricas precisam ser salvos.

É possível que entre eles o maior crescimento esteja relacionado justamente à terceira razão - este é o aumento do uso do monitoramento. E nesse caso, vale a pena examinar a natureza da carga, quais são as principais consultas de select. As principais consultas de select, provavelmente, irão se concentrar em um subconjunto específico de métricas.

Por exemplo, o uso de CPU em alguns servidores por um determinado serviço. Assim, existe um subconjunto de chaves a partir do qual você extrai esses dados. E a própria consulta para esses dados provavelmente é bastante simples e é executada em questão de dezenas de milissegundos. É usado para serviços de monitoramento, para dashboards. Espero que eu esteja entendendo isso corretamente.

Vladimir Kolobaev: A questão é que frequentemente apelamos a dados históricos, pois comparamos a situação atual com o histórico em tempo real. Para nós, é importante ter acesso rápido a grandes volumes de dados, e o ClickHouse faz isso muito bem.

Você está absolutamente certo, a maioria das consultas de leitura que fazemos são do último dia, assim como qualquer sistema de monitoramento. No entanto, também existe uma carga significativa sobre os dados históricos, principalmente proveniente do sistema de alertas, que a cada trinta segundos acessa o ClickHouse e diz: "Me dê os dados das últimas seis semanas. E agora, construa uma média móvel a partir deles, e vamos comparar o valor atual com o histórico."

Eu gostaria de dizer que temos uma pequena tabela para consultas muito recentes, onde armazenamos dados de apenas dois dias, e as principais consultas vão para ela. Para a grande tabela particionada, enviamos apenas grandes consultas históricas.

Alexey Milovidov: Infelizmente, isso não se aplica bem ao seu cenário, mas vou descrever dois esquemas ruins e complexos de particionamento que não devem ser usados, mas que são empregados no serviço de amigos meus.

Há um cluster principal com eventos do 'Yandex.Metrica'. Os eventos incluem visualizações de páginas, cliques e transições. A maioria das consultas é direcionada a um site específico. Você abre o serviço 'Yandex.Metrica', tem um site — avito.ru, acessa o relatório e faz a consulta pelo seu site.

Mas também há outras consultas — analíticas e globais, realizadas por analistas internos. Apenas para esclarecer, os analistas internos fazem consultas apenas sobre os serviços do 'Yandex'. Entretanto, mesmo os serviços do 'Yandex' ocupam uma parte significativa de todos os dados. Essas são consultas não relacionadas a contadores específicos, mas a uma filtragem mais ampla.

Como organizar os dados de forma que tanto a partir de um único contador tudo funcione de maneira eficiente, quanto para consultas globais? A dificuldade ainda reside no fato de que o número de consultas no ClickHouse no cluster "Métricas" é de várias milhares por segundo. Consultas não triviais, por exemplo, não podem ser suportadas por um único servidor ClickHouse com várias milhares de consultas por segundo.

O tamanho do cluster é de seiscentos e poucos servidores. Se apenas esticarmos uma tabela Distributed sobre esse cluster e enviarmos várias milhares de consultas, será ainda pior do que enviá-las para um único servidor. Por outro lado, a opção de que os dados estão uniformemente distribuídos e nós vamos e consultamos em todos os servidores é imediatamente descartada.

Há uma opção diametralmente oposta. Imagine que estamos shardando os dados por sites, e a consulta para um único site irá para um shard. Agora o cluster conseguirá facilmente suportar dez mil consultas por segundo, mas em um único shard, alguma consulta específica funcionará muito lentamente. Ela não poderá mais escalar em termos de capacidade. Especialmente se for um site como avito.ru. Não estou revelando um segredo ao dizer que Avito é um dos sites mais visitados da internet russa. E tratá-lo em um único shard seria uma loucura.

Portanto, o esquema de sharding é organizado de uma forma mais sofisticada. Todo o cluster é dividido em um certo número de pequenos clusters, que chamamos de camadas. Dentro de cada pequeno cluster, há de dez a várias dezenas de shards. E no total, existem trinta e nove desses pequenos clusters.

Como tudo isso escala? O número de pequenos clusters não muda - como eram trinta e nove há alguns anos, assim continuam. Mas dentro de cada um deles, gradualmente aumentamos o número de shards à medida que acumulamos dados. E o esquema de sharding, de maneira geral, é o seguinte: a divisão nesses pequenos clusters é feita por websites e, para entender qual site está em qual cluster, usamos uma base de dados separada no MySQL. Um site - em um pequeno cluster. E dentro dele, o sharding é feito de acordo com os identificadores dos visitantes.

Ao registrar, dividimos pelos restos da divisão do identificador do visitante. Mas, ao adicionar um novo shard, o esquema de sharding muda; continuamos a dividir, mas usando um outro número como divisor. Isso significa que um visitante pode estar, de fato, localizado em vários servidores, e não podemos contar com isso. Isso é feito exclusivamente para que os dados sejam melhor compactados. E nas consultas, vamos à tabela Distributed, que analisa o cluster e se conecta a dezenas de servidores. Assim é este complicado esquema.

Mas minha narrativa estará incompleta se eu não mencionar que abandonamos esse esquema. No novo esquema, mudamos tudo e copiamos todos os dados usando o clickhouse-copier.

No novo esquema, todos os sites são divididos em duas categorias — grandes e pequenos. Não sei como o limite foi escolhido, mas no resultado, os grandes sites são registrados em um cluster, onde há 120 shards com três réplicas em cada um — ou seja, 360 servidores. E o esquema de sharding é tal que qualquer consulta vai imediatamente para todos os shards. Se você abrir qualquer página de relatório para avito.ru no "Yandex.Metrica" agora, a consulta será direcionada a 120 servidores. Existem poucos grandes sites na internet russa. Assim, o número de consultas não chega a mil por segundo, muitas vezes ficando abaixo de cem. Tudo isso é processado com tranquilidade pela tabela Distributed, que cada um deles manipula com 120 servidores.

E o segundo cluster é para sites pequenos. Aqui, o esquema de sharding é baseado no identificador do site, e cada consulta vai exatamente para um shard.

No ClickHouse, existe uma ferramenta chamada clickhouse-copier. Você pode nos contar sobre ela?

Devo dizer que essa solução é mais complexa e um pouco menos eficiente. A vantagem é que ela distribui os dados completamente de acordo com o esquema que você especificar. Mas a desvantagem da ferramenta é que ela não realiza um reshading. Ela copia os dados de um esquema de cluster para outro esquema de cluster.

Isso significa que, para seu funcionamento, você deve ter dois clusters. Eles podem estar localizados nos mesmos servidores, mas, mesmo assim, os dados não serão movidos incrementalmente, mas sim copiados.

Por exemplo, havia quatro servidores, agora são oito. Você cria uma nova tabela Distributed em todos os servidores, novas tabelas locais e inicia o clickhouse-copier, especificando nele o esquema de trabalho, que deve ler dali, aceitar o novo esquema de sharding e transferir os dados para lá. E você precisará de um espaço nos servidores antigos que é uma vez e meia maior do que existe agora, porque os dados antigos devem permanecer neles, e em cima disso chegará metade desses mesmos dados antigos. Se você pensou antecipadamente que os dados precisariam ser resharded e há espaço disponível, então esse método será adequado.

Como funciona internamente o clickhouse-copier? Ele divide todo o trabalho em um conjunto de tarefas para processar uma partição de uma tabela em um shard. Todas essas tarefas podem ser executadas em paralelo, e o clickhouse-copier pode ser executado em diferentes máquinas em várias instâncias, mas o que ele faz para uma partição é nada mais do que um insert select. Os dados são lidos, descompactados, redistribuídos, depois compactados novamente, gravados em algum lugar e reordenados. Esta é uma solução mais pesada.

Você teve uma implementação piloto chamada resharding. O que aconteceu com ela?

Você teve uma coisa piloto em 2017 que se chamava resharding. Há até uma opção no ClickHouse. Pelo que entendi, isso não decolou. Você pode contar o motivo pelo qual isso aconteceu? Parece bastante relevante.

Todo o problema é que, quando se precisa reshardar os dados no local, é necessária uma sincronização bastante complexa para fazê-lo de maneira atômica. Quando começamos a olhar como essa sincronização é estruturada, ficou claro que há problemas fundamentais. E esses problemas fundamentais não são apenas teóricos, mas também começaram a se manifestar na prática na forma de algo que pode ser explicado muito simplesmente - nada funciona.

É possível mesclar todas as partes de dados antes de movê-las para discos lentos?

Pergunta sobre o TTL com a opção move to slow disk no contexto de merges. Existe alguma maneira, além do cron, de mesclar todas as partes em uma antes de mover para discos lentos?

A resposta à pergunta se há uma maneira de automaticamente juntar todos os pedaços em um antes da transferência - não. Eu acho que não há necessidade disso. Não é necessário mesclar todas as partes em uma, mas apenas contar que elas serão transferidas para os discos lentos automaticamente.

Temos dois critérios para a transferência de dados. O primeiro é baseado na utilização. Se o nível atual de armazenamento tiver menos de certa porcentagem de espaço livre, escolhemos um chunk e transferimos para um armazenamento mais lento. Ou melhor, não necessariamente mais lento, mas o próximo — como você configurar.

O segundo critério é o tamanho. Ele trata da transferência de chunks grandes. Você pode ajustar o limite de espaço livre no disco rápido, e os dados serão transferidos automaticamente.

Como migrar para novas versões do ClickHouse se não houver como verificar a compatibilidade com antecedência?

Este tópico é discutido regularmente no chat do Telegram ClickHouse considerando diferentes versões, e mesmo assim. Quão seguro é atualizar da versão 19.11 para 19.16 e, por exemplo, de 19.16 para 20.3. Qual é a melhor forma de migrar para novas versões, sem a possibilidade de verificar previamente a compatibilidade em um sandbox?

Aqui estão algumas regras "douradas". A primeira é ler o changelog. Ele é extenso, mas contém seções específicas sobre mudanças que não são compatíveis com versões anteriores. Não deve-se encarar esses pontos como um sinal vermelho. Normalmente são pequenas incompatibilidades, relacionadas a algumas funcionalidades marginais que, muito provavelmente, você não utiliza.

A segunda é — se não há como verificar a compatibilidade em um sandbox, e você deseja atualizar diretamente em produção, a recomendação é — não faça isso. Primeiro, crie um sandbox e verifique. Se não houver um ambiente de teste, então você provavelmente não é uma empresa muito grande, e é possível copiar parte dos dados para o seu notebook e verificar se tudo funciona corretamente. Você pode até levantar várias réplicas localmente na sua máquina. Ou pode levantar uma nova versão em algum lugar próximo e transferir parte dos dados para lá — ou seja, criar um ambiente de teste improvisado.

Outra regra é — não atualize durante a semana após o lançamento da versão devido à captura de bugs em produção e correções rápidas subsequentes. Vamos entender a numeração das versões ClickHouse para não nos confundirmos.

A versão é 20.3.4. O número 20 representa o ano de lançamento - 2020. Em termos do que está por trás disso, isso não tem significado, então não vamos nos preocupar com isso. Em seguida, temos 20.3. O segundo dígito - neste caso, 3 - aumentamos sempre que lançamos uma versão com alguma nova funcionalidade. Se quisermos adicionar algum recurso ao ClickHouse, somos obrigados a aumentar esse número. Ou seja, na versão 20.4, o ClickHouse funcionará ainda melhor. O terceiro dígito - 20.3.4. Aqui, 4 representa o número de lançamentos de patch em que não adicionamos novas funcionalidades, mas corrigimos bugs. E 4 significa que fizemos isso quatro vezes.

Não pense que isso é algo terrível. Normalmente, o usuário pode instalar a versão mais recente, e ela funcionará sem problemas, com tempo de atividade de um ano. Mas imagine que, em alguma função para processamento de bitmaps, que foi adicionada por nossos colegas chineses, ao passar argumentos incorretos, o servidor falha. Temos que corrigir isso. Lançaremos uma nova versão de patch e o ClickHouse se tornará mais estável.

Se o ClickHouse estiver funcionando em produção e uma nova versão do ClickHouse com recursos adicionais for lançada - por exemplo, 20.4.1 - na primeira vez, não se apresse em implantá-la em produção no primeiro dia. Para que ela é necessária? Se você ainda não estiver usando o ClickHouse, pode instalá-la, e provavelmente tudo ficará bem. Mas se o ClickHouse já estiver funcionando de forma estável, acompanhe os patches e atualizações - quais problemas estamos corrigindo.

Kirill Shvakov: Gostaria de acrescentar um pouco sobre ambientes de teste. Muitos têm medo de ambientes de teste e, por algum motivo, acreditam que, se você tem um grande cluster do ClickHouse, o ambiente de teste também deve ser tão grande ou pelo menos dez vezes menor. Isso não é verdade.

Posso dizer pela minha experiência. Tenho um projeto e lá está o ClickHouse. O nosso ambiente de teste para ele é uma pequena máquina virtual na Hetzner por vinte euros, onde tudo está implementado. Para fazer isso, temos total automação no Ansible, e, portanto, não há diferença, em termos gerais, de onde implantar - em servidores físicos ou simplesmente em máquinas virtuais.

O que pode ser feito? Seria bom incluir no documento do ClickHouse um exemplo de como implantar um pequeno cluster em Docker, LXC ou, possivelmente, criar um playbook do Ansible, pois cada pessoa tem implantações diferentes. Isso facilitaria muito. Quando você pode implantar um cluster em cinco minutos, fica muito mais fácil tentar entender algo. É muito mais conveniente, porque implantar uma versão de produção que você não testou é um caminho sem volta. Às vezes funciona, e outras vezes não. Portanto, contar com o sucesso é problemático.

Maxim Kotiakov, engenheiro backend sênior da Avito: Vou complementar um pouco sobre os ambientes de teste na série de problemas das grandes empresas. Temos um cluster de aceitação ClickHouse, que é uma cópia exata, em termos de esquemas de dados e configurações, do que temos em produção. Este cluster está implantado em contêineres com recursos mínimos. Enviamos uma certa porcentagem dos dados de produção para lá, já que temos a possibilidade de replicar um fluxo no Kafka. Tudo está sincronizado e escalonado - tanto em poder quanto em fluxo, e, em teoria, em condições iguais, deve se comportar como produção em termos de métricas. Todo o potencialmente explosivo é primeiramente levado para esse ambiente e permanece lá por alguns dias até estar pronto. Mas, naturalmente, essa solução é cara, pesada e requer custos de manutenção.

Alexey Milovidov: Vou contar como é o ambiente de teste dos nossos amigos do "Yandex.Metrica". Um cluster tinha mais de 600 servidores, outro 360, e há um terceiro e vários outros clusters. O ambiente de teste para um deles consiste simplesmente em dois shards com duas réplicas em cada. Por que dois shards? Para que não seja apenas um. E as réplicas também precisam existir. Um número mínimo que pode ser permitido.

Esse ambiente de teste permite verificar se as consultas estão funcionando e se algo não quebrou majoritariamente. Mas frequentemente, problemas de outro tipo surgem, quando tudo funciona, mas há algumas pequenas mudanças na carga.

Vou dar um exemplo. Decidimos instalar uma nova versão do ClickHouse. Ela foi disponibilizada no ambiente de teste, passaram por testes automatizados na própria "Yandex.Metrica", que comparam os dados da versão antiga e da nova, executando toda a linha de produção. E, naturalmente, os testes verdes da nossa CI. Caso contrário, não teríamos até mesmo sugerido essa versão.

Está tudo bem. Vamos começar a implementar em produção. Recebo uma mensagem dizendo que a carga nos gráficos aumentou várias vezes. Revertendo a versão. Olho para o gráfico e vejo: a carga realmente aumentou várias vezes durante a implementação e voltou a cair assim que foi feito o rollback. Depois, começamos a reverter a versão. E a carga aumentou da mesma forma e voltou a cair exatamente igual. Portanto, a conclusão é que a carga aumentou devido à implementação, nada surpreendente.

Depois, foi difícil convencer os colegas a realmente instalar a nova versão. Eu digo: "Está tudo certo, lancem. Vamos torcer, tudo funcionará. Agora a carga aumentou nos gráficos, mas está tudo bem. Mantenham-se firmes". Enfim, fizemos isso, e assim — a versão foi implantada em produção. Mas quase a cada nova versão surgem problemas semelhantes.

O comando Kill query deve encerrá-los, mas ele não está fazendo isso. Por quê?

Um usuário, um analista qualquer, veio até mim e criou uma requisição que derrubou meu cluster ClickHouse. Uma determinada nó ou o cluster inteiro — dependendo de em qual réplica ou shard a requisição caiu. Vejo que todos os recursos de CPU nesse servidor estão sobrecarregados, tudo vermelho. Ao mesmo tempo, o ClickHouse responde às requisições. Então, eu escrevo: "Por favor, me mostre a lista de processos, qual requisição gerou essa loucura".

Encontro essa requisição e escrevo para matar. E vejo que nada acontece. Meu servidor está sobrecarregado, o ClickHouse continua me respondendo com alguns comandos, mostrando que o servidor está vivo e tudo parece ótimo. Mas a degradação em todas as requisições dos usuários começa, onde a degradação da gravação no ClickHouse também começa, e meu kill query não está funcionando. Por quê? Eu pensei que o kill query deveria acabar com as requisições, mas isso não acontece.

Agora, a resposta será um pouco estranha. O fato é que o kill query não mata requisições.

O kill query estabelece uma pequena flag chamada "eu quero que esta requisição seja morta". E a própria requisição, ao processar cada bloco, verifica essa flag. Se estiver ativada, a requisição para de funcionar. Ou seja, ninguém mata a requisição; ela mesma deve verificar tudo e parar. E isso deve funcionar em todos os casos em que a requisição está em um estado de processamento de blocos de dados. Ela processará o próximo bloco de dados, verificará a flag e parará.

Isso não funciona em casos onde a solicitação está bloqueada em alguma operação. No entanto, provavelmente esse não é o seu caso, pois, segundo suas palavras, ele utiliza muitos recursos do servidor. Pode ser que isso não funcione em casos de ordenação externa e em alguns outros detalhes. Mas no geral, isso não deveria acontecer, é um bug. A única coisa que posso aconselhar é atualizar o ClickHouse.

Como calcular o tempo de resposta sob carga de leitura?

Há uma tabela onde estão armazenados agregados por item - diferentes contadores. O número de linhas é de aproximadamente cem milhões. É possível esperar um tempo de resposta previsível se injetarmos 1K RPS em 1K itens?

Pelo contexto, parece que estamos falando de carga de leitura, pois não há problemas para gravação - seja mil, cem mil ou até vários milhões de linhas podem ser inseridos.

Consultas de leitura podem ser das mais diversas. No select 1, o ClickHouse pode executar cerca de dezenas de milhares de consultas por segundo, por isso mesmo consultas por uma única chave já requererão alguns recursos. E essas consultas pontuais serão mais complexas do que em bancos de dados tipo key-value, porque para cada leitura é necessário ler um bloco de dados por índice. O índice aqui não endereça cada registro, mas cada intervalo. Ou seja, será necessário ler todo o intervalo - isso é 8192 linhas por padrão. E será preciso descompactar um bloco de dados comprimido de 64 Kb para 1 Mb. Normalmente, essas consultas pontuais levam de alguns milissegundos. Mas essa é a opção mais simples.

Vamos tentar fazer uma aritmética simples. Se multiplicarmos alguns milissegundos por mil, teremos alguns segundos. Como se não fosse possível manter mil solicitações por segundo, mas na verdade é, pois temos vários núcleos de processador. Assim, em tese, o ClickHouse pode ocasionalmente suportar 1000 RPS, mas em solicitações curtas, especificamente pontuais.

Se for necessário escalar o cluster ClickHouse quanto ao número de solicitações simples, recomendo o mais simples - aumentar o número de réplicas e enviar as solicitações para uma réplica aleatória. Se uma réplica suporta quinhentas solicitações por segundo, o que é completamente viável, então três réplicas podem suportar mil e quinhentas.

Às vezes, é possível configurar o ClickHouse para o máximo de leituras pontuais. O que é necessário para isso? Primeiro, é preciso reduzir a granularidade do índice. No entanto, essa redução não deve ser para um, mas com base no cálculo de que o número de registros no índice será de alguns milhões ou dezenas de milhões no servidor. Se a tabela tiver cem milhões de linhas, a granularidade pode ser definida como 64.

É possível diminuir o tamanho do bloco comprimido. Para isso, existem configurações tamanho mínimo do bloco comprimido, tamanho máximo do bloco comprimido. Eles podem ser reduzidos, os dados podem ser reorganizados, e então as consultas pontuais ficarão mais rápidas. Mas ainda assim, o ClickHouse não é um banco de dados key-value. Um grande número de pequenas consultas é um antipadrão de carga.

Kirill Shvakov: Vou dar um conselho para o caso de haver contadores comuns. É uma situação bastante padrão, onde no ClickHouse é armazenado algum contador. Eu tenho um usuário, que é de tal país, mais um terceiro campo, e é necessário incrementar algo. Você pega o MySQL, faz uma chave única — no MySQL é uma chave duplicada, e no PostgreSQL é um conflito — e acrescenta com um mais. Isso funcionará muito melhor.

Quando você tem poucos dados, não faz sentido usar o ClickHouse, especialmente. Existem bancos de dados comuns, e eles lidam bem com isso.

O que ajustar no ClickHouse para que mais dados estejam no cache?

Vamos imaginar uma situação — nos servidores há 256 GB de RAM, na rotina diária, o ClickHouse consome cerca de 60–80 GB, e no pico — até 130. O que se pode ativar e ajustar para que mais dados estejam no cache e, consequentemente, haja menos acessos ao disco?

Como regra, o cache de página do sistema operacional lida bem com essa tarefa. Se você simplesmente abre o top, verifica lá o cached ou free — também está escrito quanto está em cache — você pode notar que toda a memória livre é utilizada para o cache. E esses dados, ao serem lidos, não serão lidos do disco, mas da RAM. Posso afirmar que o cache é utilizado de forma eficiente, porque são justamente os dados comprimidos que estão sendo cacheados.

No entanto, se você quiser acelerar ainda mais algumas consultas simples, há a possibilidade de ativar dentro do ClickHouse um cache de dados descomprimidos. Isso é chamado de cache descomprimidoNo arquivo de configuração config.xml, você define o tamanho do cache não compactado para o valor que precisar - eu aconselho não mais do que metade da memória RAM livre, porque o restante será usado para o cache de páginas.

Além disso, há duas configurações no nível da consulta. A primeira configuração é usar cache não compactado — que ativa seu uso. É recomendado ativá-lo para todas as consultas, exceto para as pesadas, que podem ler todos os dados e limpar esse cache. E a segunda configuração é algo como o número máximo de linhas para o uso do cache. Ela automaticamente limita consultas grandes para que não passem pelo cache.

Como é possível configurar storage_configuration para armazenamento na memória?

Na nova documentação do ClickHouse, encontrei uma seção relacionada ao armazenamento de dados. Na descrição, há um exemplo com SSDs rápidos.

É interessante como configurar a mesma coisa com memória quente em volume. E mais uma pergunta. Como funciona o select com essa organização de dados, ele irá ler todo o conjunto ou apenas o que está no disco, e os dados são comprimidos na memória? E como a seção prewhere funciona nessa organização de dados?

Essa configuração afeta o armazenamento de pedaços de dados, e seu formato não muda.
Vamos examinar isso com mais detalhes.

É possível configurar o armazenamento de dados na memória. Tudo que se configura para o disco é o seu caminho. Você cria uma partição tmpfs, que está montada em algum caminho no sistema de arquivos. Você indica esse caminho como o caminho para armazenar dados para a própria partição quente, e os pedaços de dados começam a ser recebidos e gravados lá, tudo certo.

Mas eu não recomendo fazer isso devido à baixa confiabilidade, embora, se você tiver pelo menos três réplicas em diferentes datacenters, seja possível. Se necessário, os dados poderão ser recuperados. Vamos supor que o servidor de repente desligou e foi ligado novamente. A partição foi montada novamente, mas está vazia. O servidor ClickHouse, ao iniciar, vê que esses pedaços estão ausentes, embora, de acordo com os metadados do ZooKeeper, eles deveriam estar lá. Ele verifica em quais réplicas eles existem, solicita e baixa. Assim, os dados serão recuperados.

Neste sentido, o armazenamento de dados na memória é essencialmente o mesmo que o armazenamento em disco, pois, ao gravar dados no disco, eles também passam primeiro pelo cache de página e são fisicamente gravados de forma adiá. Isso depende da opção de montagem do sistema de arquivos. Mas, só para esclarecer, o ClickHouse não realiza fsync ao inserir.

Os dados na memória são armazenados exatamente no mesmo formato que em disco. A consulta select seleciona exatamente os pedaços que precisam ser lidos, seleciona os intervalos de dados necessários e os lê. E o prewhere funciona absolutamente da mesma forma, independentemente de os dados estarem na memória ou no disco.

Até quantos valores únicos o Low Cardinality é eficaz?

Low Cardinality é astuto. Ele cria dicionários de dados, mas são locais. Primeiro, os dicionários são específicos para cada pedaço e, segundo, mesmo dentro de um único pedaço, eles podem ser diferentes para cada intervalo. Quando o número de valores únicos atinge um número limite — me parece que um milhão — o dicionário é simplesmente deixado de lado, e um novo é criado.

A resposta geral: para cada intervalo local — digamos, para cada dia — até cerca de um milhão de valores únicos, o Low Cardinality é eficaz. Depois, haverá apenas um fallback, onde muitos dicionários diferentes serão usados, em vez de um só. Ele funcionará de maneira semelhante a uma coluna comum do tipo string, talvez um pouco menos eficiente, mas não haverá uma degradação séria no desempenho.

Quais são as melhores práticas para busca de texto completo em uma tabela com cinco bilhões de linhas?

Há várias opções de resposta. A primeira é dizer que o ClickHouse não é um sistema para busca de texto completo. Para isso, existem sistemas especializados, por exemplo, Elasticsearch e Sphinx. No entanto, encontro cada vez mais pessoas que dizem que estão migrando do Elasticsearch para o ClickHouse.

Por que isso acontece? Eles explicam que o Elasticsearch começa a ter dificuldades em lidar com a carga em determinados volumes, especialmente no que diz respeito à construção de índices. Os índices se tornam muito volumosos e, se apenas transferirmos os dados para o ClickHouse, ele armazenará os dados de forma muito mais eficiente em termos de volume. Além disso, muitas vezes as consultas de busca não exigiam encontrar uma frase em todo o volume de dados considerando a morfologia, mas sim algo completamente diferente. Por exemplo, localizar nos logs dos últimos algumas horas com base em uma subsequência de bytes.

Neste caso, você cria um índice no ClickHouse, sendo o primeiro campo a data com hora. E a maior filtragem de dados será exatamente por intervalo de datas. Dentro do intervalo de datas escolhido, geralmente é possível realizar uma busca de texto completo, mesmo com o método brute force, utilizando like. O operador like no ClickHouse é o operador like mais eficiente que você pode encontrar. Se encontrar melhor, me avise.

Mas mesmo assim, like é um full scan. E um full scan pode ser lento não apenas por CPU, mas também pelo disco. Se você tiver um terabyte de dados por dia e está buscando uma palavra em um dia, terá que escanear um terabyte. E isso provavelmente está em discos rígidos normais, e no final ficarão tão carregados que você não conseguirá acessar esse servidor via SSH.

Neste caso, estou pronto para oferecer mais um pequeno truque. É experimental — pode funcionar, pode não funcionar. No ClickHouse, existem índices de texto completo na forma de filtros Bloom trigramas. Nossos colegas da empresa Arenadata já testaram esses índices e muitas vezes eles funcionam da maneira que foram projetados.

Para usá-los corretamente, é necessário entender bem como eles funcionam: o que é um filtro Bloom trigram e como escolher seu tamanho. Posso dizer que eles ajudarão em consultas sobre frases raras, sub-strings que aparecem raramente nos dados. Nesse caso, os índices escolherão subintervalos e menos dados serão lidos.

Recentemente, o ClickHouse introduziu funções ainda mais avançadas para busca de texto completo. Primeiro, isso é a busca de várias sub-strings em uma única passagem, incluindo opções que consideram ou ignoram maiúsculas e minúsculas, com suporte a UTF-8 ou apenas para ASCII. Escolha o mais eficaz que você precisa.

Também houve a busca por várias expressões regulares em uma única passagem. Você não precisa escrever X like uma sub-string or X like outra sub-string. Basta escrever uma vez, e tudo é executado da maneira mais eficiente possível.

Por último, agora há busca aproximada de regex e busca aproximada de sub-strings. Se alguém escreveu uma palavra com erro de digitação, ela será buscada com a máxima correspondência.

Como organizar o acesso ao ClickHouse para um grande número de usuários?

Descreva como organizar melhor o acesso para um grande número de consumidores e analistas. Como formar uma fila, priorizar solicitações de consultas simultâneas máximas e quais ferramentas usar?

Se o cluster é suficientemente grande, uma boa solução seria adicionar mais dois servidores, que se tornariam o ponto de entrada para os analistas. Ou seja, não permitir que os analistas acessem shards específicos do cluster, mas simplesmente criar dois servidores vazios, sem dados, e configurar os direitos de acesso neles. Dessa forma, as configurações de usuários nas consultas distribuídas são transferidas para os servidores remotos. Ou seja, você configura tudo nesses dois servidores, e as configurações têm efeito em todo o cluster.

Esses servidores estão, na verdade, sem dados, mas a quantidade de RAM neles é muito importante para realizar as consultas. O disco também pode ser utilizado para dados temporários, se a agregação ou ordenação externa estiver habilitada.

É importante observar as configurações relacionadas a todos os limites possíveis. Se eu acessar o cluster "Yandex.Metrica" como analista e fizer uma solicitação select count from hits, serei imediatamente informado de que não posso executar a consulta. O número máximo de linhas que me é permitido escanear é cem bilhões, enquanto no cluster há cinquenta trilhões em uma única tabela. Essa é a primeira limitação.

Suponha que eu remova a limitação de número de linhas e execute a consulta novamente. Então eu verei a seguinte exceção — a configuração está habilitada force index by date. Não posso executar a consulta se não especifiquei o intervalo de datas. Não se deve contar que os analistas irão indicar isso manualmente. O caso típico é que o intervalo de datas é escrito como where event date between uma semana. E então simplesmente não foi colocado o parêntese no lugar certo, e em vez de and ficou or — or URL match. Se não houver limitações, ele começará a escanear a coluna de URL e consumirá muitos recursos.

Além disso, no ClickHouse há duas configurações de prioridades. Infelizmente, elas são muito primitivas. Uma delas é simplesmente chamada de prioritySe a prioridade ≠ 0, e as solicitações são feitas com alguma prioridade, mas ao mesmo tempo é feita uma solicitação com uma prioridade que tem um valor menor, o que significa uma prioridade mais alta, então a solicitação com valor de prioridade maior, que denota uma prioridade mais baixa, simplesmente é suspensa e não funcionará durante esse tempo.

Esta é uma configuração muito grosseira, e não é adequada para casos em que há uma carga constante no cluster. Mas se você tiver solicitações curtas e importantes, e em sua maioria o cluster estiver ocioso, essa configuração funcionará.

A próxima configuração de prioridades é chamada de prioridade de thread do SO. Ela simplesmente define para todos os threads de execução da solicitação o valor de nice para o escalonador Linux. Funciona mais ou menos, mas ainda assim funciona. Se você definir o menor valor de nice — que é o maior em tamanho, e significa a prioridade mais baixa — e para as solicitações de alta prioridade definir -19, então a CPU consumirá solicitações de baixa prioridade cerca de quatro vezes menos do que as de alta prioridade.

Também é necessário configurar o tempo máximo de execução da solicitação — digamos, cinco minutos. A velocidade mínima de execução da solicitação — essa é a configuração mais interessante. Esta configuração existe há bastante tempo, e é necessária, não apenas para afirmar que o ClickHouse não está lento, mas para forçar isso.

Imagine que você está configurando: se alguma solicitação processar menos de um milhão de linhas por segundo — isso não pode ser feito. Isso envergonha nosso bom nome, nosso bom banco de dados. Vamos simplesmente proibi-lo. Na verdade, há duas configurações. Uma se chama velocidade mínima de execução — em linhas por segundo, e a outra se chama tempo limite antes de verificar a velocidade mínima de execução — por padrão quinze segundos. Ou seja, quinze segundos são aceitáveis, mas depois, se estiver lento, é só lançar uma exceção — interromper a solicitação.

Também é necessário configurar cotas. No ClickHouse, há um recurso embutido de cotas, que contabiliza o consumo de recursos. Mas, infelizmente, não de recursos físicos como CPU e discos, mas sim lógicos — a quantidade de solicitações processadas, linhas e bytes lidos. E pode-se configurar, por exemplo, um máximo de cem solicitações em cinco minutos e mil solicitações por hora.

Por que isso é importante? Porque parte das consultas analíticas será executada manualmente diretamente no cliente ClickHouse. E tudo ficará bem. Mas se na sua empresa houver analistas avançados, eles escreverão um script, e pode haver um erro nesse script. E esse erro fará com que a consulta seja executada em loop infinito. É disso que precisamos nos proteger.

É possível devolver os resultados de uma consulta para dez clientes?

Temos alguns usuários que gostam de fazer consultas muito grandes ao mesmo tempo. A consulta é grande, executa rapidamente, mas devido ao número elevado de consultas simultâneas, isso se torna um problema. É possível executar a mesma consulta que chegou dez vezes seguidas apenas uma vez e devolver o resultado a dez clientes?

O problema é que não temos resultados de cache ou cache de dados intermediários. Há o cache de página do sistema operacional, que permitirá não ler os dados do disco novamente, mas, infelizmente, os dados ainda precisarão ser descompactados, desserializados e processados novamente.

Gostaríamos de evitar isso de alguma forma, seja fazendo cache de dados intermediários, seja organizando consultas semelhantes em uma fila e adicionando um cache de resultados. Atualmente, temos um pull request em desenvolvimento que adiciona cache de consultas, mas apenas para subconsultas nas seções in e join — ou seja, a solução ainda não é completa.

No entanto, essa situação também ocorre conosco. Um exemplo clássico é o das consultas com paginação. Existe um relatório que tem várias páginas e há uma consulta com limite de 10. Depois o mesmo, mas com limite de 10,10. E depois a próxima página. E pergunta-se, por que estamos calculando tudo isso a cada vez? Mas, no momento, não há solução, e não conseguimos evitar isso.

Há uma solução alternativa que é executada ao lado do ClickHouse — ClickHouse Proxy.

Kirill Shvakov: No ClickHouse Proxy, há um limitador de taxa embutido e um cache de resultados. Muitas configurações foram feitas, pois uma tarefa semelhante foi resolvida. O Proxy permite limitar as consultas, organizando-as em filas e configurando quanto tempo o cache de consultas permanece ativo. Se as consultas forem realmente idênticas, o Proxy as devolverá várias vezes, enquanto consultará o ClickHouse apenas uma vez.

O Nginx também tem cache na versão gratuita, e isso também funcionará. O Nginx até possui configurações que, se as solicitações chegarem simultaneamente, ele irá desacelerar outras até que uma seja concluída. Mas na ClickHouse Proxy, a configuração é feita de forma muito melhor. Foi projetado especificamente para ClickHouse, para esses tipos de solicitações, por isso é mais adequado. E é fácil de configurar.

Como lidar com operações assíncronas e visões materializadas?

Há um problema em que as operações com o motor de replicação são assíncronas - primeiramente, os dados são gravados e, em seguida, ocorre a compactação. Se houver uma tabela materializada abaixo da tabela com alguns agregados, os duplicados serão registrados nela. E se não houver uma lógica complexa, os dados serão duplicados. O que pode ser feito a respeito?

Há uma solução óbvia - implementar um acionador em uma determinada classe de materializações durante a operação assíncrona de compactação. Existem algumas 'balas de prata', planos para implementar funcionalidades semelhantes?

É importante entender como funciona a deduplicação. O que vou explicar agora não está relacionado à pergunta, mas vale a pena lembrar disso.

Ao inserir em uma tabela replicada, há deduplicação de blocos totalmente inseridos. Se você inseriu o mesmo bloco novamente, contendo a mesma quantidade de linhas na mesma ordem, os dados são deduplicados. Você receberá 'Ok' em resposta ao insert, mas, na prática, apenas um lote de dados será gravado, e não haverá duplicações.

Isso é necessário para certeza. Se durante a inserção você receber 'Ok', significa que seus dados foram inseridos. Se você receber um erro do ClickHouse, significa que eles não foram inseridos, e a inserção precisa ser repetida. Mas se a conexão foi interrompida durante a inserção, você não saberá se os dados foram inseridos ou não. A única opção é repetir a inserção. Se os dados realmente foram inseridos e você os inseriu novamente, há deduplicação de blocos. Isso é necessário para evitar duplicatas.

E é importante também como funciona para visualizações materializadas. Se os dados foram deduplicados ao serem inseridos na tabela principal, eles também não irão para a visualização materializada.

Agora sobre a questão. Você tem uma situação mais complexa, porque está registrando duplicatas de linhas individuais. Ou seja, não é um conjunto inteiro que está duplicado, mas sim linhas específicas, e elas se combinam em segundo plano. De fato, os dados se combinarão na tabela principal, e na visualização materializada irão as não combinadas, e durante as mesclagens nada acontecerá com as visualizações materializadas. Porque uma visualização materializada não é nada mais do que um gatilho para insert. Em outras operações, nada adicional acontece com ela.

E eu não posso agradar de forma alguma aqui. Precisamos apenas procurar uma solução específica para este caso. Por exemplo, é possível também fazer o rebranding em uma visualização materializada, e o método de deduplicação pode funcionar igualmente. Mas, infelizmente, nem sempre. Se é agregadora, então não vai funcionar.

Kirill Shvakov: Nós também enfrentamos muitos problemas no passado. Havia um problema com exibições de anúncios, e havia alguns dados que poderíamos mostrar em tempo real — apenas exibições. Elas raramente são duplicadas, mas, se isso acontecer, nós ainda as combinaremos depois. E havia elementos que não podiam ser duplicados — cliques e toda essa história. Mas nós queríamos mostrá-los praticamente imediatamente.

Como foram feitas as visualizações materializadas? Existem visualizações para as quais escrevemos diretamente — vai para os dados brutos e também vai para as visualizações. Em algum momento, os dados lá não são muito corretos, eles se duplicam, e assim por diante. E há uma segunda parte da tabela, onde eles parecem absolutamente iguais às visualizações materializadas, ou seja, em termos de estrutura, são absolutamente idênticos. De tempos em tempos, recalculamos os dados, completamos os dados sem duplicatas e escrevemos nessas tabelas.

Nós passamos pelo API — manualmente no ClickHouse isso não vai funcionar. E o API verifica: quando eu tenho a data da última adição na tabela, onde os dados já estão garantidamente corretos e calculados, e ele faz a solicitação a uma tabela e a outra. De uma, a solicitação seleciona até um determinado momento, e da outra, complementa o que ainda não foi calculado. E isso funciona, mas não com os recursos de um só ClickHouse.

Se você tiver alguma API — para analistas, para usuários — então, em princípio, esta é uma opção. Você sempre conta, sempre recalcula. Isso pode ser feito uma vez por dia ou em qualquer outro momento. Você mesmo escolhe o intervalo que não é necessário e não é crítico para você.

No ClickHouse há muitos logs. Como posso ver tudo que acontece com o servidor em tempo real?

No ClickHouse há uma quantidade muito grande de logs diferentes, e essa quantidade está aumentando. Em versões novas, alguns deles estão até ativados por padrão, enquanto em versões antigas é necessário habilitá-los na atualização. No entanto, eles estão se acumulando cada vez mais. Eu gostaria de ver, no final, o que acontece agora com meu servidor, talvez em um painel resumido.

Você não tem na sua equipe do ClickHouse, ou em equipes de amigos que mantenham alguma funcionalidade de painéis prontos, que mostrariam esses logs como um produto já elaborado? No final das contas, olhar logs no ClickHouse é ótimo. Mas seria muito legal se isso já estivesse preparado na forma de um painel. Eu ficaria muito feliz com isso.

Existem painéis, embora não sejam padronizados. Na nossa empresa, cerca de 60 equipes usam ClickHouse, e o mais estranho é que muitas delas têm painéis que fizeram para si mesmas, e são um pouco diferentes. Algumas equipes usam uma instalação interna do Yandex.Cloud. Existem alguns relatórios prontos, embora não sejam todos necessários. Outras têm suas próprias soluções.

Meus colegas da "Métrica" têm seu próprio painel no Grafana, e eu tenho o meu no mesmo cluster deles. Eu olho para itens como o hit cache para o cache de pontos. E ainda é mais complicado porque usamos ferramentas diferentes. Eu criei meu painel em uma ferramenta muito antiga chamada Graphite-web. Ele é completamente feio. E até hoje ainda uso isso, embora o Grafana provavelmente fosse mais conveniente e bonito.

A coisa básica nos dashboards é a mesma. São as métricas do sistema para o cluster: CPU, memória, disco, rede. Outras métricas incluem o número de solicitações simultâneas, o número de merges simultâneos, o número de solicitações por segundo, o número máximo de partes para as partições das tabelas MergeTree, o atraso de replicação, o tamanho da fila de replicação, o número de linhas inseridas por segundo e o número de blocos inseridos por segundo. Tudo isso é obtido não a partir de logs, mas de métricas.

Vladimir Kolobaev: Alexey, eu gostaria de fazer uma pequena correção. Existe o Grafana. O Grafana possui um datasource que é o ClickHouse. Ou seja, posso fazer solicitações diretamente no ClickHouse a partir do Grafana. No ClickHouse, há uma tabela com logs, que é idêntica para todos. Quero que, no resultado, eu possa acessar essa tabela de logs no Grafana e ver as solicitações feitas pelo meu servidor. Seria ótimo ter um dashboard como esse.

Eu mesmo o criei. Mas tenho uma dúvida — se tudo é padronizado e o Grafana é usado por todos, por que não existe um dashboard oficial no 'Yandex'?

Kirill Shvakov: Na verdade, o datasource para o ClickHouse é atualmente suportado pela Altinity. E eu só quero apontar o caminho para onde investigar e quem pressionar. Pode-se perguntar a eles, porque o 'Yandex', afinal, faz o ClickHouse, e não a história em torno dele. A Altinity é a principal empresa que está promovendo o ClickHouse agora. Eles não vão abandoná-lo e continuarão a oferecer suporte. Porque, em princípio, para carregar um dashboard no site do Grafana, você só precisa se registrar e carregá-lo - não há muitos problemas.

Alexey Milovidov: No último ano, muitas funcionalidades foram adicionadas ao ClickHouse para o perfilamento de consultas. Existem métricas para cada consulta sobre o uso de recursos. E recentemente foi adicionado um profiler de consultas de nível ainda mais baixo, para visualizar onde cada consulta consome cada milissegundo. Mas para usufruir dessa funcionalidade, eu tenho que abrir o cliente de console e digitar a consulta que sempre esqueço. Eu a salvei em algum lugar e constantemente esqueço onde exatamente.

Eu gostaria de ter uma ferramenta que mostrasse simplesmente - aqui estão suas consultas pesadas, agrupadas por classes de consultas. Clique em uma delas e eu gostaria que me dissessem que ela é pesada por isso. No momento, não há tal solução. E realmente é bem estranho que, quando as pessoas me perguntam: 'Diga, existem painéis prontos para Grafana?', eu digo: 'Vá ao site do Grafana, lá está a comunidade “Painéis”, e lá tem um painel do Dimka, tem um painel do Kostya. O que isso é, eu não sei, eu mesmo não usei.'

Como influenciar os merges para que o servidor não caia em OOM?

Eu tenho uma tabela, que possui apenas uma partição, é um ReplacingMergeTree. Eu escrevo dados nela há quatro anos. Eu precisava fazer um alter nela e deletar alguns dados.

Eu fiz isso, e durante o processamento dessa consulta, toda a memória de todos os servidores do cluster foi consumida, e todos os servidores do cluster caíram em OOM. Depois, eles todos juntos reiniciaram, começaram a executar o merge da mesma operação, desse bloco de dados, e caíram novamente em OOM. Então eles reiniciaram novamente e caíram de novo. E essa situação não parava.

Depois, descobriu-se que isso na verdade era um bug que os desenvolvedores corrigiram. Isso é ótimo, muito obrigado. Mas a sensação ruim ficou. E agora, quando eu penso em ter que fazer algum merge na tabela, me surge a pergunta - por que não posso de alguma forma influenciar esses merges? Por exemplo, limitar a quantidade de memória RAM necessária, ou, em princípio, a quantidade que irá processar especificamente essa tabela.

Eu tenho uma tabela chamada 'Métricas', por favor processe-a para mim em dois streams. Não é necessário gerar dez ou cinco merges em paralelo, faça em dois. Eu acredito que em dois eu tenho memória suficiente, mas para processar dez, pode ser que não. Por que o medo persiste? Porque a tabela está crescendo, e algum dia vou me deparar com uma situação em que, em princípio, não será por causa de um bug, mas devido ao fato de que os dados estarão mudando em uma quantidade tão grande que simplesmente não terei memória suficiente no servidor. E então o servidor cairá em OOM durante o merge. E eu posso cancelar a mutação, mas merges já não.

Você sabe que, durante os merges, o servidor não irá cair em OOM, porque durante o merge é utilizado apenas uma pequena quantidade de memória para um pequeno intervalo de dados. Portanto, tudo irá funcionar bem, independentemente do volume de dados.

Vladimir Kolobaev: Certo. Aqui está a questão: depois que fizemos a correção de bugs, baixei uma nova versão e, em outra tabela, menor, onde há muitas partições, realizei uma operação semelhante. E durante o merge, aproximadamente 100 GB de memória foram utilizados no servidor. Eu tinha 150 ocupados, 100 foram consumidos, e sobraram 50 GB, então não tive OOM.

O que me protege neste momento de não cair em OOM, se realmente consumir 100 GB de memória? Como agir se, de repente, a memória acabar durante os merges?

Alexey Milovidov: Há um problema de que o consumo de memória durante os merges não é limitado. E o segundo problema é que, se algum merge foi designado, ele precisa ser executado, pois está registrado no log de replicação. O log de replicação contém as ações necessárias para trazer a réplica a um estado consistente. Se não forem feitas alterações manuais que revertam esse log de replicação, o merge terá que ser executado, de uma forma ou de outra.

Claro, não seria ruim ter uma limitação de memória que protege 'para caso' contra OOM. Isso não ajudará o merge a ser completado, ele começará novamente, atingirá um certo limite, lançará uma exceção e então começará de novo - nada bom sairá disso. Mas, de fato, seria útil implementar essa limitação.

Como será o desenvolvimento do driver Golang para ClickHouse?

O driver Golang, que foi escrito por Kirill Shvakov, agora está oficialmente, aparentemente, sendo suportado pela equipe do ClickHouse. Ele está no repositório ClickHouse, ele agora é grande e verdadeiro.

Uma pequena observação. Há um maravilhoso e adorado armazenamento de formas normais de ordem infinita — é o Vertica. Eles também têm seu próprio driver oficial para Python, que é mantido pelos desenvolvedores do Vertica. E várias vezes aconteceu que as versões do armazenamento e do driver se desincronizavam bastante, resultando na interrupção do funcionamento do driver. E a segunda questão. A manutenção desse driver oficial, me parece, é feita através do sistema de "nipples" — você escreve um issue e isso fica pendente para sempre.

Eu tenho duas perguntas. Atualmente, o driver Golang do Kirill é quase o método padrão para comunicar entre Golang e ClickHouse. A não ser que alguém ainda use a interface http, porque prefere assim. Como será o desenvolvimento desse driver? Ele irá se sincronizar com algumas mudanças drásticas no próprio armazenamento? E qual é o procedimento para revisão de issues?

Kirill Shvakov: Primeiro — como tudo funciona burocraticamente. Esse aspecto não foi discutido, então não tenho como responder.

Para responder à pergunta sobre issues, é necessário um breve histórico do driver. Eu trabalhei em uma empresa que lidava com muitos dados. Era uma plataforma publicitária com um enorme volume de eventos que precisavam ser armazenados. E em determinado momento, surgiu o ClickHouse. Nós injetamos os dados lá e, num primeiro momento, tudo estava bem, mas depois o ClickHouse falhou. Naquele momento decidimos que não precisávamos mais dele.

Um ano depois, voltamos à ideia de usar o ClickHouse, e precisávamos de alguma forma para inserir dados lá. A premissa era que o hardware era muito fraco, com poucos recursos. Mas sempre trabalhamos assim, então olhamos para o protocolo nativo.

Como trabalhávamos com Go, ficou claro que precisávamos de um driver em Go. Eu trabalhei nele praticamente em tempo integral — essa era a minha tarefa de trabalho. Até certo ponto, nós o desenvolvemos, e na verdade ninguém esperava que alguém além de nós o usasse. Depois, a CloudFlare surgiu exatamente com o mesmo problema, e por algum tempo trabalhamos em estreita colaboração, pois eles tinham as mesmas tarefas. Aliás, fizemos isso tanto no ClickHouse em si quanto no driver.

Em algum momento, eu simplesmente parei de me envolver com isso, porque minha atividade em relação ao ClickHouse e meu trabalho mudaram um pouco. Por isso, os issues não são fechados. Periodicamente, pessoas com quem trabalho fazem commits no repositório porque elas mesmas precisam de algo. Então eu olho os pull requests e, às vezes, até faço algumas correções, mas isso acontece raramente.

Quero voltar ao driver. Alguns anos atrás, quando tudo isso começou, o ClickHouse também era diferente e tinha outras possibilidades. Agora, há uma compreensão de como refazer o driver para que funcione bem. Se isso acontecer, a versão 2, de qualquer forma, será incompatível devido a gambiarras acumuladas.

Não sei como organizar isso. Eu mesmo não tenho muito tempo. Se algumas pessoas quiserem finalizar o desenvolvimento do driver, poderei ajudá-las e dizer o que fazer. Mas a participação ativa do Yandex no desenvolvimento do projeto ainda não foi discutida.

Alexey Milovidov: Na verdade, ainda não há burocracia em relação a esses drivers. A única coisa é que eles foram transferidos para uma organização oficial, ou seja, esse driver é reconhecido como solução oficial por padrão para Go. Existem outros drivers, mas eles seguem separadamente.

Internamente, não temos nenhum desenvolvimento para esses drivers. A questão é se conseguiremos contratar uma pessoa separada, não especificamente para esse driver, mas para o desenvolvimento de todos os drivers comunitários, ou se conseguiremos encontrar alguém externamente.

O dicionário externo não é carregado após uma reinicialização com a configuração lazy_load ativada. O que fazer?

Temos a configuração lazy_load ativada, e após a reinicialização do servidor, o dicionário não é carregado automaticamente. Ele só é carregado quando o usuário acessa esse dicionário. E ao primeiro acesso, ocorre um erro. Existe alguma maneira de carregar dicionários automaticamente com os recursos do ClickHouse, ou precisamos sempre controlar a prontidão deles para que os usuários não recebam erros?

Talvez tenhamos uma versão antiga do ClickHouse, então o dicionário não foi carregado automaticamente. Isso é possível?

Primeiro, os dicionários podem ser forçados a carregar com a ajuda da consulta system reload dictionaries. Em segundo lugar, em relação ao erro — se o dicionário já foi carregado, as consultas funcionarão com os dados que foram carregados. Se o dicionário ainda não foi carregado, ele será carregado no momento da consulta.

Para dicionários pesados, isso não é muito conveniente. Por exemplo, é necessário carregar um milhão de linhas do MySQL. Alguém faz um select simples, mas esse select vai esperar essas milhões de linhas. Existem duas soluções. A primeira é desligar o lazy_load. A segunda é, quando o servidor é iniciado, antes de aplicar pressão sobre ele, executar system reload dictionary ou simplesmente executar uma consulta que utiliza o dicionário. Nesse caso, o dicionário será carregado. É necessário controlar a acessibilidade dos dicionários com a configuração lazy_load ativada, porque o ClickHouse não os carrega automaticamente.

A resposta à última pergunta é que ou a versão é antiga, ou precisa ser depurada.

Como lidar com o fato de que a reinicialização do sistema não carrega nenhum dos vários dicionários, se pelo menos um deles falhar com um erro?

Há também uma pergunta sobre system reload dictionaries. Temos dois dicionários - um não é carregado, o outro é carregado. Nesse caso, System reload dictionaries não carrega nenhum dicionário, e é necessário carregar um específico pelo nome utilizando system reload dictionary. Isso também está relacionado à versão do ClickHouse?

Quero tranquilizá-lo. Esse comportamento mudou. Portanto, se você atualizar o ClickHouse, isso também mudará. Se você não está satisfeito com o comportamento atual system reload dictionaries, atualize, e vamos torcer para que melhore.

Há uma maneira de configurar as credenciais no config do ClickHouse, mas não expô-las em caso de erros?

A próxima pergunta é sobre erros relacionados ao dicionário, especificamente às credenciais. Nós registramos as credenciais de conexão no arquivo de configuração do ClickHouse para o dicionário, e quando ocorre um erro, recebemos essas credenciais e a senha na resposta.

Resolvemos esse erro movendo as credenciais para a configuração do driver ODBC. Existe uma forma de configurar as credenciais no arquivo de configuração do ClickHouse sem expor essas credenciais em caso de erros?

Aqui a solução é realmente indicar essas credentials em odbc.ini, e no próprio ClickHouse, indicar apenas o ODBC Data Source Name. Para outras fontes de dicionários, isso não acontecerá - nem para o dicionário com MySQL, nem para os outros você deverá ver a senha na mensagem de erro. Quanto ao ODBC, eu também vou verificar - se existir, é só retirar.

Bônus: fundos para ZOOM com as reuniões

Ao clicar na imagem, para os leitores mais perseverantes, abrirão fundos bônus de reuniões. Apagamos o incêndio junto com os mascotes da tecnologia Avito, nos reunimos com colegas na sala do administrador de sistemas ou no velho clube de computação e realizamos um daily sob a ponte com um fundo de grafite.

ClickHouse para usuários avançados em perguntas e respostas

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