Arquivo da tag: Schema on Write

Document Stores?

Eles são bancos de dados Relacionais ao contrário, porque ao invés de criar as tabelas e escrever os dados, você cria os dados e escreve as tabelas. Eles vieram, provavelmente, da mente de algum desenvolvedor de front-end que se cansou do pragmatismo dos Relacionais, e se revoltou. Não farão nada com a gente: os bancos Multi-Model já o absorveram! 


Documento é uma forma alternativa de persistência que se popularizou com o formato XML há alguns anos, e hoje está mais presente com o formato JSON, que tem melhor usabilidade.

Opinião sincera e desnecessária sobre a comparação entre XML e JSON para a narrativa de quando usar o que, como, e onde: esqueça XML, use JSON. Não é o objetivo deste post comparar os dois, mas vamos lá: além de ridículo, antigo, e ocupar muito espaço, XML me lembra muito HTML, e eu detesto HTML! Quando pessoas fazem o trabalho sujo de desenvolver ferramentas como o DreamWeaver, que nos permitem NÃO VER códigos HTML, eu definitvamente me sinto no topo da cadeia alimentar…

Normalmente utilizado para troca de dados entre sistemas, um documento JSON também pode ser utilizado como um formato de armazenamento [sempre surge alguém com uma ideia brilhante]. Então vou compará-lo com o modelo Relacional, o mais popular.

No modelo Relacional, uma tabela é um conjunto de linhas, e cada linha é um conjunto de colunas. No modelo de Documentos, uma coleção é um conjunto de documentos, e cada documento possui atributos. Então uma coleção é como se fosse uma tabela, um documento é como se fosse uma linha, e seus atributos é como se fossem colunas.

A diferença é que cada documento pode ser diferente em uma coleção, e em cada documento, seus atributos também podem ser diferentes, e ainda cada um desses atributos pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos [não estou repetindo só pro meu post ter mais palavras. É que, de fato, rola um lance recursivo aqui, e incrivelmente eles não entram em loop].

Leia o parágrafo anterior 3 vezes. Eu sei que ficou confuso, e você não entendeu. Na terceira re-leitura você vai entender, e também vai notar que fiz o melhor que pude. A confusão faz parte desse tipo de banco de dados. Eu mesmo li três vezes pra entender o que escrevi.

No modelo Relacional, a estrutura deve ser definida antes de escrever os dados, enquanto que no modelo de Documentos, você pode escrever sem ter uma estrutura. A aplicação que escreve é que define a estrutura dos dados.

Se você conhece os bancos de dados Orientados a Objeto [faz parte do passado, e ninguém sente saudade], um banco de dados de Documentos vai lhe parecer bastante familiar, pois ambos permitem estruturas aninhadas [um atributo dentro de outro]. Porém, as semelhanças param por aí. O banco de dados de Documentos tem característica schemaless, onde você não especifica a estrutura antes de escrever os dados.

Reforçando os conceitos para você não esquecer nunca mais: 

Schema-full, ou Schema on Write

Quando a estrutura da tabela e as regras são definidas no banco de dados, e o código da aplicação deve respeita-las. Típico do banco de dados Relacional.

Schemaless, ou Schema ou Read

Quando a estrutura da tabela e as regras são definidas na aplicação, e no banco de dados se define apenas o nome da tabela, ou coleção. Típico do banco de dados de Documentos.

Neste post vou explorar as principais características de um banco de dados de Documentos para você utilizar quando for necessário.

Ele é necessário, e você precisa conhecer. É sério.

Vamos Olhar por Dentro: Coloque as Crianças pra Dormir!

O formato Java Script Object Notation [JSON] surgiu por volta do ano 2000, e logo se tornou popular principalmente pela sua simplicidade. Ele é mais compacto do que XML e consegue representar estruturas complexas, que não são possíveis na forma tabular comum nos bancos relacionais. 

{
 id: 100, 
 nome: “Suco”, 
 dept: “Bebidas”, 
 qtd: 10, 
 vl: 4.50
}

O documento JSON acima é um dos mais simples, e nota-se que ele se parece muito com uma estrutura chave-valor. O documento é tudo que está entre as chaves {}, e os atributos estão separados por vírgulas.

Ao buscar um atributo, obtem-se o valor correspondente: nome, por exemplo, retorna Suco.

Vamos ver um exemplo no Banco de Dados Oracle.

No Oracle 21c, criamos uma tabela comum, e os documentos são armazenados em uma coluna desta tabela, que representa a coleção, quando especificamos o datatype JSON.

Abaixo, eu crio uma tabela produtos que armazena uma coleção na coluna prodDocument, que é o documento JSON acima. Depois consulto o atributo nome:

CREATE TABLE produtos
(id INT, prodDocument JSON);

INSERT INTO produtos VALUES (1,
'{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50}');

SELECT p.prodDocument.nome FROM produtos p;

É possível também colocar documentos dentro de documentos:

{"Produtos":
[
{id: 100, nome: “Suco”, dept: “Bebidas”, qtd: 10, vl: 4.50},
{id: 200, nome: “Chá”, dept: “Bebidas”, qtd: 31, vl: 6.75},
{id: 300, nome: “Água”, dept: “Bebidas”, qtd: 17, vl: 1.70}
]
}

Então basta abrir colchetes [observação desnecessária: eu definitivamente substitui os parenteses por eles nos meus textos, você já deve ter percebido] na parte do valor de um atributo para aninhar outros documentos JSON.

Na mesma tabela produtos vou incluir outra linha, desta vez com 3 documentos:

INSERT INTO produtos VALUES (2,
'{"Produtos":[
{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
{id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
{id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
]}');

SELECT p.prodDocument.Produtos.nome
FROM produtos p
WHERE id = 2;

Perceba que a primeira linha que inseri [id = 1] tem um documento JSON com 5 atributos. E a segunda linha [id = 2] tem 3 documentos JSON com 5 atributos. Isto é, a segunda linha é aninhada, pois tem vários documentos. Agora veja este outro exemplo mais interessante:   

{OrdemVenda: 
[
 {id: 1,
  loja: "SP-A",
  data: "01-10-2018",
  items: [
    {id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
    {id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
    {id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
   ]},
 {id: 2,
  loja: "SP-B",
  data: "01-10-2018",
  items: [
    {id: 300, nome: "Água", dept: "Bebidas", qtd: 1, vl: 1.70},
    {id: 110, nome: "Café", dept: "Bebidas", qtd: 2, vl: 3.20}
   ]}
]}

No exemplo acima temos um documento com Ordens de Venda. Precisamente temos duas Ordens, sendo que a primeira tem três itens vendidos, e a segunda tem dois itens.

Basicamente modelamos em um documento o que seria equivalente a 5 tabelas em um modelo Relacional Normalizado em 3NF [Ordem, ItemOrdem, Loja, Produto e Departamento].

Agora quero retornar a quantidade total de itens vendidos em todas as Ordens.

Mas antes veja que este documento se lê assim: ele é composto por Ordens de Venda com os atributos [id, loja, data e items], que por sua vez, items é composto por [id, nome, dept, qtd e vl]. Então para somar a quantidade, eu tenho que percorrer as ordens, depois os items, e então buscar [qtd].

Vou usar a função JSON_TABLE para converter o JSON com Arrays para Relacional para poder somar.

Para cada array [OrdemVenda e Items] precisamos incluir a cláusula NESTED com o PATH do atributo que queremos retornar. $ indica a raíz do documento, ou o ponto onde eu parei no caso da cláusula NESTED. O asterisco entre colchetes é para retornar todos os itens do array – poderia passar 0 para retornar o primeiro item, 1 para o segundo, e assim por diante:

INSERT INTO produtos VALUES (3,
'{OrdemVenda: [
{id: 1,
loja: "SP-A",
data: "01-10-2018",
items: [
{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
{id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
{id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
]},
{id: 2,
loja: "SP-B",
data: "01-10-2018",
items: [
{id: 300, nome: "Água", dept: "Bebidas", qtd: 1, vl: 1.70},
{id: 110, nome: "Café", dept: "Bebidas", qtd: 2, vl: 3.20}
]}
]}');

SELECT SUM(qtd)
FROM produtos p,
     JSON_TABLE(p.prodDocument, '$'
                 COLUMNS (
                          NESTED PATH '$.OrdemVenda[*]'
                          COLUMNS (
                                   NESTED PATH '$.items[*]'
                                   COLUMNS (qtd NUMBER PATH '$.qtd')
                                   )
                         )
                ) as prodDocument
WHERE id = 3

Em breve vou fazer um artigo somente sobre a manipulação de documentos JSON no banco de dados Oracle. Há muitas funcionalidades que podemos explorar.

Fiz um post há um tempo sobre algumas funções poderosas no Oracle para fazer transformação de dados. Algumas delas são para transformar documentos JSON. Veja aqui.

Já Passa da Meia-Noite, Vamos Falar dos Benefícios, Comparando com o Modelo Relacional

DOCUMENTOS SÃO SCHEMA ON READ. A principal característica de um Documento JSON no banco de dados é o fato de ele ser schema on read. Isto é, você não precisa criar uma estrutura rígida que uma tabela tem [colunas, datatypes, constraints] para depois colocar os dados: a aplicação que determina como os dados são escritos, sem a especificar previamente a estrutura – veja claramente nos exemplos que criei mais cedo neste artigo.

Essa característica permite um desenvolvimento mais flexível, por exemplo: em uma tabela “Clientes” você pode ter clientes com diferentes tipos e quantidades de contato, como telefone, celular, email, Facebook, Whatsapp, Linkedin, etc. Mas nem todos os clientes precisam ter todos os contatos.

Em um modelo Relacional, a tabela Clientes poderia ter muitas colunas com valores nulos [porque nem todos teriam todas essas formas de contato], ou teríamos que normalizar as tabelas, criando outras, e usando joins nas consultas, podendo eventualmente deixa-las mais lentas.

{"nome": "João",  "telefone" : "911112222"}
{"nome": "Mario", "telefone" : "922223333", "twitter": "@mario"}

Com a tabela [coleção] armazenando os clientes desta forma, uma query que busca “twitter” para um cliente que não tenha esta informação receberia um null.

COM DOCUMENTOS AS MUDANÇAS NO BANCO DE DADOS SÃO MAIS ÁGEIS. Não é necessário fazer DDL [ALTER TABLE] para alterar a estrutura de uma coleção, porque a estrutura na prática está nos documentos [schema on read, lembra?]. Se a aplicação quiser incluir um “Complemento de Endereço” na coleção “Clientes” por exemplo, basta incluir a nova informação na aplicação, sem precisar alterar o banco de dados. De fato essa característica torna as mudanças mais ágeis. Entretanto, alguns bancos relacionais, como o Oracle por exemplo, permite alterar uma estrutura relacional sem downtime – ainda que, também, suporta o modelo de Documentos, conforme observamos nos exemplos.

OS DOCUMENTOS MINIMIZAM O USO DE JOINS, E POR ISSO AS CONSULTAS SÃO MAIS RÁPIDAS. Uma outra característica interessante do JSON é o fato de armazenar os “relacionamentos” no mesmo documento, sem precisar recorrer a joins como ocorre no modelo Relacional. No exemplo da Ordem de Venda mais acima, temos os pais [ordens] e os filhos [itens das ordens] juntos no mesmo documento. Isto indica que ao modelar você pode pensar na forma como a aplicação vai funcionar, e então especificar um Documento que terá todas as informações contidas nele.

OS DOCUMENTOS TRABALHAM MELHOR COM API REST, A SENSAÇÃO DO MOMENTO. JSON é o formato mais comum quando se utiliza as populares APIs REST [chamadas por http]. Consultar uma coleção retorna nativamente um JSON, sem necessitar de conversão.

Diga-me o que Você Fala só para seus Amigos em Particular

SCHEMA ON READ REQUER CUIDADO. A característica schema on read promove agilidade no desenvolvimento, mas impõe maior governança por parte do desenvolvedor. Veja o seguinte exemplo:

{cliente_id:100, desc_cep: "11111-100"}
{cliente_id:101, desc_cep: "22222-100"}
{cliente_id:102, desc_cep: "33333-100"}
{cliente_id:103, cep: "44444-100"}
{cliente_id:"texto", cep: "55555-100"}

A coleção utiliza como padrão desc_cep para indicar o CEP, mas em algum momento a informação foi escrita como cep. Como o atributo cep ou desc_cep não tem a integridade exercida no banco de dados, a informação é gravada errada. O mesmo ocorre com client_id: o último está como texto ao invés de número. Um find para buscar a informação certamente não traria o resultado correto.

LEITURA DE MUITOS DOCUMENTOS DE UMA SÓ VEZ. O fato de não fazer joins porque toda a informação está contida em um mesmo documento só é vantagem quando a maioria das buscas forem para um ou poucos documentos. Suponha que uma consulta retorne 10 mil documentos: um I/O vai trazer muito mais dados no formato Relacional do que no formato JSON, mesmo fazendo joins. Isso ocorre porque no modelo Relacional as linhas estão mais próximas fisicamente, pois as tabelas são normalizadas, e portanto faz menos I/O. No JSON não se normaliza, então existe muita redundância e isto implica em maior consumo de armazenamento [menos “dados” por I/O].

COMPRESSÃO MAIS OU MENOS. Há um trade-off entre compressão e performance, em especial para o formato de Documentos. Em geral os bancos de dados de Documentos não possuem compressões colunares ou deduplicações. Eles recorrem a algoritmos mais lentos como zlib que podem até gerar uma boa redução, mas ao custo de impactar no desempenho das leituras.

CONTROLE TRANSACIONAL PRECÁRIO. Os sistemas de banco de dados puramente baseados em Documentos JSON, em geral, não suportam ACID nativamente, ou suportam com alguma restrição. Isto significa que se você transacionar sobre documentos terá que gerenciar as transações manualmente na aplicação ou por meio de alguma API. Boa sorte. 

RELACIONAMENTOS SÃO UM PARTO. Os defensores deste formato argumentam que os Documentos não precisam de relacionamentos, pois eles estão todos auto-contidos no mesmo Documento. Mas o mundo é feito de relacionamentos, e eles são complexos em especial quando são do tipo muitos-para-muitos. Veja um exemplo: em um sistema nós temos papéis e usuários. Você pode ter uma coleção “Papéis” cujos documentos contém para cada papel todos os seus usuários, ou uma coleção “Usuários” cujos documentos contém para cada usuário todos os seus papéis. Qualquer atributo de Papéis ou Usuários, como “descrição do Papel” ou “Nome do Usuário”, vai se repetir nos documentos, criando redundância e margem para bugs… é possível, entretanto, utilizar IDs ao invés de “embeddar” um documento no outro, mas aí é como no Relacional, só que desta vez com menos performance, pois o Relacional já é otimizado para relacionamentos.

Então modelar relacionamentos em um banco de dados de Documentos exige maior skill do desenvolvedor para não implementar alguma prática que depois se torne um problema — e não há estrutura schemaless que salve o esforço de mudança depois.

Para te deixar Menos Relacional por Entender Melhor como os Bancos de Documentos Funcionam. Vamos Finalmente para as Considerações Finais!

Uma coleção schemaless no banco de dados é na verdade um “schema implícito” porque o “schema” sempre existe em algum lugar: neste caso, no código da aplicação.

É necessário um schema para determinar se o correto é “DataNasc” ou “Data_Nascimento”. A flexibilidade no banco de dados vem ao custo de uma maior governança na aplicação.

Ter todas as informações contidas em um Documento para evitar joins não necessariamente é um benefício de performance. Consultas SQL com muitos joins são em geral uma deficiência de design da aplicação do que propriamente do modelo Relacional. Documentos JSON muito aninhados [documentos dentro de documentos] também podem ser um problema, e uma deficiência da aplicação.

Há restrições importantes quando armazenamos JSON em um banco de dados de Documentos, como controle transacional, tipos de leituras realizadas e inclusive tamanho do banco de dados.

O banco de dados de Documentos é um modelo bastante interessante que endereça alguns casos de usos. No entanto, na minha visão ele é um formato COMPLEMENTAR ao Relacional.

Recomendo ARMAZENAR DOCUMENTOS no banco de dados RELACIONAL quando:

[1] Houver a necessidade da aplicação criar atributos de forma ad-hoc, em runtime, com formatos diversos.

[2] A aplicação exigir que se normalize uma informação tal que gere muitas colunas com Nulos em várias linhas.

Recomendo ARMAZENAR DOCUMENTOS em um banco de dados DE DOCUMENTOS quando:

[3] A origem [quem gerou] e o fim [quem vai consumir] também forem JSON. Neste caso não faria sentido o meio [o banco de dados] ter outro formato, e em especial se o dado tem perfil transitório, isto é, ele é consumido rapidamente e fica como histórico, sem pós-processamentos [ex: IOT, logs, configurações, etc].

Em geral, minha primeira opção é optar por schema on write [Relacional], e complementar com schema on read [JSON] se for necessário.

Tenho uma preferência particular por bancos de dados Multi-Model, que permitem vários formatos nativamente ao mesmo tempo, em detrimento dos bancos de dados especializados. E por isso, em uma estrutura multi-model, você pode usar os formatos mais adequados para os mais diversos casos de uso sem muita complexidade.

Hoje em dia as aplicações tem necessidades de Dados mais poliglotas, e ter várias tecnologias especialistas aumenta bastante a complexidade.

Pense no caso de uso, e não na plataforma.

Se você pensar em “plataforma” para endereçar um caso de uso, vai acabar colocando um banco de dados muito especializado que só faz aquilo: se os requisitos mudarem, você ficará sem saída. Um banco de dados multi-model é capaz de atender vários casos de uso, e você não precisa se preocupar com a plataforma.

Você pode filtrar por Document Store no site DB-Engines, para ver quais são os bancos de dados especializados em Documentos mais populares. MongoDB é o banco de Documentos mais popular, mas é importante notar que os bancos mais populares da lista [1. Oracle e 2. MySQL] são multi-model, e aceitam Documentos JSON nativamente.

Publicidade

Há Uma Coisa em Comum entre os Duendes que Vivem Embaixo da sua Cama, e os Bancos de Dados Schemaless

Ambos não existem!


Do ponto de vista da aplicação, na prática, schemaless não existe.

Schemaless, ou schema on read, é quando não existe a estrutura de dados definida [colunas, datatypes, constraints] quando se escreve, pois quem a define é quem lê [a aplicação]. O oposto é Schema-full, ou schema on write, normalmente utilizado pelos bancos de dados Relacionais: define-se a estrutura, e depois insere os dados.

Provavelmente a fábula do schemaless parece ter sido criada por desenvolvedores de front-end que, supostamente, teriam muito mais agilidade para desenvolver e mudar as aplicações sem depender do banco de dados [e seus agregados, como DBAs, indisponibilidade, processos de mudança, etc].

Mas a realidade não é bem assim: tudo SEMPRE tem schema, e se tem schema, mudanças SEMPRE têm esforço.

Schema é o conjunto de regras de integridade que você define para organizar os dados. O sufixo full é quando o BANCO DE DADOS exerce o schema, e o sufixo less é quando VOCÊ exerce. SEMPRE ALGUÉM EXERCE O SCHEMA.

Nos últimos 30 anos os principais bancos de dados criaram mecanismos para implementar recursos de integridade com escalabilidade e disponibilidade. Em alguns bancos de dados é possível fazer alterações de estrutura com zero downtime para a aplicação, e mesmo assim garantir toda a integridade.

Não há como uma aplicação não ter schema. Se você utiliza um Document Store por exemplo, onde os dados persistem como documentos JSON [logo, schemaless], a aplicação tem que saber como ler esses documentos. Saber como ler significa ter schema. Quando você lê um documento JSON e extrai dele um valor numérico para fazer um cálculo, terá que convertê-lo para int, float ou Decimal [um type, logo, um schema]. A propósito, Python é uma linguagem dinamicamente tipada, e mesmo que você não especifique o type estaticamente enquanto programa, deve especifica-lo mentalmente [logo, um schema] para não gerar erro durante a execução.

No final do dia, quando você entende que sempre existe um schema, não há nada que um banco de dados schemaless faça com mais agilidade do que um banco de dados schema-full. A questão é se você quer deixar que o banco de dados exerça o schema por você, ou você adia o trabalho inevitável de VOCÊ exercer o schema depois.  

Eu tenho uma preferência particular por bancos de dados Multi-Model, pois há situações onde o uso conjunto da persistência Relacional e Documentos oferece o melhor da flexibilidade com o melhor da integridade.