Conjunto de dados do Stack Overflow
s3://datasets-documentation/stackoverflow/parquet/:
As chaves primárias e os relacionamentos indicados não são impostos por meio de restrições (Parquet é um formato de arquivo, não de tabela) e apenas indicam como os dados se relacionam e quais chaves exclusivas eles têm.
O conjunto de dados do Stack Overflow contém várias tabelas relacionadas. Em qualquer tarefa de modelagem de dados, recomendamos que os usuários se concentrem primeiro em carregar a tabela principal. Ela não será necessariamente a maior tabela, mas sim aquela sobre a qual você espera executar a maior parte das consultas analíticas. Isso permitirá que você se familiarize com os principais conceitos e tipos do ClickHouse, algo especialmente importante para quem vem de um contexto predominantemente OLTP. Essa tabela pode precisar ser remodelada à medida que tabelas adicionais forem sendo adicionadas, para explorar totalmente os recursos do ClickHouse e obter o melhor desempenho. O esquema acima foi intencionalmente definido de forma não ideal para os propósitos deste guia.
Definir o esquema inicial
posts será o destino da maioria das consultas analíticas, vamos nos concentrar em definir o esquema dessa tabela. Esses dados estão disponíveis no bucket público do S3 s3://datasets-documentation/stackoverflow/parquet/posts/*.parquet, com um arquivo por ano.
Carregar dados do S3 no formato Parquet é a forma mais comum e recomendada de carregar dados no ClickHouse. O ClickHouse é otimizado para processar Parquet e pode potencialmente ler e inserir dezenas de milhões de linhas do S3 por segundo.O ClickHouse oferece um recurso de inferência de esquema para identificar automaticamente os tipos de um conjunto de dados. Isso é compatível com todos os formatos de dados, incluindo Parquet. Podemos usar esse recurso para identificar os tipos do ClickHouse para os dados por meio da função de tabela s3 e do comando
DESCRIBE. Observe abaixo que usamos o padrão glob *.parquet para ler todos os arquivos na pasta stackoverflow/parquet/posts.
A função de tabela S3 permite consultar, no ClickHouse, dados no S3 diretamente no local. Essa função é compatível com todos os formatos de arquivo suportados pelo ClickHouse.Isso nos fornece um esquema inicial não otimizado. Por padrão, o ClickHouse mapeia esses tipos para tipos Nullable equivalentes. Podemos criar uma tabela do ClickHouse usando esses tipos com um simples comando
CREATE EMPTY AS SELECT.
ORDER BY () significa que não temos índice e, mais especificamente, nenhuma ordenação nos dados. Falaremos mais sobre isso adiante. Por enquanto, basta saber que todas as consultas exigirão uma varredura linear.
Para confirmar que a tabela foi criada:
INSERT INTO SELECT, lendo-os com a função de tabela S3. O comando a seguir carrega os dados de posts em cerca de 2 min em uma instância de 8 núcleos do ClickHouse Cloud.
A consulta acima carrega 60 milhões de linhas. Embora isso seja pouco para o ClickHouse, usuários com conexões de internet mais lentas talvez prefiram carregar apenas um subconjunto dos dados. Isso pode ser feito simplesmente especificando os anos que desejam carregar por meio de um padrão glob, por exemplohttps://datasets-documentation.s3.eu-west-3.amazonaws.com/stackoverflow/parquet/posts/2008.parquetouhttps://datasets-documentation.s3.eu-west-3.amazonaws.com/stackoverflow/parquet/posts/{2008, 2009}.parquet. Veja aqui como padrões glob podem ser usados para selecionar subconjuntos de arquivos.
Otimizando tipos
Para entender por que o ClickHouse comprime dados tão bem, recomendamos este artigo. Em resumo, como um banco de dados orientado a colunas, os valores são gravados na ordem das colunas. Se esses valores estiverem ordenados, valores iguais ficarão adjacentes. Os algoritmos de compressão exploram padrões contíguos nos dados. Além disso, o ClickHouse tem codecs e tipos de dados granulares que permitem ajustar ainda mais as técnicas de compressão.A compressão no ClickHouse é impactada por 3 fatores principais: a chave de ordenação, os tipos de dados e os codecs usados. Todos eles são configurados por meio do esquema. O maior ganho inicial em compressão e desempenho de consultas pode ser obtido por meio de um processo simples de otimização de tipos. Algumas regras simples podem ser aplicadas para otimizar o esquema:
- Use tipos estritos - Nosso esquema inicial usava String em muitas colunas que claramente são numéricas. Usar os tipos corretos garante a semântica esperada ao filtrar e agregar. O mesmo se aplica aos tipos de data, que já foram fornecidos corretamente nos arquivos Parquet.
- Evite colunas Nullable - Por padrão, as colunas acima foram consideradas NULL. O tipo Nullable permite que as consultas diferenciem um valor vazio de NULL. Isso cria uma coluna separada do tipo UInt8. Essa coluna adicional precisa ser processada sempre que um usuário trabalha com uma coluna Nullable. Isso consome espaço de armazenamento adicional e quase sempre afeta negativamente o desempenho das consultas. Use Nullable apenas se houver diferença entre o valor vazio padrão de um tipo e NULL. Por exemplo, o valor 0 para valores vazios na coluna
ViewCountprovavelmente será suficiente para a maioria das consultas e não afetará os resultados. Se valores vazios precisarem ser tratados de forma diferente, muitas vezes eles também podem ser excluídos das consultas com um filter. - Use a precisão mínima para tipos numéricos - O ClickHouse tem vários tipos numéricos projetados para diferentes intervalos e níveis de precisão. Procure sempre minimizar o número de bits usados para representar uma coluna. Além de inteiros de tamanhos diferentes, por exemplo Int16, o ClickHouse oferece variantes sem sinal cujo valor mínimo é 0. Elas podem permitir o uso de menos bits em uma coluna; por exemplo, UInt16 tem valor máximo de 65535, o dobro de um Int16. Prefira esses tipos a variantes maiores com sinal, quando possível.
- Precisão mínima para tipos de data - O ClickHouse oferece suporte a vários tipos de data e data/hora. Date e Date32 podem ser usados para armazenar apenas datas, sendo que o segundo oferece suporte a um intervalo maior, ao custo de mais bits. DateTime e DateTime64 oferecem suporte a data e hora. DateTime é limitado à granularidade de segundos e usa 32 bits. DateTime64, como o nome sugere, usa 64 bits, mas oferece suporte até a granularidade de nanossegundos. Como sempre, escolha a versão mais grosseira aceitável para as consultas, minimizando o número de bits necessários.
- Use LowCardinality - Números, strings e colunas Date ou DateTime com poucos valores únicos podem potencialmente ser codificados usando o tipo LowCardinality. Essa codificação por dicionário reduz o tamanho em disco. Considere isso para colunas com menos de 10 mil valores únicos.
- FixedString para casos especiais - Strings com comprimento fixo podem ser codificadas com o tipo FixedString, por exemplo, códigos de idioma e moeda. Isso é eficiente quando os dados têm exatamente N bytes de comprimento. Em todos os outros casos, isso provavelmente reduz a eficiência, e LowCardinality é preferível.
- Enums para validação de dados - O tipo Enum pode ser usado para codificar com eficiência tipos enumerados. Enums podem ter 8 ou 16 bits, dependendo do número de valores únicos que precisam armazenar. Considere usá-lo se você precisar da validação associada no momento da insert (valores não declarados serão rejeitados) ou quiser realizar consultas que explorem uma ordenação natural nos valores de Enum; por exemplo, imagine uma coluna de feedback contendo respostas de usuários
Enum(':(' = 1, ':|' = 2, ':)' = 3).
Dica: Para encontrar o intervalo de todas as colunas e o número de valores distintos, você pode usar a consulta simples SELECT * APPLY min, * APPLY max, * APPLY uniq FROM table FORMAT Vertical. Recomendamos executar isso em um subconjunto menor dos dados, pois isso pode ser custoso. Essa consulta exige que os valores numéricos estejam definidos pelo menos como tal para produzir um resultado preciso, ou seja, não como String.
Ao aplicar essas regras simples à nossa tabela Posts, podemos identificar um tipo ideal para cada coluna:
O texto acima resulta no seguinte esquema:
INSERT INTO SELECT, lendo os dados da tabela anterior e inserindo-os nesta:
Escolhendo uma chave de ordenação
Todas as colunas de uma tabela serão ordenadas com base no valor da chave de ordenação especificada, independentemente de estarem incluídas na própria chave. Por exemplo, seAlgumas regras simples podem ser aplicadas para ajudar a escolher uma chave de ordenação. Às vezes, os critérios a seguir podem entrar em conflito, então considere-os nesta ordem. Você pode identificar várias chaves nesse processo, e 4–5 normalmente são suficientes:CreationDatefor usada como chave, a ordem dos valores em todas as outras colunas corresponderá à ordem dos valores na colunaCreationDate. Várias chaves de ordenação podem ser especificadas — isso ordenará os dados com a mesma semântica de uma cláusulaORDER BYem uma consultaSELECT.
- Selecione colunas que estejam alinhadas com seus filtros mais comuns. Se uma coluna é usada com frequência em cláusulas
WHERE, priorize incluí-la na chave em vez de outras usadas com menos frequência. Prefira colunas que ajudem a excluir uma grande porcentagem do total de linhas quando filtradas, reduzindo assim a quantidade de dados que precisa ser lida. - Prefira colunas com alta probabilidade de correlação com outras colunas da tabela. Isso ajuda a garantir que esses valores também sejam armazenados de forma contígua, melhorando a compressão.
As operações
GROUP BYeORDER BYsobre colunas da chave de ordenação também podem se tornar mais eficientes em termos de memória.
Exemplo
posts, vamos supor que os usuários queiram fazer análises com filtros por data e tipo de post, por exemplo:
“Quais perguntas tiveram mais comentários nos últimos 3 meses”.
A consulta para essa pergunta usando a tabela posts_v2 anterior, com tipos otimizados, mas sem chave de ordenação:
A consulta aqui é muito rápida, embora todas as 60 milhões de linhas tenham sido varridas linearmente — o ClickHouse é simplesmente rápido :) Você vai ter que confiar em nós: chaves de ordenação valem a pena em escala de TB e PB!Vamos selecionar as colunas
PostTypeId e CreationDate como nossas chaves de ordenação.
Talvez, no nosso caso, esperemos que os usuários sempre filtrem por PostTypeId. Isso tem cardinalidade 8 e representa a escolha lógica para a primeira entrada da nossa chave de ordenação. Como a filtragem com granularidade de data provavelmente será suficiente (e ainda beneficiará filtros de data e hora), usamos toDate(CreationDate) como o 2º componente da nossa chave. Isso também produzirá um índice menor, já que uma data pode ser representada com 16 bits, acelerando a filtragem. A entrada final da nossa chave é CommentCount, para ajudar a encontrar os posts com mais comentários (a ordenação final).
Próximo: Técnicas de Modelagem de Dados
Posts continue sendo nossa tabela central, por meio da qual a maioria das consultas analíticas é executada. Embora outras tabelas ainda possam ser consultadas isoladamente, partimos do princípio de que a maior parte das análises será feita no contexto de posts.
Ao longo desta seção, usamos variantes otimizadas das nossas outras tabelas. Embora forneçamos seus esquemas, por questão de brevidade omitimos as decisões tomadas. Elas se baseiam nas regras descritas anteriormente, e deixamos para o leitor inferi-las.As abordagens a seguir têm como objetivo minimizar a necessidade de usar JOINs para otimizar leituras e melhorar o desempenho das consultas. Embora JOINs tenham suporte completo no ClickHouse, recomendamos usá-los com moderação (2 a 3 tabelas em uma consulta com JOIN é aceitável) para obter o melhor desempenho.
O ClickHouse não tem o conceito de chaves estrangeiras. Isso não impede JOINs, mas significa que a integridade referencial fica a cargo do usuário, que deve gerenciá-la no nível da aplicação. Em sistemas OLAP como o ClickHouse, a integridade dos dados geralmente é gerenciada no nível da aplicação ou durante o processo de ingestão de dados, em vez de ser imposta pelo próprio banco de dados, onde isso gera uma sobrecarga significativa. Essa abordagem permite mais flexibilidade e inserção de dados mais rápida. Isso está alinhado ao foco do ClickHouse em velocidade e escalabilidade para consultas de leitura e inserção em conjuntos de dados muito grandes.Para minimizar o uso de JOINs no momento da consulta, os usuários têm várias ferramentas/abordagens:
- Desnormalização de dados - Desnormalize os dados combinando tabelas e usando tipos complexos para relacionamentos que não sejam 1:1. Isso geralmente envolve mover quaisquer JOINs do momento da consulta para o momento da inserção.
- Dictionaries - Um recurso específico do ClickHouse para lidar com direct joins e lookups de chave-valor.
- Views materializadas incrementais - Um recurso do ClickHouse para transferir o custo de uma computação do momento da consulta para o momento da inserção, incluindo a capacidade de calcular valores agregados incrementalmente.
- Views materializadas atualizáveis - Semelhante às visões materializadas usadas em outros produtos de banco de dados, isso permite que os resultados de uma consulta sejam calculados periodicamente e que o resultado seja armazenado em cache.