BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos PostgreSQL BRIN índices como uma solução para Big Data

PostgreSQL BRIN índices como uma solução para Big Data

Pontos Principais

  • Escrever e ler muitos dados no Apache Cassandra não é um problema, mas deletar dados, isto pode ser um grande problema;
  • Ter muitas linhas marcadas para exclusão no Cassandra afeta o desempenho quando há limitação com infraestrutura;
  • O índice do tipo BRIN foi projetado para manipular tabelas muito grandes nas quais certas colunas têm alguma correlação natural com sua localização física na tabela;
  • Em muitas situações, devido as limitações orçamentárias e técnicas, cabe ao Arquiteto buscar alternativas que viabilizem o projeto.

Você já imaginou o PostgreSQL como uma solução para Big Data? Em um cenário o qual seja necessário escrever mais de duas mil linhas por segundo e ler mais de 60 mil linhas o mais rápido possível? Este artigo é um relato de experiência sobre os motivos pelos quais adotamos o PostgreSQL como solução de Big Data em substituição à ferramentas tradicionais como o Apache Cassandra.

Contexto

À época, precisávamos de um banco de dados capaz de armazenar grandes quantidades de dados e nos permitir ler esses dados o mais rápido possível. Estamos falando de quase 100 mil linhas escritas por minuto e, após todos os dados serem persistidos, nossa solução lia todos os dados e enviava ao Apache Spark para que fossem reduzidas. Para aumentar o grau de dificuldade, tínhamos pelo menos 8 threads gravando os dados ao mesmo tempo e, depois que eles eram persistidos, outras 8 threads faziam a leitura de todos esses dados para posterior análise.

Se você é da área de engenharia de dados, deve estar se perguntando:

Porque não utilizar Apache Cassandra para fazer isto?

O Apache Cassandra é uma boa alternativa e que se encaixa bem em muitos cenários, mas em nosso contexto em particular não, pois precisávamos deletar muitos dados toda vez que alcançávamos o limite de armazenamento o qual éramos limitados.

Estamos falando de um cenário onde os recursos de infraestrutura destinados à solução eram limitados por restrições orçamentárias e técnicas e que não nos oferecia alternativa para mover de tecnologia ou mesmo de plataforma. Estávamos amarrados a um PaaS e não tínhamos opção de mudar para um IaaS.

Escrever e ler muitos dados no Apache Cassandra não é um problema, mas deletar dados, isto pode ser um grande problema conforme descreveremos a seguir.

Tombstones

O Apache Cassandra é capaz de lidar com ações de exclusão que marcam as linhas que serão excluídas com um indicador para exclusão - os chamados Tombstones.

O Apache Cassandra não simplesmente exclui uma linha quando solicitado, é um procedimento deste produto marcar a linha para exclusão e notificar todos os nós antes que a exclusão efetivamente ocorra a fim de manter a consistência dos dados. É um problema comum na comunidade que faz uso do Cassandra que ter muitas linhas marcadas para exclusão afeta o desempenho e além disso, há um limite para a quantidade de linhas que podem ser marcadas para exclusão.

Devido a grande volumetria de dados do projeto o qual estamos tratando neste artigo, este limite era alcançado sem muito esforço. Em nosso contexto, quando o Apache Spark tentava ler os dados do Apache Cassandra, enfrentamos problemas com o tempo limite de conexão excedido e outros erros devido ao fato de os nós estarem muito ocupados lidando com a grande quantidade de linhas marcadas para exclusão.

Mais detalhes sobre problemas com Tombstones e deletes no Apache Cassandra podem ser encontrados nos blogs de Alain Rodriguez e Alla Babkina.

Tentamos várias combinações de TTL (Time to Live) e estratégias de compactação, mas devido ao fato de ter uma capacidade de armazenamento limitada, a alta taxa de gravação e leitura e muitos dados para excluir, precisamos repensar o modelo de dados e o banco de dados que estávamos utilizando na solução. E no nosso caso, tentamos mudar o banco de dados.

Mas por que o PostgreSQL? Por que não HBase ou MongoDB?

Para ser honesto, precisávamos de uma solução que pudesse ser usada na Heroku, sendo assim, não há um add-on HBase neste PaaS, e por este motivo descartamos. E para o MongoDB, estávamos começando a testar uma solução com este produto e ainda não nos sentíamos confiantes para utilizá-lo como solução. Neste momento, nos perguntamos. Porque não o PostgreSQL? Nós já fazíamos uso dele em outra parte do projeto utilizando JSONB, o que para nós resolveu um outro grande problema e foi incrível, pois funcionava muito bem e assim tomamos a decisão de mudar de direção e testar os recursos do PostgreSQL.

Como fazer para ler muitos dados muito rapidamente com o PostgreSQL?

A resposta foi: Utilizar índices BRIN - Block Range Index.

No nosso contexto, uma vez que muitos dados são altamente correlacionados por uma coluna específica, o BRIN foi de grande utilidade. Além disso, é muito simples usá-lo no PostgreSQL.

CREATE INDEX idx_brin_example

ON example_table

USING BRIN (correlated_column)

WITH (pages_per_range = 128);

Do manual do PostgreSQL sobre os índice BRIN temos:

BRIN significa Block Range Index. O BRIN é projetado para manipular tabelas muito grandes nas quais certas colunas têm alguma correlação natural com sua localização física na tabela. Um intervalo de blocos é um grupo de páginas fisicamente adjacentes na tabela; para cada intervalo de bloco, algumas informações sumarizadas são armazenadas pelo índice. Por exemplo, uma tabela que armazena os pedidos de venda de uma loja pode ter uma coluna de data na qual cada pedido foi feito e, na maioria das vezes, as entradas para pedidos anteriores aparecerão mais cedo na tabela; uma tabela armazenando uma coluna de CEP pode ter todos os códigos de uma cidade agrupados naturalmente.

Os índices do tipo BRIN podem satisfazer as consultas por meio de varreduras regulares de índice de bitmap e retornarão todas as tuplas em todas as páginas dentro de cada intervalo, se as informações sumarizadas armazenadas pelo índice forem consistentes com as condições da consulta. O executor da consulta é encarregado por verificar novamente essas tuplas e descartar aquelas que não correspondem às condições da consulta - em outras palavras, esses índices são perdidos. Como um índice BRIN é muito pequeno, a varredura do índice adiciona pouca sobrecarga em comparação com uma varredura seqüencial, mas pode evitar a varredura de grandes partes da tabela que não são conhecidas por conter tuplas correspondentes.

Em verdade, quando usado apropriadamente, um índice BRIN não terá melhor performance que um índice do tipo B-Tree, mas economizará cerca de 99% de espaço em disco. Uma das limitações que tínhamos em nosso contexto.

Mas e sobre a opção de particionamento de tabelas?

Nós também usamos particionamento. Em nosso caso, definimos uma partição com base no nome do arquivo da origem dos dados. E criamos dinamicamente as tabelas de partição com base em cada nome de arquivo. Depois de processarmos os dados, descartamos a tabela de partições e assim excluímos os dados.

E quanto à escrita dos dados?

Utilizamos Spring Data/JPA, dessa forma a escrita afetaria o processo de gravação, pois esses componentes verificam se o elemento existe para decidir se é uma ação update ou persist.

Em nosso contexto, uma linha nunca era atualizada e também não precisávamos verificar se ela existia, portanto, usamos o PreparedStatement para inserir várias linhas e evitar a verificação se o elemento existe. Além disso, usamos o recurso UNLOGGED TABLE do PostgreSQL. Este recurso, não grava no WAL (Write-Ahead Log) e numa eventual queda do servidor de banco de dados ocasionado por uma falha ou em um desligamento não planejado, a tabela é truncada. Se perdermos os dados dessa tabela específica, para nós não era um grandes problema pois projetamos a solução para que os dados nesta tabela sejam efêmeros. Se as políticas de replicação não enviarem o arquivo de dados para a aplicação, poderíamos enviá-los manualmente a fim de popular o banco de dados e essa é uma situação que em mais de 12 meses de projeto, nunca enfrentamos. E conhecemos sim a lei de Murphy.

O exemplo a seguir exemplifica o uso de UNLOGGED TABLE

CREATE UNLOGED TABLE unlogged_data (

id uuid PRIMARY KEY,

time timestamp,

user_id int,

filename varchar (255)

) PARTITION BY LIST(filename);

CREATE UNLOGGED TABLE IF NOT EXISTS partitioned_table_test

PARTITION OF unlogged_data

FOR VALUES IN ('source_filename');

O que aprendemos?

Devido à limitação de infraestrutura imposta pelo projeto, entendemos que, como arquitetos e desenvolvedores além de profissionais de TI, somos agentes de mudança, devemos constantemente buscar alternativas e que, em alguns casos, este novo caminho passará pelo uso e implementação de saídas simples para problemas que aparentemente se mostravam sem uma solução trivial.

Investimos alguns dias analisando a performance com a adoção do PostgreSQL e observamos que enquanto no Cassandra, quando um arquivo chegava para ser processado, ele crescia para 60 MB de tamanho após a decodificação e virar um arquivo CSV. Note que o tamanho original do arquivo de entrada era de 2MB. Com a adoção do PostgreSQL, essa limitação não era um problema e suportava a ingestão de dados sem denegrir a performance da solução.

O gráfico a seguir, apresenta as taxas de processamento assim como a volumetria de dados a medida que processávamos os arquivos.

Pontos de atenção

Antes de decidir migrar de uma solução de banco de dados, no caso do artigo, de Cassandra para PostgreSQL, considere os pontos:

  • Invista tempo para extrair métricas e dados para comparar soluções.
  • Construa, de preferência mais de uma, prova de conceito.
  • Fique atento ao número de conexões ao banco, pode ser um problema.
  • Considere concorrência durante a escrita
  • Verifique o seu modelo de dados, refatorando-o caso este não seja adequado para a transição

Conclusão

Em alguns casos, precisamos pensar fora da caixa. Esse foi um desses casos. Todos os dias vemos que existem muitas soluções possíveis para cada problema. Por que não devemos tentar algo incomum?

E você? Tem algum caso que gostaria de compartilhar?

Sobre os autores

Ronan Silva (LinkedIn), pós-graduando em Ciência de Dados, com mais de 10 anos de experiência em desenvolvimento e arquitetura de soluções de software, destacando áreas de gestão empresarial, comércio eletrônico, meios de pagamento, soluções na área aeroespacial e marketing; atualmente é Engenheiro de Software na Resultados Digitais.

Rafael Santana (LinkedIn) é graduado em Ciência da Computação pela UNIFESP. Com 6 anos de experiência em desenvolvimento de aplicações Java, em especial na criação de softwares para as áreas médica, logística, soluções em Big Data e na área aeroespacial. Atualmente é Desenvolvedor Java na Aubay em Lisboa.

Marcelo Costa (LinkedIn) é pós-graduado em Engenharia de Software pela UNICAMP. Atua em sistemas de alta complexidade desde 2002, liderando equipes multidisciplinares no desenvolvimento de soluções de software nas áreas de varejo, aeroespacial, logística, educação, saúde e finanças. Especializa-se em liderança de equipes e arquiteturas de soluções, na coleta inteligente de informações na Internet e de conteúdo eletronicamente disponível; atualmente é consultor em Arquitetura de Soluções.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT