Arquivo da tag: MySQL

Formatos dos Dados

Este assunto não é novidade, mas ainda é bastante incompreendido. Seja em um banco de dados, ou em arquivos (Big Data), podemos escolher basicamente dois tipos de formatos de dados: organizado por linhas, ou por colunas.

A organização por linhas é mais conhecida. Ela é principalmente utilizada em sistemas de informação transacionais: esses que as empresas usam no dia-a-dia. A orientação dos dados por linha é otimizada para esse tipo de sistema, porém, para ambientes analíticos, onde realizamos consultas com grandes agregações, esse formato não é realmente eficiente.

A organização dos dados por colunas é diametralmente o oposto: terrível para sistemas transacionais, e excelente para ambientes analíticos (data warehouse, data lakes e data lakehouses).

Neste post vou explicar por que e onde um tipo de formato é melhor que o outro.

Organização por Linha

Primeiro temos que entender quais são as características de um sistema transacional (OLTP) no contexto de um banco de dados.

Geralmente esses sistemas são caracterizados por requisições curtas, e com grande volume de acesso concorrente. Exemplos de requisições curtas:

  • Quais são os dados do cliente 1002;
  • Insira um produto novo na tabela;
  • Atualize a quantidade deste produto no estoque;
  • Me dê todas as informações do funcionário 8567.

Ou seja, requisições curtas são operações realizadas no banco de dados que envolvem um ou poucos registros por vez, seja para consulta, ou alteração, e em grande volume (muitos usuários concorrentes).

Outra característica importante é que praticamente todas as requisições nesse contexto utilizam (ou, deveriam utilizar) a chave primária (índice) da tabela, isto é, sempre estamos filtrando por um cliente, por um produto, por um funcionário…

Normalmente as tabelas em um sistema transacional são normalizadas (3FN), ou seja, os dados são espalhados em várias tabelas diferentes (conectados por chaves primárias e estrangeiras) por questões de integridade, e para evitar redundância de informação.

Dito isto, vamos para o formato de linha com um exemplo simples:

Nesta tabela temos 6 linhas, e 5 colunas, mas pense que em um banco de dados de verdade temos milhões ou bilhões de linhas. Fisicamente essas linhas são armazenadas, uma após a outra, em blocos, e cada bloco pode armazenar uma centena de linhas.

Neste exemplo, suponha que cada bloco consegue armazenar 2 linhas:

Esse formato de linha é extremamente eficiente sempre que você faz uma busca em SQL como esta abaixo (típica em ambientes transacionais):

SELECT * FROM CLIENTES WHERE ID = 103

O banco de dados, usando o índice sobre a coluna ID, vai identificar que o bloco 2 possui a linha 103. Esse bloco será colocado em memória (se já não estiver lá), e o bloco será literalmente “varrido” até chegar na linha 103. Neste caso, todas as colunas desta linha serão retornadas na consulta.

Por que esse formato de linha é extremamente eficiente para consultas como essa que mostrei acima?

  • Não importa a quantidade de linhas, com o índice e a estrutura de blocos, o tempo de busca sempre será consistente e próximo do ótimo;
  • O tempo de resposta será geralmente o mesmo não importa se você consultar uma, duas, três ou todas as colunas da tabela: uma vez identificada a linha, todas as colunas desta linha estão fisicamente juntas;
  • Como as linhas estão fisicamente dispostas em centenas ou milhares de blocos diferentes, mesmo com muitos usuários concorrentes é razoável observar que haverá pouca concorrência em cada bloco, logo, esse formato é capaz de suportar uma grande quantidade de usuários simultâneos.

Agora vamos para um outro exemplo onde esse formato de linha não é muito bom. Suponha que fiz a seguinte consulta em SQL:

SELECT CIDADE, COUNT(*) as TOTAL
FROM CLIENTES
WHERE PAIS = 'BRASIL'
GROUP BY CIDADE

Esta consulta funciona com o formato de linhas, mas não é realmente eficiente. Em outras palavras, dependendo de alguns fatores, o desempenho desta consulta pode ser desastroso:

  • Se a quantidade de linhas na tabela for muito grande, a coluna PAÍS poderá não filtrar (restringir) muitos dados, e o banco de dados terá que processar uma grande quantidade de linhas para fazer o COUNT;
  • Se a quantidade de colunas na tabela for muito grande muitas delas serão “varridas”, mas apenas duas serão úteis para o resultado: neste exemplo CIDADE e PAIS;
  • A consulta poderá até retornar poucas linhas, mas terá que ler muitas linhas para compor o resultado, e por isso se houver muitos usuários concorrentes, todos lendo muitas linhas ao mesmo tempo, com certeza todos serão impactados no tempo de resposta (pela concorrência que um usuário gerará sobre os demais usuários);
  • A compressão dos dados não é boa, porque dentro de um mesmo bloco uma linha tem várias colunas de vários tipos (pouca repetição dos dados). Algoritmos de compressão são eficientes quando há bastante repetição dos dados dentro do mesmo bloco.

Nota: geralmente os algoritmos de compressão consideram um bloco de dados como “limite” dos dados a serem usados para compressão, de forma que os valores repetidos em um mesmo bloco seja substituído por ponteiros, economizando espaço.

Em outras palavras, quanto mais restritivas são as consultas (usando filtros pelo índice da chave primária, por exemplo), melhor será o formato de dados orientado por linhas. Quanto menos restritivas, ou seja, quanto maior o volume de dados movimentado pela consulta, pior será este formato.

Organização por Coluna

Vamos entender primeiro quais são as características de um sistema que possui um perfil mais analítico.

Geralmente esse perfil se caracteriza por requisições mais complexas, consultas que agregam muitos dados (relatórios), e com cargas de dados de alto volume, porém, possuem menor concorrência de usuários comparado com os sistemas transacionais.

Para acelerar as consultas, o modelo de dados geralmente é composto por tabelas mais desnormalizadas (dados mais repetidos, sem necessidade de ter que fazer joins), organizadas segundo padrões de design de suporte a decisão, como star schema ou snow flake.

Exemplos típicos onde encontraremos esse tipo de sistema: data warehouses, data marts, data lakes e data lakehouses.

O formato colunar é diametralmente o oposto do formato orientado por linha. Neste formato, os dados são fisicamente organizados pelas colunas, e não pelas linhas:

Na figura podemos ver que os dados de cada coluna são armazenados fisicamente juntos, em blocos diferentes. Isto é, o ID 100 está mais próximo do ID 101 fisicamente do que do nome Maria.

Então consultas que buscam retornar todas as colunas são terríveis neste formato, mesmo filtrando pela chave primária:

SELECT * FROM CLIENTES WHERE ID = 103

Isto ocorre porque neste caso teremos que fazer pelo menos 5 leituras físicas diferentes, uma em cada bloco (sendo que no formato por linha, todas as colunas estão fisicamente no mesmo bloco).

Agora, para consultas com perfil mais analítico, onde buscamos apenas por algumas colunas, e descartamos todas as outras, nada é mais eficiente que o formato colunar:

SELECT CIDADE, COUNT(*) as TOTAL
FROM CLIENTES
WHERE PAIS = 'BRASIL'
GROUP BY CIDADE

Neste exemplo, vamos ler apenas os blocos 4 (CIDADE) e 5 (PAÍS).

Por que esse formato colunar é extremamente eficiente para consultas como essa que mostrei acima?

  • Reduz bastante a quantidade de I/O físico a ser feita, pois o I/O é feito apenas sobre os blocos das colunas usadas na consulta (menos dados para ler);
  • Como os dados do mesmo tipo (mesma coluna) estão juntos, no mesmo bloco, a probabilidade de repetição de valores aumenta, e portanto a compressão dos dados também aumenta (por haver maior repetição dos dados de uma mesma coluna): menos I/O;
  • Muitos bancos de dados usam instruções do tipo SIMD (Single Instruction Multiple Data) nos processadores, fazendo com que com uma única instrução de leitura, o processador seja capaz de ler “várias linhas colunares” ao mesmo tempo, aumentando a performance.

Vale lembrar que o formato dos dados, seja linha ou coluna, é algo físico, e não lógico. Em outras palavras, para o usuário-final, ou o sistema, é transparente: utiliza-se SQL.

Exemplos de Tecnologias para cada Formato de Dados

Alguns exemplos de armazenamento no formato de linha:

  • Bancos de dados: Oracle, SQL Server, MySQL, PostgreSQL;
  • Arquivos: AVRO, CSV.

Alguns exemplos de armazenamento no formato de coluna:

  • Bancos de dados: HBase, Oracle ADW, AWS Redshift, GCP BigQuery;
  • Arquivos: ORC, Parquet.

Existem bancos de dados que fornecem um modelo híbrido entre linha e coluna, onde os dados no formato de linha são replicados para um cache colunar, porém, esta replicação é transparente para o sistema (mesmo endpoint de conexão).

Exemplos de bancos de dados que tem a capacidade híbrida: MySQL HeatWeave e Oracle Database In-Memory.

O formato híbrido permite que um sistema transacional co-exista com cargas de relatórios ao mesmo tempo, sem impacto no desempenho, e sem ter que depender de uma engenharia de dados para movimentar os dados entre meios de armazenamento de formatos diferentes.

Conclusão

É importante entender as características de acesso de um sistema que você está criando para selecionar o formato mais apropriado: que traz boa performance e baixo consumo de armazenamento de dados (compressão).

O formato de linha é melhor para sistemas OLTP, enquanto que o formato colunar é melhor para Data Warehouses, Data Lakes e Data Lakehouses.

O formato híbrido (linha e coluna) é o melhor para casos mistos, onde há nitidamente a necessidade de existir transações e relatórios analíticos sob um mesmo banco de dados.