Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Por ser o ClickHouse um sistema especializado, ao utilizá-lo é importante levar em consideração as características de sua arquitetura. Neste relatório, Alexey falará sobre exemplos de erros comuns ao usar o ClickHouse, que podem levar a um trabalho ineficaz. Exemplos práticos mostrarão como a escolha de um ou outro esquema de processamento de dados pode alterar o desempenho em ordens de grandeza.

Olá a todos! Meu nome é Alexey, eu faço ClickHouse.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Em primeiro lugar, apresso-me em agradá-lo desde já, hoje não vou lhe contar o que é ClickHouse. Para ser honesto, estou cansado disso. Toda vez que eu te digo o que é. E provavelmente todo mundo já sabe.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Em vez disso, direi quais são os possíveis erros, ou seja, como você pode usar o ClickHouse incorretamente. Na verdade, não há necessidade de ter medo, porque estamos desenvolvendo o ClickHouse como um sistema simples, conveniente e que funciona imediatamente. Eu instalei, sem problemas.

Mas você ainda precisa levar em conta que este sistema é especializado e você pode facilmente se deparar com um caso de uso incomum que tirará este sistema de sua zona de conforto.

Então, que tipo de ancinho existe? Principalmente falarei sobre coisas óbvias. Tudo é óbvio para todos, todos entendem tudo e podem ficar felizes por serem tão inteligentes, e quem não entende aprenderá algo novo.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

O primeiro e mais simples exemplo, que infelizmente ocorre com frequência, é um grande número de pastilhas com pequenos lotes, ou seja, um grande número de pastilhas pequenas.

Se considerarmos como o ClickHouse realiza a inserção, você poderá enviar pelo menos um terabyte de dados em uma solicitação. Isso não é um problema.

E vamos ver qual seria o desempenho típico. Por exemplo, temos uma tabela de dados Yandex.Metrica. Exitos. 105 algumas colunas. 700 bytes descompactados. E vamos inserir bem em lotes de um milhão de linhas.

Inserimos MergeTree na tabela, resultando em meio milhão de linhas por segundo. Ótimo. Em uma tabela replicada será um pouco menor, aproximadamente 400 linhas por segundo.

E se você ativar a inserção de quorum, obterá um desempenho um pouco menor, mas ainda decente, 250 termos por segundo. A inserção de quorum é um recurso não documentado no ClickHouse*.

*a partir de 2020, já documentado.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

O que acontece se você fizer algo ruim? Inserimos uma linha na tabela MergeTree e obtemos 59 linhas por segundo. Isso é 10 vezes mais lento. Em ReplicatedMergeTree – 000 linhas por segundo. E se o quorum estiver ativado, serão produzidas 6 linhas por segundo. Na minha opinião, isso é algum tipo de porcaria absoluta. Como você pode desacelerar assim? Até tenho escrito na minha camiseta que o ClickHouse não deveria desacelerar. Mas mesmo assim isso acontece às vezes.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Na verdade, esta é a nossa deficiência. Poderíamos facilmente ter feito tudo funcionar bem, mas não o fizemos. E não fizemos isso porque nosso roteiro não exigia isso. Já tínhamos matadouros. Acabamos de receber lotes na nossa entrada e sem problemas. Nós o inserimos e tudo funciona bem. Mas, claro, todos os tipos de cenários são possíveis. Por exemplo, quando você tem vários servidores nos quais os dados são gerados. E eles não inserem dados com tanta frequência, mas ainda assim acabam com inserções frequentes. E precisamos de alguma forma evitar isso.

Do ponto de vista técnico, a questão é que quando você faz um insert no ClickHouse, os dados não vão para nenhuma memtable. Nem sequer temos uma estrutura de log real MergeTree, mas apenas uma MergeTree, porque não existe um log nem uma memTable. Simplesmente gravamos imediatamente os dados no sistema de arquivos, já organizados em colunas. E se você tiver 100 colunas, mais de 200 arquivos precisarão ser gravados em um diretório separado. Tudo isso é muito complicado.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E surge a pergunta: “Como fazer certo?” Se a situação for tal que você ainda precise registrar dados de alguma forma no ClickHouse.

Método 1. Esta é a maneira mais fácil. Use algum tipo de fila distribuída. Por exemplo, Kafka. Você simplesmente extrai os dados do Kafka e os agrupa em lote uma vez por segundo. E vai dar tudo certo, você grava, tudo funciona bem.

As desvantagens são que o Kafka é outro sistema distribuído volumoso. Também entendo se você já tem o Kafka na sua empresa. É bom, é conveniente. Mas se não existir, você deve pensar três vezes antes de arrastar outro sistema distribuído para o seu projeto. E por isso vale a pena considerar alternativas.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Método 2. Esta é uma alternativa tradicional e ao mesmo tempo muito simples. Você tem algum tipo de servidor que gera seus logs. E apenas grava seus logs em um arquivo. E uma vez por segundo, por exemplo, renomeamos esse arquivo e arrancamos um novo. E um script separado, via cron ou algum daemon, pega o arquivo mais antigo e o grava no ClickHouse. Se você gravar logs uma vez por segundo, tudo ficará bem.

Mas a desvantagem desse método é que se o servidor no qual os logs são gerados desaparecer em algum lugar, os dados também desaparecerão.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Método 3. Existe outro método interessante que não requer nenhum arquivo temporário. Por exemplo, você tem algum tipo de girador de publicidade ou algum outro daemon interessante que gera dados. E você pode acumular vários dados diretamente na RAM, no buffer. E quando tiver passado tempo suficiente, você deixa esse buffer de lado, cria um novo e, em um thread separado, insere o que já acumulou no ClickHouse.

Por outro lado, os dados também desaparecem com kill -9. Se o seu servidor travar, você perderá esses dados. E outro problema é que se você não conseguir gravar no banco de dados, seus dados irão se acumular na RAM. E ou a RAM acabará ou você simplesmente perderá dados.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Método 4. Outro método interessante. Você tem algum tipo de processo de servidor. E pode enviar dados para ClickHouse imediatamente, mas em uma conexão. Por exemplo, enviei uma solicitação http com codificação de transferência: fragmentada com inserção. E não raramente gera pedaços, você pode enviar cada linha, embora haja sobrecarga para enquadrar esses dados.

Porém, neste caso os dados serão enviados para ClickHouse imediatamente. E o próprio ClickHouse irá armazená-los em buffer.

Mas também surgem problemas. Agora você perderá dados, inclusive quando seu processo for encerrado e se o processo ClickHouse for encerrado, porque será uma inserção incompleta. E no ClickHouse as inserções são atômicas até um determinado limite especificado no tamanho das linhas. Em princípio, esta é uma forma interessante. Também pode ser usado.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Método 5. Aqui está outro método interessante. Este é algum tipo de servidor desenvolvido pela comunidade para processamento em lote de dados. Eu mesmo não olhei, então não posso garantir nada. No entanto, nenhuma garantia é fornecida para o próprio ClickHouse. Também é de código aberto, mas por outro lado, você pode estar acostumado com algum padrão de qualidade que tentamos fornecer. Mas para isso - não sei, vá ao GitHub, veja o código. Talvez eles tenham escrito algo normal.

* a partir de 2020, também deve ser adicionado à consideração Casa dos gatinhos.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Método 6. Outro método é usar tabelas Buffer. A vantagem deste método é que é muito fácil de começar a usar. Crie uma tabela Buffer e insira-a nela.

A desvantagem é que o problema não está completamente resolvido. Se, em uma taxa como MergeTree, você precisar agrupar dados em um lote por segundo, então, em uma taxa em uma tabela de buffer, será necessário agrupar pelo menos vários milhares por segundo. Se for mais de 10 por segundo, ainda será ruim. E se você inserir em lotes, verá que são cem mil linhas por segundo. E isso já está baseado em dados bastante pesados.

E também as tabelas de buffer não possuem log. E se houver algo errado com o seu servidor, os dados serão perdidos.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E como bônus, recentemente tivemos a oportunidade no ClickHouse de recuperar dados do Kafka. Existe um mecanismo de mesa - Kafka. Você acabou de criar. E você pode pendurar representações materializadas nele. Nesse caso, ele próprio extrairá os dados do Kafka e os inserirá nas tabelas necessárias.

E o que é especialmente agradável nesta oportunidade é que não fomos nós quem a fez. Este é um recurso da comunidade. E quando digo “recurso comunitário”, quero dizer isso sem qualquer desprezo. Lemos o código, fizemos uma revisão, deve funcionar bem.

* a partir de 2020, apareceu suporte semelhante para RabbitMQ.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

O que mais poderia ser inconveniente ou inesperado ao inserir dados? Se você fizer uma solicitação de inserção de valores e escrever algumas expressões calculadas em valores. Por exemplo, now() também é uma expressão calculada. E neste caso, ClickHouse é forçado a lançar o interpretador dessas expressões em cada linha, e o desempenho cairá em ordens de magnitude. É melhor evitar isso.

* no momento o problema está totalmente resolvido, não há mais regressão de desempenho ao utilizar expressões em VALUES.

Outro exemplo é quando pode haver alguns problemas quando você tem dados em um lote que pertence a várias partições. Por padrão, as partições do ClickHouse são por mês. E se você inserir um lote de um milhão de linhas e houver dados de vários anos, você terá várias dezenas de partições lá. E isso equivale ao fato de que haverá lotes várias dezenas de vezes menores, porque dentro eles são sempre primeiro divididos em partições.

* Recentemente, em modo experimental, ClickHouse adicionou suporte para o formato compacto de pedaços e pedaços de RAM com log write-ahead, o que resolve quase completamente o problema.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Agora vamos examinar o segundo tipo de problema – digitação de dados.

A digitação de dados pode ser estrita ou string. String é quando você simplesmente pega e declara que todos os seus campos são do tipo string. Isso é uma merda. Não há necessidade de fazer isso.

Vamos descobrir como fazer isso corretamente nos casos em que você quer dizer que temos algum campo, uma string, e deixar o ClickHouse descobrir por conta própria, e não vou me preocupar. Mas ainda vale a pena fazer algum esforço.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Por exemplo, temos um endereço IP. Em um caso, salvamos como uma string. Por exemplo, 192.168.1.1. E em outro caso, será um número do tipo UInt32*. 32 bits são suficientes para um endereço IPv4.

Em primeiro lugar, curiosamente, os dados serão compactados de forma aproximadamente igual. Haverá uma diferença, é claro, mas não tão grande. Portanto, não há problemas especiais com E/S de disco.

Mas há uma grande diferença no tempo do processador e no tempo de execução da consulta.

Vamos contar o número de endereços IP exclusivos se eles estiverem armazenados como números. Isso equivale a 137 milhões de linhas por segundo. Se o mesmo estiver na forma de strings, então 37 milhões de linhas por segundo. Não sei por que essa coincidência aconteceu. Eu mesmo realizei esses pedidos. Mas ainda cerca de 4 vezes mais lento.

E se você calcular a diferença no espaço em disco, também haverá uma diferença. E a diferença é de cerca de um quarto, porque existem muitos endereços IP exclusivos. E se houvesse linhas com um pequeno número de significados diferentes, elas seriam facilmente compactadas de acordo com o dicionário em aproximadamente o mesmo volume.

E a diferença horária quádrupla não está na estrada. Talvez você não dê a mínima, claro, mas quando vejo tanta diferença fico triste.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Vejamos diferentes casos.

1. Um caso em que você tem poucos valores exclusivos diferentes. Nesse caso, usamos uma prática simples que você provavelmente conhece e pode usar para qualquer SGBD. Tudo isso faz sentido não apenas para ClickHouse. Basta escrever identificadores numéricos no banco de dados. E você pode converter em strings e vice-versa na lateral do seu aplicativo.

Por exemplo, você tem uma região. E você está tentando salvá-lo como uma string. E estará escrito lá: Moscou e região de Moscou. E quando vejo que está escrito “Moscou”, não é nada, mas quando é Moscou, de alguma forma fica completamente triste. Isto é quantos bytes.

Em vez disso, simplesmente anotamos o número Ulnt32 e 250. Temos 250 no Yandex, mas o seu pode ser diferente. Por precaução, direi que ClickHouse tem uma capacidade integrada de trabalhar com uma geobase. Você simplesmente anota um diretório com regiões, inclusive hierárquica, ou seja, haverá Moscou, região de Moscou e tudo que você precisa. E você pode converter no nível da solicitação.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

A segunda opção é aproximadamente a mesma, mas com suporte dentro do ClickHouse. Este é o tipo de dados Enum. Você simplesmente escreve todos os valores necessários dentro do Enum. Por exemplo, tipo de dispositivo e escreva lá: desktop, celular, tablet, TV. Existem 4 opções no total.

A desvantagem é que você precisa trocá-lo periodicamente. Apenas uma opção adicionada. Vamos alterar a tabela. Na verdade, alterar tabela no ClickHouse é gratuito. Especialmente gratuito para Enum porque os dados no disco não mudam. Mesmo assim, alter adquire um lock* na tabela e deve esperar até que todas as seleções sejam executadas. E somente após esta alteração será executada, ou seja, ainda existem alguns inconvenientes.

* nas versões mais recentes do ClickHouse, ALTER é totalmente sem bloqueio.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outra opção exclusiva do ClickHouse é conectar dicionários externos. Você pode escrever números no ClickHouse e manter seus diretórios em qualquer sistema conveniente para você. Por exemplo, você pode usar: MySQL, Mongo, Postgres. Você pode até criar seu próprio microsserviço que enviará esses dados via http. E no nível ClickHouse, você escreve uma função que converterá esses dados de números em strings.

Esta é uma forma especializada, mas muito eficiente, de realizar uma junção em uma tabela externa. E há duas opções. Numa modalidade, estes dados serão completamente armazenados em cache, totalmente presentes na RAM e atualizados com alguma frequência. E em outra opção, se esses dados não couberem na RAM, você poderá armazená-los parcialmente em cache.

Aqui está um exemplo. Existe Yandex.Direct. E tem uma empresa de publicidade e banners. Existem provavelmente cerca de dezenas de milhões de empresas de publicidade. E eles cabem aproximadamente na RAM. E existem bilhões de banners, eles não cabem. E usamos um dicionário em cache do MySQL.

O único problema é que o dicionário armazenado em cache funcionará bem se a taxa de acertos estiver próxima de 100%. Se for menor, ao processar consultas para cada lote de dados, você terá que pegar as chaves que faltam e obter os dados do MySQL. Sobre o ClickHouse, ainda posso garantir que – sim, ele não fica lento, não vou falar de outros sistemas.

E como bônus, os dicionários são uma maneira muito fácil de atualizar dados retroativamente no ClickHouse. Ou seja, você tinha um relatório sobre empresas anunciantes, o usuário simplesmente trocou de empresa anunciante e em todos os dados antigos, em todos os relatórios, esses dados também mudaram. Se você escrever linhas diretamente na tabela, será impossível atualizá-las.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outra maneira quando você não sabe onde obter os identificadores de suas strings. você pode simplesmente fazer hash. Além disso, a opção mais simples é usar um hash de 64 bits.

O único problema é que se o hash for de 64 bits, é quase certo que você terá colisões. Porque se houver um bilhão de linhas ali, a probabilidade já se torna perceptível.

E não seria muito bom misturar os nomes das empresas de publicidade desta forma. Se as campanhas publicitárias de diferentes empresas se confundirem, algo incompreensível surgirá.

E há um truque simples. É verdade que também não é muito adequado para dados sérios, mas se algo não for muito sério, basta adicionar o identificador do cliente à chave do dicionário. E então você terá colisões, mas apenas dentro de um cliente. E usamos esse método para mapas de links no Yandex.Metrica. Temos URLs lá, armazenamos hashes. E sabemos que, claro, existem colisões. Mas quando a página é exibida, a probabilidade de que em uma página de um usuário alguns URLs estejam grudados e isso seja notado pode ser negligenciada.

Como bônus, para muitas operações, apenas os hashes são suficientes e as strings em si não precisam ser armazenadas em lugar nenhum.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outro exemplo é se as strings forem curtas, por exemplo, domínios de sites. Eles podem ser armazenados como estão. Ou, por exemplo, o idioma do navegador ru tem 2 bytes. Claro, sinto muito pelos bytes, mas não se preocupe, 2 bytes não é uma pena. Por favor, mantenha-o como está, não se preocupe.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outro caso é quando, ao contrário, há muitas linhas e há muitas linhas únicas nelas, e até mesmo o conjunto é potencialmente ilimitado. Um exemplo típico são frases de pesquisa ou URLs. Frases de pesquisa, incluindo erros de digitação. Vamos ver quantas frases de pesquisa exclusivas existem por dia. E acontece que eles representam quase metade de todos os eventos. E neste caso, você pode pensar que precisa normalizar os dados, contar os identificadores e colocá-los em uma tabela separada. Mas você não precisa fazer isso. Apenas mantenha essas linhas como estão.

É melhor não inventar nada, porque se você guardar separadamente, vai precisar fazer uma junção. E essa junção é, na melhor das hipóteses, um acesso aleatório à memória, se ainda couber na memória. Se não couber, haverá problemas.

E se os dados estiverem armazenados no local, eles serão simplesmente lidos na ordem necessária no sistema de arquivos e tudo ficará bem.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Se você tiver URLs ou alguma outra string longa e complexa, vale a pena considerar que você pode calcular algum tipo de extrato antecipadamente e escrevê-lo em uma coluna separada.

Para URLs, por exemplo, você pode armazenar o domínio separadamente. E se você realmente precisa de um domínio, basta usar esta coluna e os URLs ficarão lá, e você nem mesmo tocará neles.

Vamos ver qual é a diferença. ClickHouse possui uma função especializada que calcula o domínio. É muito rápido, nós o otimizamos. E, para ser sincero, nem cumpre a RFC, mas mesmo assim considera tudo o que precisamos.

E em um caso simplesmente obteremos os URLs e calcularemos o domínio. Isso equivale a 166 milissegundos. E se você pegar um domínio pronto, será apenas 67 milissegundos, ou seja, quase três vezes mais rápido. E é mais rápido não porque precisamos fazer alguns cálculos, mas porque lemos menos dados.

É por isso que uma solicitação, que é mais lenta, tem uma velocidade maior de gigabytes por segundo. Porque lê mais gigabytes. Estes são dados completamente desnecessários. A solicitação parece ser executada mais rapidamente, mas demora mais para ser concluída.

E se você observar a quantidade de dados no disco, verifica-se que o URL tem 126 megabytes e o domínio tem apenas 5 megabytes. Acontece 25 vezes menos. Mesmo assim, a solicitação é executada apenas 4 vezes mais rápido. Mas isso ocorre porque os dados são importantes. E se estivesse frio, provavelmente seria 25 vezes mais rápido devido à E/S do disco.

A propósito, se você estimar o quanto um domínio é menor que uma URL, ele será cerca de 4 vezes menor. Mas, por algum motivo, os dados ocupam 25 vezes menos no disco. Por que? Devido à compressão. E a URL é compactada e o domínio é compactado. Mas muitas vezes o URL contém um monte de lixo.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E, claro, vale a pena usar os tipos de dados certos, projetados especificamente para os valores desejados ou que sejam adequados. Se você estiver em IPv4, armazene UInt32*. Se for IPv6, então FixedString(16), porque o endereço IPv6 é de 128 bits, ou seja, armazenado diretamente em formato binário.

Mas e se às vezes você tiver endereços IPv4 e às vezes IPv6? Sim, você pode armazenar ambos. Uma coluna para IPv4, outra para IPv6. Claro, existe uma opção para exibir IPv4 em IPv6. Isso também funcionará, mas se você precisar frequentemente de um endereço IPv4 em solicitações, seria bom colocá-lo em uma coluna separada.

* ClickHouse agora possui tipos de dados IPv4 e IPv6 separados que armazenam dados com a mesma eficiência que números, mas os representam de forma tão conveniente quanto strings.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Também é importante ressaltar que vale a pena pré-processar os dados com antecedência. Por exemplo, você recebe alguns logs brutos. E talvez você não deva simplesmente colocá-los no ClickHouse imediatamente, embora seja muito tentador não fazer nada e tudo funcionará. Mas ainda vale a pena fazer os cálculos possíveis.

Por exemplo, versão do navegador. Em algum departamento próximo, para o qual não quero apontar o dedo, a versão do navegador é armazenada assim, ou seja, como uma string: 12.3. E então, para fazer um relatório, eles pegam essa string e a dividem em um array, e depois no primeiro elemento do array. Naturalmente, tudo fica mais lento. Eu perguntei por que eles fazem isso. Eles me disseram que não gostam de otimização prematura. E não gosto de pessimização prematura.

Então neste caso seria mais correto dividir em 4 colunas. Não tenha medo aqui, porque aqui é ClickHouse. ClickHouse é um banco de dados colunar. E quanto mais colunas organizadas, melhor. Haverá 5 BrowserVersions, faça 5 colunas. Isto é bom.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Agora vamos ver o que fazer se você tiver muitas strings muito longas, arrays muito longos. Eles não precisam ser armazenados no ClickHouse. Em vez disso, você só pode armazenar um identificador no ClickHouse. E coloque essas longas filas em algum outro sistema.

Por exemplo, um de nossos serviços analíticos possui alguns parâmetros de evento. E se houver muitos parâmetros para eventos, simplesmente salvamos os primeiros 512 que aparecerem, porque 512 não é uma pena.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E se você não consegue decidir sobre seus tipos de dados, então você também pode registrar os dados no ClickHouse, mas em uma tabela temporária do tipo Log, especial para dados temporários. Depois disso, você pode analisar qual distribuição de valores você tem aí, o que existe em geral, e criar os tipos corretos.

*ClickHouse agora tem um tipo de dados Baixa Cardinalidade o que permite armazenar strings de forma eficiente e com menos esforço.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Agora vamos examinar outro caso interessante. Às vezes as coisas funcionam de maneira estranha para as pessoas. Eu entro e vejo isso. E imediatamente parece que isso foi feito por algum administrador inteligente e muito experiente, que tem vasta experiência na configuração do MySQL versão 3.23.

Aqui vemos mil tabelas, cada uma das quais registra o restante da divisão sabe-se lá o quê por mil.

Em princípio, respeito a experiência de outras pessoas, incluindo a compreensão do sofrimento que pode ser adquirido através desta experiência.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E as razões são mais ou menos claras. Esses são estereótipos antigos que podem ter se acumulado durante o trabalho com outros sistemas. Por exemplo, as tabelas MyISAM não possuem uma chave primária agrupada. E esta forma de dividir os dados pode ser uma tentativa desesperada de obter a mesma funcionalidade.

Outra razão é que é difícil realizar qualquer operação de alteração em tabelas grandes. Tudo será bloqueado. Embora nas versões modernas do MySQL esse problema não seja mais tão sério.

Ou, por exemplo, microsharding, mas falaremos mais sobre isso mais tarde.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Não há necessidade de fazer isso no ClickHouse, pois, em primeiro lugar, a chave primária é agrupada, os dados são ordenados pela chave primária.

E às vezes as pessoas me perguntam: “Como o desempenho das consultas de intervalo no ClickHouse varia dependendo do tamanho da tabela?” Eu digo que isso não muda em nada. Por exemplo, você tem uma tabela com um bilhão de linhas e lê um intervalo de um milhão de linhas. Tudo está bem. Se houver um trilhão de linhas em uma tabela e você ler um milhão de linhas, será quase a mesma coisa.

E, em segundo lugar, todos os tipos de coisas como partições manuais não são necessárias. Se você entrar e olhar o que está no sistema de arquivos, verá que a tabela é muito importante. E há algo como partições dentro. Ou seja, a ClickHouse faz tudo por você e você não precisa sofrer.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Alterar no ClickHouse é gratuito se alterar adicionar/descartar coluna.

E você não deve fazer tabelas pequenas, porque se você tiver 10 ou 10 linhas em uma tabela, isso não importa. ClickHouse é um sistema que otimiza o rendimento, não a latência, por isso não faz sentido processar 000 linhas.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

É correto usar uma mesa grande. Livre-se dos velhos estereótipos, tudo ficará bem.

E como bônus, na versão mais recente agora temos a capacidade de criar uma chave de particionamento arbitrária para realizar todos os tipos de operações de manutenção em partições individuais.

Por exemplo, você precisa de muitas tabelas pequenas, por exemplo, quando há necessidade de processar alguns dados intermediários, você recebe pedaços e precisa realizar uma transformação neles antes de gravar na tabela final. Para este caso, existe um mecanismo de tabela maravilhoso - StripeLog. É como o TinyLog, só que melhor.

*agora a ClickHouse também tem entrada de função de tabela.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outro antipadrão é o microsharding. Por exemplo, você precisa fragmentar dados e tem 5 servidores, e amanhã haverá 6 servidores. E você pensa em como reequilibrar esses dados. E em vez disso você não quebra em 5 fragmentos, mas em 1 fragmentos. E então você mapeia cada um desses microshards para um servidor separado. E você receberá, por exemplo, 000 ClickHouses em um servidor, por exemplo. Instâncias separadas em portas separadas ou bancos de dados separados.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Mas isso não é muito bom no ClickHouse. Porque até mesmo uma instância do ClickHouse tenta usar todos os recursos disponíveis do servidor para processar uma solicitação. Ou seja, você tem algum tipo de servidor e ele possui, por exemplo, 56 núcleos de processador. Você está executando uma consulta que leva um segundo e usará 56 núcleos. E se você colocou 200 ClickHouses em um servidor, 10 threads serão iniciados. Em geral, tudo ficará muito ruim.

Outra razão é que a distribuição do trabalho entre essas instâncias será desigual. Alguns terminarão mais cedo, outros terminarão mais tarde. Se tudo isso acontecesse em uma instância, o próprio ClickHouse descobriria como distribuir corretamente os dados entre os threads.

E outro motivo é que você terá comunicação entre processadores via TCP. Os dados terão que ser serializados, desserializados, e este é um grande número de microshards. Simplesmente não funcionará de forma eficaz.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outro antipadrão, embora dificilmente possa ser chamado de antipadrão. Esta é uma grande quantidade de pré-agregação.

Em geral, a pré-agregação é boa. Você tinha um bilhão de linhas, agregou e se tornou 1 linhas, e agora a consulta é executada instantaneamente. Tudo é bom. Você consegue fazer isso. E para isso, até o ClickHouse possui um tipo de tabela especial, AggregatingMergeTree, que realiza agregação incremental à medida que os dados são inseridos.

Mas há momentos em que você pensa que iremos agregar dados como este e agregar dados como este. E em alguns departamentos vizinhos, também não quero dizer qual deles, eles usam tabelas SummingMergeTree para resumir pela chave primária, e cerca de 20 colunas são usadas como chave primária. Por precaução, mudei os nomes de algumas colunas por questão de sigilo, mas é só isso.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E esses problemas surgem. Em primeiro lugar, o seu volume de dados não diminui muito. Por exemplo, diminui três vezes. Três vezes seria um bom preço para pagar os recursos analíticos ilimitados que surgirão se seus dados não forem agregados. Se os dados forem agregados, em vez de análises você obterá apenas estatísticas lamentáveis.

E o que há de tão especial nisso? O fato é que essas pessoas do departamento vizinho às vezes vão e pedem para adicionar outra coluna à chave primária. Ou seja, agregamos os dados assim, mas agora queremos um pouco mais. Mas ClickHouse não possui uma chave primária alterada. Portanto, temos que escrever alguns scripts em C++. E não gosto de scripts, mesmo que sejam em C++.

E se você observar para que o ClickHouse foi criado, então os dados não agregados são exatamente o cenário para o qual ele nasceu. Se estiver usando ClickHouse para dados não agregados, você está fazendo certo. Se você agregar, isso às vezes é perdoável.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outro caso interessante são as consultas em loop infinito. Às vezes eu vou para algum servidor de produção e vejo show processlist lá. E toda vez eu descubro que algo terrível está acontecendo.

Por exemplo, assim. Fica imediatamente claro que tudo pode ser feito em uma única solicitação. Basta escrever o URL e a lista lá.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Por que muitas dessas consultas em um loop infinito são ruins? Se um índice não for usado, você terá muitas passagens pelos mesmos dados. Mas se o índice for usado, por exemplo, você tem uma chave primária para ru e escreve url = algo lá. E você acha que se apenas uma URL for lida da tabela, tudo ficará bem. Mas na verdade não. Porque ClickHouse faz tudo em lotes.

Quando ele precisa ler uma determinada faixa de dados, ele lê um pouco mais, pois o índice do ClickHouse é esparso. Este índice não permite que você encontre uma linha individual na tabela, apenas algum tipo de intervalo. E os dados são compactados em blocos. Para ler uma linha, você precisa pegar o bloco inteiro e abri-lo. E se você estiver fazendo muitas consultas, terá muitas sobreposições e muito trabalho a fazer continuamente.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E como bônus, você pode notar que no ClickHouse você não deve ter medo de transferir até mesmo megabytes e até centenas de megabytes para a seção IN. Lembro-me da nossa prática que se no MySQL transferimos um monte de valores para a seção IN, por exemplo, transferimos 100 megabytes de alguns números para lá, então o MySQL consome 10 gigabytes de memória e nada mais acontece com ele, tudo funciona mal.

E a segunda é que no ClickHouse, se suas consultas usam um índice, então ele sempre não é mais lento que uma varredura completa, ou seja, se você precisar ler quase toda a tabela, ele irá sequencialmente e lerá a tabela inteira. Em geral, ele descobrirá sozinho.

Mas, no entanto, existem algumas dificuldades. Por exemplo, o fato de IN com uma subconsulta não usar o índice. Mas este é o nosso problema e precisamos resolvê-lo. Não há nada fundamental aqui. Nós vamos consertar isso*.

E outra coisa interessante é que se você tiver uma solicitação muito longa e o processamento da solicitação distribuída estiver em andamento, essa solicitação muito longa será enviada para cada servidor sem compactação. Por exemplo, 100 megabytes e 500 servidores. E, consequentemente, você terá 50 gigabytes transferidos pela rede. Será transmitido e então tudo será concluído com sucesso.

*já usando; Tudo foi resolvido conforme prometido.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

E um caso bastante comum é quando as solicitações vêm da API. Por exemplo, você criou algum tipo de serviço próprio. E se alguém precisar do seu serviço, você abre a API e literalmente dois dias depois vê que algo incompreensível está acontecendo. Tudo está sobrecarregado e chegam alguns pedidos terríveis que nunca deveriam ter acontecido.

E só há uma solução. Se você abriu a API, terá que cortá-la. Por exemplo, introduza algum tipo de cota. Não há outras opções normais. Caso contrário, eles escreverão imediatamente um script e haverá problemas.

E o ClickHouse possui um recurso especial - cálculo de cotas. Além disso, você pode transferir sua chave de cota. Este é, por exemplo, o ID do usuário interno. E as cotas serão calculadas de forma independente para cada um deles.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Agora outra coisa interessante. Esta é a replicação manual.

Conheço muitos casos em que, apesar do ClickHouse ter suporte de replicação integrado, as pessoas replicam o ClickHouse manualmente.

Qual é o princípio? Você tem um pipeline de processamento de dados. E funciona de forma independente, por exemplo, em diferentes data centers. Você escreve os mesmos dados da mesma maneira no ClickHouse. É verdade que a prática mostra que os dados ainda divergirão devido a alguns recursos do seu código. Espero que esteja no seu.

E de vez em quando você ainda terá que sincronizar manualmente. Por exemplo, uma vez por mês, os administradores fazem o rsync.

Na verdade, é muito mais fácil usar a replicação integrada no ClickHouse. Mas pode haver algumas contra-indicações, pois para isso é necessário usar o ZooKeeper. Não vou falar mal do ZooKeeper, a princípio o sistema funciona, mas acontece que as pessoas não o usam por causa da javafobia, porque o ClickHouse é um sistema muito bom, escrito em C++, que você pode usar e tudo ficará bem. E o ZooKeeper está em java. E de alguma forma você nem quer olhar, mas pode usar a replicação manual.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

ClickHouse é um sistema prático. Ela leva em consideração suas necessidades. Se você tiver replicação manual, poderá criar uma tabela distribuída que analise suas réplicas manuais e faça um failover entre elas. E existe ainda uma opção especial que permite evitar flops, mesmo que as suas linhas divirjam sistematicamente.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Outros problemas poderão surgir se você usar mecanismos de tabela primitivos. ClickHouse é um construtor que possui vários mecanismos de tabela diferentes. Para todos os casos graves, conforme escrito na documentação, utilize tabelas da família MergeTree. E todo o resto é assim, para casos individuais ou para testes.

Em uma tabela MergeTree, você não precisa ter data e hora. Você ainda pode usá-lo. Se não houver data e hora, escreva que o padrão é 2000. Isso funcionará e não exigirá recursos.

E na nova versão do servidor, você pode até especificar que possui particionamento personalizado sem uma chave de partição. Será a mesma coisa.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Por outro lado, você pode usar mecanismos de tabela primitivos. Por exemplo, preencha os dados uma vez e veja, torça e exclua. Você pode usar Log.

Ou armazenar pequenos volumes para processamento intermediário é StripeLog ou TinyLog.

A memória pode ser usada se a quantidade de dados for pequena e você puder simplesmente mexer em algo na RAM.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

ClickHouse realmente não gosta de dados renormalizados.

Aqui está um exemplo típico. Este é um grande número de URLs. Você os coloca na próxima tabela. E então decidiram fazer JOIN com eles, mas isso não vai funcionar, via de regra, porque ClickHouse só suporta Hash JOIN. Se não houver RAM suficiente para muitos dados que precisam ser conectados, JOIN não funcionará*.

Se os dados forem de alta cardinalidade, não se preocupe, armazene-os de forma desnormalizada, os URLs ficam diretamente no lugar na tabela principal.

* e agora o ClickHouse também possui um merge join e funciona em condições onde os dados intermediários não cabem na RAM. Mas isto é ineficaz e a recomendação permanece em vigor.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Mais alguns exemplos, mas já duvido se são antipadrão ou não.

ClickHouse tem uma falha conhecida. Não sabe como atualizar*. De certa forma, isso é até bom. Se você tiver alguns dados importantes, por exemplo, contábeis, ninguém poderá enviá-los, pois não há atualizações.

* suporte para atualização e exclusão em modo lote foi adicionado há muito tempo.

Mas existem algumas maneiras especiais que permitem atualizações como se estivessem em segundo plano. Por exemplo, tabelas como ReplaceMergeTree. Eles fazem atualizações durante mesclagens em segundo plano. Você pode forçar isso usando otimizar tabela. Mas não faça isso com muita frequência, pois isso substituirá completamente a partição.

JOINs distribuídos no ClickHouse também são mal tratados pelo planejador de consultas.

Ruim, mas às vezes ok.

Usando ClickHouse apenas para ler dados usando select*.

Eu não recomendaria usar ClickHouse para cálculos complicados. Mas isto não é inteiramente verdade, porque já nos afastamos desta recomendação. E recentemente adicionamos a capacidade de aplicar modelos de aprendizado de máquina no ClickHouse - Catboost. E isso me incomoda porque penso: “Que horror. Isto é quantos ciclos por byte acontece! Eu realmente odeio desperdiçar relógios em bytes.

Uso eficaz do ClickHouse. Alexei Milovidov (Yandex)

Mas não tenha medo, instale o ClickHouse, tudo ficará bem. Na verdade, temos uma comunidade. A propósito, a comunidade é você. E se você tiver algum problema, você pode pelo menos ir ao nosso chat, e esperamos que eles te ajudem.

perguntas

Obrigado pelo relatório! Onde posso reclamar do travamento do ClickHouse?

Você pode reclamar comigo pessoalmente agora mesmo.

Recentemente comecei a usar ClickHouse. Eu imediatamente abandonei a interface cli.

Você tem sorte.

Um pouco depois travei o servidor com um pequeno select.

Você tem talento.

Abri um bug do GitHub, mas ele foi ignorado.

Nós veremos.

Alexey me enganou para que eu assistisse ao relatório, prometendo me dizer como você acessa os dados contidos nele.

Muito simples.

Eu percebi isso ontem. Mais detalhes.

Não existem truques terríveis aí. Há apenas compactação bloco por bloco. O padrão é LZ4, você pode ativar ZSTD*. Bloqueia de 64 kilobytes a 1 megabyte.

* também há suporte para codecs de compactação especializados que podem ser usados ​​​​em cadeia com outros algoritmos.

Os blocos são apenas dados brutos?

Não completamente cru. Existem matrizes. Se você tiver uma coluna numérica, os números consecutivos serão colocados em uma matriz.

Eu vejo

Alexey, um exemplo que foi com o uniqExact sobre IPs, ou seja, o fato de o uniqExact demorar mais para calcular por linhas do que por números, e assim por diante. E se usarmos uma finta com os ouvidos e lançarmos na hora da revisão? Ou seja, você parece ter dito que no nosso disco não é muito diferente. Se lermos as linhas do disco e lançarmos, nossas agregações serão mais rápidas ou não? Ou ainda vamos ganhar um pouco aqui? Parece-me que você testou isso, mas por algum motivo não indicou no benchmark.

Acho que será mais lento do que sem casting. Neste caso, o endereço IP deve ser analisado a partir da string. Claro, na ClickHouse, nossa análise de endereço IP também é otimizada. Tentamos muito, mas aí estão os números escritos na forma de dez milésimos. Muito desconfortável. Por outro lado, a função uniqExact funcionará mais lentamente em strings, não apenas porque são strings, mas também porque uma especialização diferente do algoritmo foi selecionada. As strings são simplesmente processadas de maneira diferente.

E se pegarmos um tipo de dados mais primitivo? Por exemplo, anotamos o ID do usuário que temos, anotamos como uma linha e depois embaralhamos, será mais divertido ou não?

Duvido. Acho que será ainda mais triste, porque afinal analisar números é um problema sério. Parece-me que este colega até fez um relatório sobre como é difícil analisar números na forma de dez milésimos, mas talvez não.

Alexey, muito obrigado pelo relatório! E muito obrigado pelo ClickHouse! Tenho uma pergunta sobre planos. Há planos para um recurso que atualize os dicionários de forma incompleta?

Ou seja, uma reinicialização parcial?

Sim Sim. Como a capacidade de definir um campo MySQL lá, ou seja, atualizar depois para que apenas esses dados sejam carregados se o dicionário for muito grande.

Um recurso muito interessante. E acho que alguma pessoa sugeriu isso em nosso chat. Talvez tenha sido até você.

Eu não acho.

Ótimo, agora existem dois pedidos. E você pode começar a fazer isso lentamente. Mas quero avisar desde já que esse recurso é bastante simples de implementar. Ou seja, em teoria, basta escrever o número da versão na tabela e depois escrever: versão menor que tal e tal. Isso significa que, muito provavelmente, ofereceremos isso aos entusiastas. Você é um entusiasta?

Sim, mas, infelizmente, não em C++.

Seus colegas sabem escrever em C++?

Vou encontrar alguém.

Ótimo*.

* o recurso foi adicionado dois meses após o relatório - o autor da pergunta o desenvolveu e enviou seu solicitação de recebimento.

Obrigado!

Olá! Obrigado pelo relatório! Você mencionou que o ClickHouse é muito bom em consumir todos os recursos disponíveis. E o palestrante ao lado da Luxoft falou sobre sua solução para o Russian Post. Ele disse que gostaram muito do ClickHouse, mas não o usaram no lugar do principal concorrente justamente porque estava consumindo toda a CPU. E eles não conseguiram conectá-lo à sua arquitetura, ao ZooKeeper com dockers. É possível limitar de alguma forma o ClickHouse para que ele não consuma tudo o que está disponível para ele?

Sim, é possível e muito fácil. Se você quiser consumir menos núcleos, basta escrever set max_threads = 1. E pronto, ele executará a solicitação em um núcleo. Além disso, você pode especificar configurações diferentes para usuários diferentes. Então não há problema. E diga aos seus colegas da Luxoft que não é bom que eles não tenham encontrado essa configuração na documentação.

Alexei, olá! Eu gostaria de perguntar sobre esta questão. Esta não é a primeira vez que ouço que muitas pessoas estão começando a usar o ClickHouse como armazenamento de logs. No relatório você disse para não fazer isso, ou seja, não precisa armazenar strings longas. O que você acha disso?

Em primeiro lugar, os logs não são, via de regra, sequências longas. Existem, é lógico, exceções. Por exemplo, algum serviço escrito em java lança uma exceção e é registrado. E assim por diante, em um loop infinito, e o espaço no disco rígido acaba. A solução é muito simples. Se as linhas forem muito longas, corte-as. O que significa muito tempo? Dezenas de kilobytes são ruins*.

* nas versões mais recentes do ClickHouse, a “granularidade de índice adaptativo” está habilitada, o que elimina o problema de armazenamento de linhas longas em sua maior parte.

Um kilobyte é normal?

Normalmente.

Olá! Obrigado pelo relatório! Já perguntei sobre isso no chat, mas não me lembro se recebi resposta. Existem planos para expandir de alguma forma a seção COM na forma de CTE?

Ainda não. Nossa seção COM é um tanto frívola. É como um pequeno recurso para nós.

Eu entendo. Obrigado!

Obrigado pelo relatório! Muito interessante! Questão global. Existem planos para modificar a exclusão de dados, talvez na forma de algum tipo de stubs?

Necessariamente. Esta é a nossa primeira tarefa na nossa fila. Agora estamos pensando ativamente em como fazer tudo corretamente. E você deve começar a pressionar o teclado*.

* apertou os botões do teclado e fez tudo.

Isso afetará de alguma forma o desempenho do sistema ou não? A inserção será tão rápida quanto agora?

Talvez as próprias exclusões e atualizações sejam muito pesadas, mas isso não afetará o desempenho das seleções ou das inserções.

E mais uma pequena pergunta. Na apresentação você falou sobre chave primária. Dessa forma, temos o particionamento, que por padrão é mensal, correto? E quando definimos um intervalo de datas que cabe em um mês, só essa partição é lida, certo?

Sim.

Uma pergunta. Se não podemos selecionar nenhuma chave primária, então é correto fazê-lo especificamente de acordo com o campo “Data” para que no fundo haja menos reorganização desses dados para que caibam de forma mais ordenada? Se você não possui consultas de intervalo e não consegue nem selecionar nenhuma chave primária, vale a pena colocar uma data na chave primária?

Sim.

Talvez faça sentido colocar um campo na chave primária que comprimirá melhor os dados se eles forem classificados por este campo. Por exemplo, ID do usuário. O usuário, por exemplo, acessa o mesmo site. Neste caso, coloque o ID do usuário e a hora. E então seus dados serão melhor compactados. Quanto à data, se você realmente não tem e nunca teve consultas de intervalo em datas, então não é necessário colocar a data na chave primária.

Ok, muito obrigado!

Fonte: habr.com

Adicionar um comentário