Schemaless é um recurso oferecido por sistemas gerenciadores de banco de dados que fazem uso da abordagem NoSQL para armazenar dados sem que seja necessário se importar com a normalização dos mesmos. Produtos como o MongoDB e o Cassandra fazem uso deste recurso para permitir que informações sejam armazenadas deixando toda a lógica de tratamento e organização da informação no lado da aplicação.
Nos últimos anos, a escala para criação de aplicações que tratam documentos de forma não estruturada vem crescendo cada vez mais e com isso muitos desenvolvedores são incentivados a utilizar produtos capazes de armazenar informações sem se preocupar com a modelagem dos dados que uma abordagem de banco de dados relacional normalmente costuma utilizar. Este artigo, apresenta uma sugestão para utilização do PostgreSQL como forma de armazenar dados no formato schemaless utilizando um módulo contrib do PostgreSQL, disponível para uso desde a versão 8.4.
Schemaless e modelagem tradicional de dados
Utilizar shemaless, basicamente significa não se preocupar com os tipos de dados, nomes de colunas, nomes de tabelas e seus relacionamentos no formato relacional de um banco de dados, ou seja, não há necessidade de fazer normalização uma vez que a recuperação da informação pode ser realizada via endereçamento de referência simulando um vetor. Em teoria, o que este recurso permite é armazenar todos os dados sem o conhecimento prévio das chaves ou tipos de dados.
A informação é geralmente constituída por uma cadeia que representa a chave e os dados reais, e é nomeado de relacionamento "chave - valor". As informações em si são armazenadas em forma de algum dos tipos primitivos de uma linguagem de programação (uma string, um inteiro, um array ou um objeto) e são geridos por qualquer linguagem de programação capaz de realizar o gerenciamento do armazenamento das chaves e valores. A sugestão desta abordagem, é de substituir a necessidade de modelo de dados fixa e tornar a exigência de dados devidamente formatados menos rigorosa.
Em um SGBD tradicional, muito tempo é dispensado para criação de um bom esquema que reflita da melhor forma possível a modelagem de negócio. Perguntas como:
- o campo CNPJ é um int ?
- a data da consulta é do tipo time ou timestamp?
- O número do CPF pode ser armazenado como small int?
- Ou podemos armazena-lo como varchar?
- É um char?
- O campo CNPJ poderá ser indexado para que os usuários possam realizar pesquisas utilizando ele?
Durante o processo de modelagem de um banco de dados tradicional são realizadas ao extremo a fim de montar o melhor modelo de dados que possa atender às necessidades de um determinado produto/negócio.
Segundo os defensores da abordagem NoSQL, a resposta a essas perguntas resume-se a apenas micro otimizações. Imagine um cenário em que o campo CNPJ seja do tipo big int. Com o passar do tempo, mais e mais informações são inseridas em uma tabela e após algum tempo, é decidido que o campo precisa ser alterado para long. Ao final, se o banco de dados não for escalável horizontalmente não importará a escolha feita pois após 100, 1000, ou um bilhão de registros, possivelmente o tipo de dado não suportará mais informações ou não haverá mais espaço em disco o que acarretará em uma substituição de hardware ou mesmo poderão ocorrer situações de lentidão muito grande no banco de dados.
Diante de um cenário como este, muitos arquitetos, desenvolvedores e engenheiros de software buscam alternativas para construção de modelos de dados que lhes permitam não se preocupar com modelagem de dados tradicional e seus tipos de dados. Como exemplo, nos últimos anos, muitas empresas vem buscando criar aplicações móveis para seus produtos e devido ao grande e merecido crescimento de produtos como o MongoDB e Cassandra que oferecem armazenamento de dados com relacionamentos chave/valor, essas empresas são levadas a "adotar" novas tecnologias sem levar em conta os recursos que produtos já conhecidos por seus times de desenvolvimento sejam utilizados para suprir suas necessidades arquiteturais.
O Hstore
Hstore é um módulo contrib disponível desde a versão 8.4 do PostgreSQL. Sua proposta consiste em ser útil para armazenar conjuntos de dados compostos por chave e valor e que são armazenados em uma única coluna de uma tabela conforme a proposta schemaless de produtos como o MongoDB e Cassandra. Tornou-se mais popular com o lançamento da versão 9.1 do PostgreSQL e recentemente com o lançamento da versão 9.3 uma abordagem mais simples de se trabalhar com este tipo de armazenamento de informações foi incluída. Além destes fatores, o suporte por muitos frameworks como o Django, Rails/ActiveRecord, Sequel, e Node.js ajudaram a difundir seu uso.
Recentemente, em uma lista de profissionais PostgreSQL, Dave Sisk Arquiteto de Dados e DBA na Appia.com lançou o seguinte questionamento:
Quem precisa de MongoDB se no PostgreSQL podemos simular o mesmo comportamento de schemaless com Hstore?
Dave Sisk, defende seu questionamento argumentando que o PostgreSQL é capaz de desempenhar muitas tarefas com pares de chave/valor que não são possíveis fazer com MongoDB. Ele sugere a utilização de expressões que indexam informações e realizam filtragem de índices para fazer algumas otimizações avançadas que não são passíveis de se fazer com o MongoDB e cita por exemplo, a criação de índices parciais que podem ser utilizados apenas em linhas onde existam um determinado par chave/valor como nos casos em que uma chave esparsa, ou seja, uma chave para situações em que a maioria das linhas são do tipo null, com isso, é possível ter um índice bastante seletivo, rápido em que o otimizador estará preparado para utilizá-lo.
Apesar de todo esse avanço, Dave Sisk não deixa de citar que é um fato não ser possível alcançar a capacidade de distribuição de dados entre vários servidores apresentado pelo MongoDB. Porém, para substituir essa necessidade, com o PostgreSQL é possível fazer particionamento de uma tabela por herança além de ser possível "particionar" os índices parcialmente.
Um exemplo de uso do Hstore
Imagine um cenário em que seja preciso construir uma loja online que em princípio vende apenas livros. Neste caso, uma tabela de produtos composta pelos campos id, nome e descricao, onde o campo descricao é do tipo Hstore o qual dará flexibilidade sobre o que é armazenado na tabela:
# CREATE TABLE produto (
id serial PRIMARY KEY,
nome varchar,
descricao hstore
);
Com isso, inserindo alguns dados na tabela teremos a seguinte estrutura:
# INSERT INTO produto (nome, descricao) VALUES (
'The Bourne Sanction',
'autor => "Robert Ludlum's",
paginas => 688,
categoria => ficção,
dispositivo => Kindle,
formato => ebook'
);
Os dados em um campo Hstore podem ser consultados baseados no valor de sua descrição:
# SELECT nome, descricao->'dispositivo' as dispositivo
FROM produto
WHERE descricao → 'formato' = 'ebook';
Ou consultados baseados no valor de suas chaves:
# SELECT nome, descricao => 'paginas'
FROM produto
WHERE descricao ? 'paginas';
Com o tempo, um diretor informa a seguinte situação: "nossos livros não são lidos ou comprados. Vamos mudar nosso foco! Nós agora venderemos eletrônicos".
Esta decisão pode precisar de uma mudança no código ou mesmo na marca da empresa mas o esquema permanecerá inalterado.
# INSERT INTO produto (nome, descricao)
VALUES (
'Leica M9',
'fabricante => Leica,
tipo => camera,
megapixels => 18,
sensor => "full-frame 35mm"'
),
( 'MacBook Air 11',
'fabricante => Apple,
tipo => computer,
memoria_ram => 4GB,
armazenamento_hd => 256GB,
processador => "1.8 ghz Intel i7 core duo",
peso => 1kg'
);
É possível fazer uso de todos os recursos que um SGDB como PostgreSQL oferece para trabalhar com informações e com isso, campos do tipo Hstore também podem ser indexados:
# CREATE INDEX produto_fabricante
ON produto ((produto.descricao->'fabricante'));
Além de permitir fazer uso de JOINS:
# SELECT fabricante.pais, produto.nome
FROM produto, fabricante
WHERE produto.descricao -> 'fabricante' = fabricante.nome;
A flexibilidade para trabalhar com informações oferecido pelo Hstore torna seu uso cada vez mais vantajoso uma vez que a forma de armazenamento das informações, além de similar a oferecida por produtos NoSQL, é realizada na estrutura do banco de dados em formato binário o que deixa seu desempenho ainda maior conforme gráfico apresentado por Christophe Pettus durante o PgDay FOSDEM 2013 realizado na Bélgica.
Tempo de gravação em registros por segundo
O que há de novo para o Hstore
Oleg Bartunov, um dos desenvolvedores mais ativos do núcleo do PostgreSQL, publicou em seu blog pessoal como andam os trabalhos sobre o Hstore e quais as expectativas para este módulo para a próxima versão 9.4 que espera serem aprovados para o lançamento de 2014. Ele detalha alguns pontos importantes que foram apresentados durante o PGCon-2013 em Ottawa.
O módulo Hstore foi projetado originalmente como um tipo de dados para armazenamento de pares chave/valor com um rico conjunto de funções e operadores. No entanto, rapidamente se tornou popular devido ao seu bom desempenho fornecidos pela uso de índices GIN e GIST além de fornecer a flexibilidade do modelo chave/valor. É um recurso maduro, com mais de 10 anos de liberação da 1ª versão e uma extensão do PostgreSQL muito estável.
O fato é que o PostgreSQL precisa de um suporte eficiente para modelos baseados em documentos tal como o CouchDB, MongoDB, Cassandra, entre outros. Atualmente, este tipo de recurso pode ser modelado também usando o tipo de dados JSON. JSON é um tipo de dados relativamente novo no PostgreSQL e possui uma grande desvantagem por ser basicamente uma string. Uma string que precisa ser analisada e tratada a cada acesso o que torna seu uso em consultas a grandes volume de dados muito lento.
Em uma comparação de desempenho rápida usando dados sintéticos, é possível observar o tempo de execução quando dados são armazenados em formato JSON comparados com Hstore.
CREATE TABLE Hstore_test AS (SELECT 'a=>1, b=>2, c=>3, d=>4, e=>5'::Hstore AS v FROM generate_series(1,1000000));
CREATE TABLE json_test AS (SELECT '{"a":1, "b":2, "c":3, "d":4, "e":5}'::json AS v FROM generate_series(1,1000000));
SELECT sum((v->'a')::text::int) FROM json_test;
1291,060 ms
SELECT sum((v->'a')::int) FROM Hstore_test;
303,267 ms
Oleg Bartunov relata que a decisão por desenvolver uma representação binária para objetos aninhados diferente do JSON foi acordada entre o time principal de desenvolvedores para não afetar o núcleo do PostgreSQL e que os novos recursos em desenvolvimento poderão ser utilizados tanto com o Hstore como eventualmente com o JSON e cita:
Por que escolhemos Hstore e não simplesmente iniciamos um trabalho mais aprofundado com JSON? A resposta é que JSON é um tipo de dados central e importante, as alterações no núcleo do PostgreSQL certamente iriam requerer muito mais esforço, enquanto o Hstore ainda é um módulo, podemos trabalhar a vontade com ele. Para nós, ainda não ficou claro se teremos sucesso, por isso decidimos ter uma certa liberdade com nosso protótipo. Uma vez que exista uma representação binária eficiente, outros desenvolvedores poderão escolher por começar a trabalhar no apoio ao JSON.
Mesmo já utilizado em muitas situações como a citada por Dave Sisk no início deste artigo, muitas melhorias ainda são necessárias no módulo Hstore para que ele seja incorporado de fato ao núcleo do PostgreSQL. A proposta de Oleg Burtanov e seu grupo é de se empenhar para incrementar o Hstore, e eles esperam conseguir incorporar algumas dessas melhorias no lançamento da próxima versão do PostgreSQL. Até o momento, o resumo dos resultados alcançados está em:
- O Hstore agora é um tipo aninhado de dados que suporta matrizes, o que significa construir um modelo simples de chave/valor para um modelo baseado em documentos ricos(dados não estruturados que originalmente estejam em formato PDF, doc ou similares);
- O acesso a um determinado campo utilizando Hstore é mais rápido graças a sua representação binária;
- Operadores Hstore podem usar índices GIST e GIN;
- Usuários JSON podem fazer uso de índices funcionais GIN e com isto conseguir um aumento de velocidade considerável;
- A representação binária do tipo de dados Hstore pode ser usada por JSON
Ainda há muito trabalho a ser feito, para a versão 9.4 do PostgreSQL, a pretensão é adicionar suporte a representações binárias, para que seja possível ser usado com tipos de dados JSON melhorando sua indexação. Além disso, há a necessidade de desenvolvimento de uma linguagem de consulta Hstore que permitirá uma melhor manipulação de elementos do tipo Hstore.
Conclusões
O ritmo de desenvolvimento de software atual é bastante acelerado e a cada dia a exigência de prazos menores para que as mudanças ocorram visando as necessidades que o mercado aguarda é maior. Desenvolvimento ágil, frameworks para desenvolvimento de aplicações mais rápidas, plataformas automáticas que permitem deploy em Cloud são os atuais multiplicadores e motores que estão conduzindo essas mudanças. O projeto PostgreSQL tem observado essas tendências e abraçado estas causas. Se o seu projeto de software precisa de uma mudança rápida de requisitos, ou não se encaixa com esquemas pré-definidos tradicionais (aplicações que lidam com uma grande quantidade de informações não estruturadas por exemplo), Hstore pode ser a solução para este problema sem que haja necessidade do time de desenvolvimento aprender uma nova tecnologia.
Sobre o Autor
Marcelo Costa (LinkedIn, Twitter) é pós-graduado em Engenharia de Software pela UNICAMP. Atua em sistemas de alta complexidade desde 2002, gerenciando equipes multidisciplinares no desenvolvimento de software nas áreas de educação, saúde e finanças. Especializa-se na coleta inteligente de informações na internet e de conteúdo eletronicamente disponível; atualmente é Growth Hacker e Engenheiro de Software na Collect-In Tecnologia. Possui experiência com Lean, Kanban, Scrum, SOA, ALM, PostgreSQL, Shell Script, Java, PHP e Python.