Arquivo do autor:Fernando Melo

Sobre Fernando Melo

Fernando Melo é cientista da computação especializado em Data Management, e é apaixonado pelas tecnologias que mudam positivamente a vida das pessoas. Atualmente trabalha como Engenheiro de Solução para a Área de Gestão de Dados, e escreve este blog. Você pode segui-lo no Twitter / Instagram / Facebook @fmelodb e YouTube no canal Universo dos Dados. Nota: os textos e interesses neste blog refletem minha opinião pessoal, e não necessariamente representam as visões do meu empregador.

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.

Publicidade

OCI NoSQL Tables

Geralmente temos servidores e storage, e então bancos de dados, e claro, tabelas. No entanto, com o avanço das arquiteturas cloud native existe uma outra abordagem: as tabelas sem bancos de dados e sem servidores (tabelas serverless).

Essas tabelas existem como um serviço gerenciado em diversos provedores de cloud, e somente nos provedores de cloud, pois elas são uma consequência das arquiteturas cloud native. Neste post vou escrever sobre o serviço NoSQL Tables, as tabelas Serverless da Oracle Cloud (OCI).

Vou explicar como elas funcionam, para que servem, casos de uso, e também vou mostrar um exemplo prático para ficar mais claro.

Principais Características

Apesar de você não enxergar os servidores, eles existem, claro! Mas o serviço expõe as tabelas com uma abordagem serverless. Do ponto de vista físico, as NoSQL Tables são tabelas que implementam o conceito de sharding, isto é, os dados são distribuídos de forma horizontal em servidores diferentes. Além disso, os dados de cada servidor também são replicados para outros servidores. Essa arquitetura distribuída, além de promover alta disponibilidade e durabilidade, garante que o tempo de resposta seja, sempre de forma estável e previsível, de poucos milisegundos.

Como os dados são distribuídos e replicados em servidores diferentes, você pode escolher se as leituras serão do tipo consistente ou eventual, e se vai utilizar ACID para as transações: para entender melhor este ponto (caso não conheça), veja este meu outro post onde abordo o Teorema CAP.

Há bastante flexibilidade para escolher o formato dos dados: existe suporte para dados em colunas (schema-full), tal como conhecemos no modelo relacional (ex: integer, string timestamp e Blob), mas adiciona tipos complexos, como Arrays, Records e Map. E claro, não menos importante, também suporta JSON nativamente (schema flexível).

Outras capacidades que também ajudam bastante em diversos casos é a possibilidade de configurar TTL (time-to-live, onde os dados são removidos automaticamente após o tempo que você especificar), criação de índices secundários (inclusive com JSON), e também a possibilidade de usar SQL ou API (com suporte a Spring Data).

Como não existe servidor, e se quer existe um banco de dados (pelo menos em termos de usabilidade), como será que esses serviços são dimensionados? Bom, aqui está a grande diferença: pelo workload! Isto é, você dimensiona uma NoSQL Table informando qual é a demanda de uso.

São três variáveis requeridas quando se cria uma NoSQL Table na OCI:

  • Quantidade máxima de leituras por segundo;
  • Quantidade máxima de escritas por segundo;
  • Espaço em Gbytes.

Essas variáveis podem ser alteradas de forma online enquanto a tabela existir (elástico, para cima ou para baixo, a qualquer tempo).

Nota: o billing deste serviço considera essas três variáveis: existe um preço por read unit, outro para write unit, e outro para gbytes utilizados. Em geral é um serviço considerado barato, e faz parte do free tier, mas é importante estimar o custo para o seu workload.

No final do dia NoSQL Tables é um serviço gerenciado, então operações como provisionamento de infraestrutura, backup, patching, configurações de alta disponibilidade e segurança, enfim, tudo aquilo que é necessário para deixá-lo no ar e disponível é gerenciado pela própria OCI.

Casos de Uso e Utilidade

Por ser simples, permitir um schema flexível, com baixa latência previsível, e ser completamente gerenciado, as tabelas serveless podem ser bastante úteis nos casos de uso abaixo e quaisquer outros casos similares a estes:

  • Perfis de usuário;
  • Eventos de qualquer tipo (usuários, clicks, etc) e IoT;
  • Carrinho de compras;
  • Catálogo de produtos;
  • Cache de dados para web servers;
  • Armazenamento de metadados e parâmetros;
  • Personalização de anúncios;
  • Detecção de fraude em tempo real.

Claro que existem também os casos onde elas não são úteis. Vou colocar abaixo os principais casos onde você deve fugir das tabelas serverless:

  • Migrar um banco de dados legado existente as-is (a não ser que você queira fazer um grande refactoring);
  • Necessita de um modelo de dados mais complexo, que demanda por um schema com muitas tabelas e relacionamentos. Se não for possível colocar tudo em poucas tabelas, ou se não for compatível com workloads do tipo JSON, não use;
  • No caso onde os limites do serviço gerenciado são insuficientes para o seu caso de uso. À propósito, isto vale para qualquer serviço gerenciado.

Na Prática

As NoSQL Tables podem ser usadas de diversas formas: pelo console gráfico da OCI, pela CLI (Command Line Interface), e também pelas SDKs nas diversas liguagens suportadas, como Java, Node.js, Python, .Net e Go.

Vou utilizar a CLI para criá-las, e para isso você precisa configurar a OCI CLI previamente.

Nota: no arquivo de configuração da OCI CLI você pode colocar alguns parâmetros que utiliza com frequência para evitar de repeti-los toda vez que faz uma chamada CLI. Nos exemplos a seguir eu fiz isso para o parâmetro compartment_id, que é obrigátorio e é responsável por agrupar logicamente os recursos que você cria na OCI).

Com a CLI configurada, veja abaixo como é simples criar uma tabela serverless na OCI. Primeiro vamos especificar os limites da tabela, criando este arquivo table_limits.json:

{
  "maxReadUnits": 2,
  "maxStorageInGBs": 1,
  "maxWriteUnits": 2
}

Com este arquivo como exemplo, nossa tabela permitirá no máximo 2 leituras (maxReadUnits) e 2 escritas (maxWriteUnits) concorrentes, e até 1 GByte de espaço (maxStorageInGBs).

Depois podemos utilizá-lo no comando abaixo:

oci nosql table create --ddl-statement "CREATE TABLE usuarios (id integer, nome string, PRIMARY KEY (SHARD(id)))" --name usuarios --table-limits file://table_limits.json

A tabela chama-se Usuarios, e coloquei apenas duas colunas: id (integer) e nome (string), onde id é a chave primária, e também é a coluna que distribui os dados horizontalmente (shard).

Se eu quisesse utilizar JSON para a coluna nome, bastava substituir string por json na criação da tabela, ou ainda adicionar mais colunas de outros tipos.

Para listar suas tabelas criadas, você pode executar o seguinte comando:

oci nosql table list

Este comando vai retornar um output JSON com a lista de tabelas e suas propriedades.

Apesar de eu não achar apropriado, podemos também manipular e consultar dados pelo OCI CLI. Este comando abaixo insere um registro:

oci nosql query execute --statement "INSERT INTO usuarios (id, nome) VALUES (1,\"Fernando\")"

É possível consultar a tabela com SQL, utilizando este comando:

oci nosql query execute --statement "SELECT * FROM usuarios"

Para remover a tabela, você pode fazer desta forma:

oci nosql table delete --table-name-or-id usuarios

Bom, utilizar a OCI CLI para criar e remover tabelas tudo bem, mas para manipular dados com certeza é melhor utilizar alguma SDK, pois é através dela que você utilizará as tabelas serverless no seu sistema.

Vou utilizar como exemplo a SDK em Python. Veja aqui os requerimentos para instalar os drivers (biblioteca borneo) no seu sistema.

Abaixo vou fazer uma consulta simples pela chave usando API, mas antes preciso importar a biblioteca com os objetos que usaremos, e também fazer a configuração de conexão e autenticação:

from borneo import NoSQLHandle, NoSQLHandleConfig, Regions, Consistency
from borneo.iam import SignatureProvider

handleConfig = NoSQLHandleConfig(Regions.US_ASHBURN_1)
config = handleConfig.set_authorization_provider(SignatureProvider())
config.set_default_compartment('dev')
config.set_consistency(Consistency.EVENTUAL) # ou Consistency.ABSOLUTE
conn = NoSQLHandle(config)

Importei da biblioteca borneo (que utilizamos para manipular as NoSQL Tables) todos os objetos que vou utilizar nos exemplos seguintes.

config é minha configuração de conexão e autenticação, que recebe a região da OCI que usei para criar a tabela (us_ashburn_1) e meus dados de acesso com SignatureProvider(): esse método retorna minhas credenciais que estão configuradas na minha OCI CLI — há várias outras formas de autentição também, veja aqui.

dev é o compartimento onde criei a tabela, e Consistency.EVENTUAL configuro as leituras para serem eventuais (Availability + Partition Tolerance, do Teorema CAP), mas poderia configurar como Consistency.Absolute (Strong Consistency + Partition Tolerance, do Teorema CAP).

O objeto conn (criado a partir de config) é utilizado para as demais operações sobre a tabela.

Bom, vamos então para a consulta simples pela chave usando API:


from borneo import GetRequest

request = GetRequest().set_table_name('usuarios')
request.set_key({'id': 1}) 
result = conn.get(request) 
if result.get_value() is not None:
    print(result)   

Primeiro informei no objeto request qual é o nome da minha tabela, e depois consigo consultá-la informando qual é a chave de busca pelo método set_key. O objeto result recebe o resultado da consulta.

O código abaixo faz a mesma coisa, porém utiliza SQL, e Prepared Statements:

from borneo import QueryRequest, PrepareRequest

sql = 'declare $myKey integer; select * from usuarios where id = $myKey'
request = PrepareRequest().set_statement(sql)
prepStmt = conn.prepare(request).get_prepared_statement()
prepStmt.set_variable('$myKey', 1)
queryRequest = QueryRequest().set_prepared_statement(prepStmt)
while True:
    result = conn.query(queryRequest)
    resultList = result.get_results()
    if queryRequest.is_done():
        break

print(resultList)

PreparedStatements servem para otimizar a execução das instruções SQL em um banco de dados, de forma que faça apenas um parse e múltiplas execuções (ao invés de fazer um parse para cada execução).

Inicialmente criei uma string SQL utilizando $mykey como variável. Depois preparei o statement, e depois fiz a operação de bind, que é colocar o valor na variável $mykey. Por fim a query é executada e o resultado é atribuído ao objeto resultList.

E quanto as transações? Certo! É possível inserir, atualizar e remover dados, normalmente.

Veja abaixo como faço para inserir mais um registro na minha tabela:

from borneo import PutRequest

request = PutRequest().set_table_name('usuarios')
request.set_value({'id': 2, 'nome': 'Paulo'})
result = handle.put(request)
if result.get_version() is not None:
   print('success')

Basta criar um request utilizando o objeto PutRequest, atribuir o valor, e executar com put. Também é possível fazer com SQL, e também é possível fazer várias mudanças com uma simples operação (WriteMultipleRequest).

Neste post eu fiz um overview bem resumido do que é possível fazer com as tabelas serverless. Em outros posts vou mostrar outras capacidades deste serviço. Stay tuned!

Conclusão

As NoSQL Tables são mais robustas do que parecem. Você pode criar tabelas serverless com múltiplos formatos, incluindo JSON e SQL (com ACID e opção de escolha entre consistência forte ou eventual), índices secundários e TTL. É muito fácil de começar, e não há muito o que aprender, pois o serviço é totalmente gerenciado.

Referências

Você pode me seguir neste blog:

Disclaimer: As opiniões expressas neste blog são minhas, e não são necessariamente as mesmas opiniões do meu empregador.


Change Data Capture

CDC é um padrão de integração de dados que vem se tornando cada mais vez mais comum hoje em dia. CDC significa, do inglês, Change Data Capture: uma tradução direta para o português seria Captura de Dados Alterados.

Em resumo, uma ferramenta que suporta CDC é capaz de capturar os dados de uma origem no momento em que estes são criados ou alterados, e os transportam para um destino, com baixa latência.

Há dois grandes benefícios neste tipo de integração:

  • O primeiro é a velocidade com que os dados são propagados da origem para o destino (near real-time, baixa latência);
  • O segundo é a heterogeneidade, uma vez que as ferramentas de CDC geralmente permitem capturar em um formato (plataforma, tecnologia, máquina, etc) e entregar em outro completamente diferente.

Atualmente CDC é usado em arquiteturas de microsserviços (em especial para capturar dados de sistemas legados, e também para endereçar alguns padrões como CQRS e Event Sourcing), e também, majoritariamente, em arquiteturas de integração de dados.

Sabemos que integração de dados é um processo que utilizamos para mover dados de um lugar para outro. Normalmente os dados são movidos de sistemas transacionais para repositórios centrais de dados, como os Data Warehouses, Data Lakes ou Lakehouses. Esses repositórios centrais oferecem melhor capacidade para a análise de dados através da utilização de técnicas de consumo, como mineração de dados, machine learning, e data visualization.

Bom, mas nem tudo são flores. Geralmente as empresas possuem muitos sistemas transacionais, e cada um com suas próprias complexidadades. E por isso, nem todas as integrações são realizadas da mesma forma. Cada integração é criada de acordo com as características de cada sistema, das capacidades deles de permitir que os dados sejam capturados. Algumas tabelas de um banco de dados, por exemplo, podem não oferecer colunas de timestamp ou algum identificador para capturarmos as mudanças de forma incremental, ou o sistema não possui APIs que expõem seus dados, ou eventualmente eles têm tudo, mas você não pode tocar neles porque as integrações podem impactar no desempenho do sistema. CDC resolve esses problemas.


Você pode ver este conteúdo em video pelo meu canal Universo dos Dados, no YouTube:


Velocidade e Desempenho

Nós temos a disposição geralmente três formas de lidar com a velocidade (latência de entrega dos dados) em uma integração: snapshot, batches incrementais e CDC – e cada uma dessas formas produz um tipo de impacto de desempenho na origem onde os dados são capturados:

  • O primeiro deles, snapshot, é o mais simples, e o pior (mais lento) deles. Snapshot é como uma foto em um ponto no tempo. Você pode buscar os dados, por exemplo, todo dia à meia-noite. O método de snapshot sempre vai trazer tudo, e por isso, quanto maior a quantidade de dados, mais tempo para trazê-los, mais tempo de impacto na origem (concorrência de leitura), e maior consumo de espaço no destino.
  • Batches incrementais são um pouco melhores do que o snapshot. Com esta abordagem você vai sempre trazer as diferenças desde o último batch incremental. O período de execução do batch incremental pode ser 1 vez por dia, 1 vez por hora, ou 1 vez a cada 15 minutos, por exemplo. Ou seja, você não precisa trazer todos os dados todas vezes. No entanto, um trade-off desta abordagem é que você acaba perdendo os deletes que ocorrem, pois ele retorna uma foto da diferença, desta forma dados removidos nunca aparecem nesses snapshots incrementais. Além disso, é preciso endereçar bem como as mudanças são capturadas, pois é necessário fazer consultas com filtros utilizando algum timestamp ou identificador, em especial de forma indexada, e isso pode eventualmente impactar no desempenho na origem.
  • Por fim, CDC é a terceira abordagem, e ao contrário das anteriores, é a maneira que oferece a melhor velocidade, e o menor impacto da origem.

Geralmente as ferramentas que implementam CDC suportam os seguintes tipos de origens (source) e destinos (targets): bancos de dados, filesystems, APIs e brokers de eventos (streams).

O mais comum deles são os bancos de dados: as mudanças são capturadas do log de transação deles (que praticamente todos os bancos de dados têm), isto é, cada vez que há uma alteração em um banco de dados, essa alteração é escrita no log, e imediatamente a ferramenta de CDC captura deste log e propaga os dados para o destino. Também dizemos que o mecanismo de captura dos dados em bancos de dados é chamado de push (empurrar), pois literalmente as mudanças de estado nos dados são empurradas nos arquivos de log das transações.

Desta forma, dizemos que o padrão de integração CDC possui uma velocidade near real-time.

Infelizmente, todos os outros tipos de origens suportados por ferramentas de CDC não são tão amigáveis como são para os bancos de dados, pois estes outros não possuem arquivos de log de mudanças/transações – dizemos que eles são do tipo pull (puxar), isto é, tem que ir lá e pegar/puxar de tempos em tempos. Arquivos em filesystems precisam ser copiados a cada busca; APIs e broker de eventos (streams) tem um perfil mais micro-batch incremental do que propriamente CDC.

De qualquer forma, com a popularização do CDC, muitas ferramentas estão cada vez mais oferecendo algum tipo de melhoria e logs para serem suportados pelo padrão de integração CDC do tipo push.

Heterogeneidade

A heterogeneidade é um dos grandes benefícios do CDC. Eles são capazes de capturar e entregar dados para tecnologias diferentes, sem precisar de qualquer área intermediária (stage) para conversão.

Então você pode capturar dados de um banco de dados relacional, e entregá-los a um filesystem em formato JSON. Capturar dados de uma fila de mensagens, e entregá-los a um banco de dados. Capturar dados do Hive e entregar para um Kafka, e assim por diante.

Claro que eventualmente conectores são necessários nas pontas para fazer algum tipo de conversão, mas a capacidade de lidar com origens e destinos de forma heterogênea, entregando os dados com extrema baixa latência, é um diferencial deste padrão de integração.

Quando Usar

Abaixo eu listo alguns dos principais casos de uso onde CDC tem bastante valor:

  • Migrações de plataformas e sistemas: migrar dados de um sistema legado, como um mainframe, para uma plataforma mais moderna (JSON, REST, Microsserviços, Cloud, etc). Migrar plataforma com CDC permite desacoplar o antigo do novo. Em outras palavras, é possível conviver com ambas as plataformas ao mesmo tempo sem precisar de uma migração big bang. Mudanças realizadas no sistema novo são propagadas para o sistema antigo, e vice-versa.
  • Carga em tempo real para Data Warehouses: com CDC você captura as mudanças de dados dos sistemas transacionais e já as entrega para o DW, em near real-time. É a letra E do ETL feita de uma forma não-batch. O CDC permite que o DW seja atualizado praticamente a todo tempo, e por isso essa capacidade habilita a disponibilização dos dados para suporte à decisão em tempo quase near real-time.
  • Atualizações de caches e índices: você pode manter um cache atualizado no Redis (ou qualquer outro mecanismo de cache ou data grid), e atualizar um Search Index no ElasticSearch, por exemplo, na medida que os dados são alterados em uma fonte de dados transacional.
  • Atualização de tabelas read-only em Microsserviços: nas arquiteturas de microsserviços, onde geralmente cada serviço tem seu próprio banco de dados, o correto seria um serviço chamar uma API de outro para obter seus dados, mas nem sempre isso é possível, e pode gerar problemas de desempenho. Manter uma réplica read only em bancos de dados de outros serviços, sincronizados por meio de CDC, pode melhorar bastante a performance e ainda assim manter as características de disponibilidade e autonomia dos microsserviços.
  • Sincronizar dados em cloud híbrida e multi-cloud: CDC permite sincronizar dados em serviços de dados que existem em clouds diferentes, ou mesmo entre serviços cloud e recursos on-premises. Um exemplo bastante comum é criar um ambiente de disaster recovery em cloud, a partir dos dados que existem on-premises.

Principais Tecnologias

No momento que escrevo este post existem algumas ferramentas de CDC bastante comuns no mercado.

Na esfera comercial, o principal deles, o mais robusto e sofisticado é o GoldenGate, da Oracle – este também disponibilizado como um serviço gerenciado na Oracle Cloud.

Na esfera open-source, o principal deles é o Debezium, comumente usado em vários casos de uso com Apache Kafka.

Há outras ferramentas no mercado que suportam o padrão de integração CDC, não como uma ferramenta de CDC em si, mas como uma capacidade complementar. Uma delas é o Airbyte por exemplo, que é como tantas outras uma ferramenta de Integração de Dados que suporta, também, CDC.

Conclusões

Considere o padrão de integração CDC sempre que precisar que os dados sejam capturados em near real-time.

Você pode me seguir neste blog:


Transformação Digital, sem Buzzwords

1. Seus ambientes não-produtivos são efêmeros, e você os recria do zero todo dia. Quando precisa. Quanto precisa. Por código. Imediato.

2. Você não move seus dados nunca. Todos eles são necessariamente virtualizados e convergidos, acessados sempre com a mesma interface. Não importa o formato, o tamanho, e a velocidade.

3. Zero ETL. Real-time analytics, sempre.

4. Criptografia por padrão em qualquer coisa: in-flight, in-memory, e at rest.

5. Mudanças em produção ocorrem a todo tempo, sem parada.

6. Sua capacidade escala do zero ao máximo, por segundo. Sem interrupção. O máximo é ilimitado.

7. Data Lineage de tudo que você tem com no máximo dois cliques de esforço.

Arquitetura de Dados Convergente

Nos sistemas e nas aplicações que são criadas hoje em dia, os maiores problemas com Dados em geral são a quantidade de informações geradas, os múltiplos formatos que existem, e a velocidade em que eles se modificam.

Nesse post eu vou comentar um pouco sobre esse tema, e duas maneiras de lidar com isso.


Quando a gente desenvolve um sistema hoje em dia, normalmente nós usamos várias tecnologias que tem a ver com Dados, mas cada uma tem uma finalidade específica, por exemplo:

  • Quando você armazena dados com objetivo de alguém ler depois, você usa um banco de dados;
  • Se você quer lembrar o resultado de uma busca complexa, para acelerar leituras, você usa um mecanismo de cache;
  • Para permitir que os usuários façam buscas por palavras-chave específicas, textos-livres, ou consultas ad-hoc, você usa índices;
  • Para trocar mensagens entre processos, ou entre sistemas, você usa um broker de eventos;
  • E para periodicamente pegar uma grande quantidade de dados acumulados e processar, você faz um processamento batch.

Nós tipicamente pensamos que bancos de dados, caches, filas, são todas ferramentas de diferentes categorias. Por exemplo, um banco de dados e um broker de eventos têm uma certa similaridade, porque ambos armazenam dados por algum tempo. Mas eles são diferentes. Eles têm padrões de acesso diferentes, e características de performance e implementação diferentes.

Além disso, pra cada uma dessas categorias existem dezenas de opções com base em diferentes capacidades. Existem vários tipos de bancos de dados, várias formas e métodos diferentes de fazer cache, e vários tipos de índices para buscar informações. E isso ocorre basicamente porque os sistemas TÊM necessariamente requerimentos diferentes.

E quando a gente desenvolve um sistema, especialmente na camada de persistência, na camada de dados, a gente precisa saber quais ferramentas e quais métodos são os mais apropriados para cada caso, e é difícil combinar ferramentas diferentes quando você precisa fazer uma coisa que uma só ferramenta sozinha não consegue fazer, certo?

A verdade é que no final do dia você, além de você ser um desenvolvedor de software, acaba sendo também um arquiteto de dados.

Então basicamente você tem dois caminhos.

Você tem o caminho onde você vai utilizar várias tecnologias. Você vai aprender todas essas ferramentas. Você vai integrar todas elas. Vai suportar todas elas, e vai garantir que elas funcionem em conjunto.

E você tem um outro caminho. O caminho que eu chamo de “um sistema de dados convergente”. E nesse sistema você tem uma plataforma, e nessa plataforma você tem quase todas as ferramentas, quase todos os tipos de dados…  seja schema on write, seja schema on read, seja um cache, uma fila, não importa o formato dos dados… todos eles, dentro do mesmo sistema de dados. E quando esse sistema não suporta alguma coisa, ele virtualiza o acesso e expõe a mesma interface de acesso pra você consumir aquela informação da forma mais transparente possível.

Veja meu video sobre esse tema:

Teorema CAP

Atualmente existem mais de 300 bancos de dados no mercado, e você precisa escolher aquele que melhor atende sua necessidade.

Os projetistas desses bancos de dados fizeram uma importante decisão sobre como eles devem funcionar, e por isso é importante entender o mecanismo de funcionamento deles para você não errar na escolha.

Entendendo o Teorema CAP você terá uma visão mais clara sobre os tipos de bancos de dados disponíveis, e quais estão aderentes ao seu caso de uso.

Nesse post eu passo uma visão básica sobre o significado do Teorema CAP. Você também pode assistir meu video no YouTube:

Consistency, Availability and Partition Tolerance (CAP)

CAP significa Consistência [do inglês, Consistency], Disponibilidade [do inglês, Availability], e Tolerância a Partição [do inglês, Partition Tolerance].

Partition Tolerance significa que o banco de dados é distribuído em diversos servidores independentes [shared-nothing], em pares de leitura/escrita. Isso é importante, em especial, para aumentar a performance, já que quanto mais você espalha os dados em servidores diferentes, maior é o throughput [megabytes por segundo] de leitura e escrita. Partition Tolerance também significa que se um servidor estiver fora, todo o sistema de banco de dados continua funcionando, com exceção desta parte específica que apresentou a falha.

Você pode perguntar por que P é chamado exatamente de “Tolerância a Partição”. O entendimento de partição aqui é “falha”, e mais especificamente falha de rede, que impede que um servidor se comunique com outro servidor. Então quando um banco de dados tolera partição, significa que se houver falha na comunicação entre um servidor que escreve e sua réplica, o banco de dados não para de funcionar, pois poderá haver outros pares de leitura e escrita no cluster que não estão no caminho da rede que falhou.

Consistency implica em consistência de leitura, e significa que depois que uma informação é escrita, todas as leituras subsequentes vão enxergá-la, no mesmo servidor, ou em suas réplicas. Neste caso também dizemos que há consistência forte, porque todos enxergam a mesma versão da informação.

Availability significa sempre disponível para leitura ou escrita, e todas as requisições sempre retornam alguma informação, mesmo que ela esteja inconsistente – mas nunca retorna um erro. Isto é, pode ter havido alguma falha na rede e os pares de servidores leitura/escrita não se enxergam, mas ambos permanecem abertos e recebem operações, estando em estado eventualmente consistente. Por causa disso, também dizemos que há uma consistência eventual, porque não é certeza que todos enxergam a mesma versão da informação.

A Prova

O Teorema CAP foi provado, e ele diz que das três propriedades desejadas, apenas duas podem existir ao mesmo tempo para um par leitura/escrita.

Isso nos leva a três possibilidades que definem o mecanismo de funcionamento de um banco de dados.

CA – Consistency e Availability: são bancos de dados cujos dados não estão distribuídos, mas oferecem consistência, e também disponibilidade [porém, em geral através de failover, isto é, com downtime]. O Oracle RAC é o único atualmente que oferece tanto C como A “sem downtime” em um sistema de banco de dados que não tolera partição [shared-disk].

CP – Consistency e Partition Tolerance: são bancos de dados que oferecem consistência, isto é, os pares leitura/escrita sempre estão com os mesmo dados [consistentes], mas caso haja alguma falha na rede, ambos tornam-se indisponíveis: sacrificam a disponibilidade em favor da consistência.

AP – Availability e Partition Tolerance: são bancos de dados que oferecem disponibilidade, e sacrificam a consistência. Isto é, os pares leitura/escrita permanecem operantes quando há falha na rede. Notadamente, quando isto ocorre eles ficam em estado inconsistente, pois não há uma comunicação entre eles. A inconsistência pode ser resolvida quando a comunicação é reestabelecida.

Minha Opinião… e a Sua?

Primeiramente, a letra A do CAP implica em uma premissa que eu particularmente não concordo.

A significa sempre disponível para leitura e escrita, mas sabemos que disponibilidade é medida em 9s depois da vírgula, e nada é realmente 100%. Quanto mais rotas redundantes de rede, e mais réplicas dos dados, mais 9s de disponibilidade teremos depois da vírgula, mas nunca 100% como o teorema preconiza.

Um outro ponto é que potencialmente, salvo implementação específica de cada banco de dados, os sistemas AP sempre apresentam um estado de consistência eventual, mesmo sem falha/partição na rede. Isso ocorre porque a propagação dos dados entre o servidor de que escreve e o servidor de réplica é assíncrona. Desta forma uma leitura pode ocorrer antes que uma informação que acabara de ser escrita fosse transmitida pela rede até o servidor em questão. Considerando que atualmente falhas de rede são raras, e as redes de comunicação locais são rápidas, sistemas AP oferecem uma desvantagem permanente, e na minha visão, desnecessária, tornando-os nicho para necessidades muito específicas. Há uma tendência de mercado dos bancos de dados NoSQL migrarem para o paradigma CP.

Então antes de usar um banco de dados, em especial nesses tempos de micro-serviços e persistência poliglota, atente-se ao mecanismo que é implementado no banco de dados.

Requisitos de elasticidade e alto throughput para leitura e escrita exigem que o banco de dados seja tolerante a partições. E neste caso, se houver a necessidade de consistência forte, o banco de dados deverá ser CP, do contrário, escolha AP.

Document Stores sempre serão um Nicho?

Document Stores são uma espécie de subcelebridade dos bancos de dados. Eles apareceram de repente, têm supostamente alguma relevância, mas todos os outros bancos de dados Não-Document Store já fazem o que eles fazem: Oracle, MySQL, SQL Server, PostgreSQL, DB2, ElasticSearch, Redis, MariaDB…

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a DocumentosXX

Eles são amados principalmente por front-end devs, porque Documentos permitem a mais rápida prototipação. É inegável.

O grau de flexibilidade de schema é tão alto que causa piripaques nos administradores de dados mais tradicionais. Alguns até proíbem.

Mas só depois que você usa o Modelo de Documentos você percebe o quão rígido é o Modelo Relacional. É incrível como um é o oposto do outro. Há de verdade um perigo real e iminente de você se lambuzar e querer colocar Document Stores em todos os lugares.

Bom, devo lembrar que este artigo não é exatamente sobre Documentos JSON ou XML – é sobre Document Stores: bancos de dados que só armazenam Documentos. Não confunda.

Fiz um video em meu canal no YouTube, com conteúdo para database developers, explicando algumas diferenças de arquitetura entre esses dois modelos:

Variedade, Velocidade e Volume.

Document stores resolvem principalmente a Variedade nos 3 Vs que definem Big Data, porque eles são schema-less, e por isso suportam uma grande variedade de formatos de dados.

Essa variedade é também o que lhes impede de serem completamente aderentes ao ACID, porque a letra C significa Consistência de Escrita [Schema], e Document Stores são, por definição, sem schema. Mas isso não lhes impede de suportar transações [Atomicidade, Isolamento e Durabilidade].

Há os que consideram 5 Vs, tendo Veracidade e Valor como os dois Vs adicionais. Neste post vou considerar apenas aspectos técnicos, por isso são 3 Vs.

A Velocidade nesse tipo de persistência é entregue através de particionamento dos dados: os documentos são distribuídos em partes [servidores] diferentes pela chave que identifica um documento.

Atualmente apenas tecnologias que escalam horizontalmente, como as que particionam os dados, conseguem suportar os mais altos volumes de leitura e escrita. Ainda não existe um teorema que prove isso, mas meu feeling diz que nunca vai existir outra arquitetura que escale mais, pelo menos até a popularização da computação quântica.

É questionável um Document Store ter grande desempenho quando não se pesquisa pela chave, mesmo que o banco de dados suporte índices secundários.

Pesquisas por chave são resolvidas com Hash, e índices secundários em geral são resolvidos com Árvores Binárias. Em uma análise assintótica, Hash é O(1), e Árvore Binária é O(Log n), e O(Log n) é mais lento que O(1). E uma busca por um índice secundário particionado é na melhor das hipóteses O(Log N) + O(1).

A maioria dos bancos de dados Não-Document Store que citei no início, que suporta Documentos, também suporta particionamento de dados.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a DocumentosXX
Suporte a ParticionamentoXX

E como Document Stores se diferenciam em relação ao Volume?

Qual é o tipo de persistência que você acredita que tem maiores dificuldades com grandes volumes? Relacional? Bom, já pensou que um banco relacional é normalizado justamente para reduzir grandes volumes?

Vou explicar “normalizar” explicando “desnormalizar“.

Desnormalizar tem um significado que é agregar dados, um benefício que é agregar dados, e um problema que é agregar dados.

Document Stores são do tipo do segundo ‘agregar dados’, o do benefício. Para eles, desnormalizar significa aumentar o desempenho das consultas no banco de dados, porque a final de contas, dados agregados evitam joins, e joins são lentos, segundo eles.

Bancos Relacionais são do tipo do terceiro ‘agregar dados’, o do problema. Para eles, desnormalizar significa aumentar problemas de integridade nas escritas no banco de dados, pois como os dados não-chave não dependeriam funcionalmente só da chave [terceira forma normal], poderia haver valores duplicados e inconsistentes.

Pense que um banco de dados relacional com o tamanho de 10 TBytes em 3NF, se desnormalizado, atingiria fácil os 100 TBytes.

Isso ocorre porque esses bancos relacionais normalizam os dados, e eles fazem isso substituindo as repetições por um código [chaves estrangeiras]. A maneira mais vulgar de explicar isso é dizer que eles ‘desduplicam’ os dados.

Desduplicar na área de algoritmos de compressão significa substituir um valor que se repete por um símbolo de tamanho pequeno [um tipo de compressão sem comprimir]. Então como os bancos relacionais fazem isso como parte da sua natureza, posso dizer que um banco de dados relacional é um Big Data desduplicado.

Pare por um momento e pense. É isso mesmo. Um banco relacional suporta volumes colossais e você não sacou. Eles só estão desduplicados!

E o que isso tem a ver com os Document Stores? Basicamente isso indica que ambos têm a mesma capacidade de armazenar grandes volumes, porém o fazem de forma diferente. Isso também indica que qualquer coisa, menos o Excel (risos), consegue armazenar grandes volumes de dados.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a Documentos
(Variedade)
XX
Suporte a Particionamento
(Velocidade)
XX
Suporte a Grandes Volumes
(Volume)
XX

Um desnormaliza para ter performance de consulta, e o outro normaliza para ter integridade. E o benefício de um, é o trade-off do outro.

O fato dos Não-Document Stores que citei no início deste post também suportarem Documentos, indica que eles são híbridos no sentido de que o desenvolvedor poderá escolher em qual parte do trade-off ele vai querer estar, em partes independentes do código, e em partes diferentes das informações que estiver armazenando.

Em outras palavras, ele poderá escolher qualquer combinação do C do ACID [consistência de escrita], com o C do CAP [consistência de leitura], particionado ou não.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a Documentos
(Variedade)
XX
Suporte a Particionamento
(Velocidade)
XX
Suporte a Grandes Volumes
(Volume)
XX
undefinedX

Status Quo

O grande problema dos Document Stores para serem os Panteões de Todas as Persistências e de Tudo o Mais é o fato de eles serem schema-less.

O benefício da Variedade afetou sua relevância.

A história mostra que antes e após o Modelo Relacional, as formas de persistência de dados têm sido schema-less, onde só a aplicação entende como os dados estão organizados. Todas elas falharam como status quo, sumiram, ou são apenas nichos tornando-se subcelebridades [importantes pontualmente].

Qualquer modelo de dados onde você tem que conhecer a aplicação para entender os dados vai falhar no caminho para o mainstream, e será sempre um nicho.

Winter is Coming: Como Sobreviver em um Mundo com Persistência Poliglota?

Eu costumo dizer que bancos de dados são como queijos e vinhos, pois eles melhoram enquanto envelhecem.


No início existia apenas uma arquitetura de dados one-size-fits-all: a solução para todo e qualquer problema.

O que vemos hoje é uma verdadeira miscelânea, centenas de combinações e permutações possíveis de formatos diferentes, tecnologias, e integrações de todas as latências. Você já não sabe se tem que ser streaming ou batch, síncrono ou assíncrono, schema-less ou schema-full, SQL ou NoSQL, se a consistência mais adequada é eventual ou forte, o relacionamento é entre nós de um grafo ou entre chaves de tabelas, se precisa ser scale-up ou scale-out, e se é necessário um Data Lake ou um Data Warehouse.

Se identificou? Continue lendo.

Este artigo é sobre uma das maiores tendências a respeito de como organizar toda essa bagunça: persistência poliglota multi-modelo. Esta coisa de nome complicado, longo, e difícil de falar [eu entendo], é a fusão de uma dezena de tecnologias já testadas em batalha, e que provavelmente será, eu estimo, o futuro da arquitetura de dados. Será como antes só que o oposto: de one-size-fits-all, para all-fits-one-size.

O Início de Tudo: a Jornada do Herói

Nos primeiros sistemas de banco de dados, se é que podemos chamá-los de “sistemas” diante da primitividade que pairava no início, eu me lembro de ter lido que para conseguir uma informação você tinha que [1] saber qual era o arquivo onde ela estava, e [2] qual era o registro nesse arquivo, e [3] então quais eram as suas posições de início e término no registro. Essas posições eram sempre fixas, a propósito. Nessa estrutura, que se chamava ISAM, basicamente, você tinha que ter um mapa que lhe explicava como os dados que eram gravados, para poder buscá-los depois.

Não vou detalhar com mais profundidade sobre a estrutura de dados ISAM, pois assim como eu, hoje, você não vai querer saber!

Um senhor chamado Edgar Codd, pesquisador da IBM [claro, todos os pesquisadores da época eram da IBM] disse que as pessoas não deveriam ter que se preocupar em como os dados estavam organizados para entendê-los. Isso se tornaria um caos num futuro reservado para os Megabytes [hoje, Terabytes].

Codd então criou o Modelo de Dados Relacional. Era um mecanismo engenhoso, porém simples, que desacoplava os dados da organização física deles. Uma simples frase como “selecione os dados do cliente, na tabela de clientes” foi capaz de substituir milhares de linhas de código, e acelerar em ordens de magnitude o tempo para buscar as informações.

Eis que surge Larry Ellison, com mais alguns amigos. Enquanto uns apenas viam o que seus olhos enxergavam, Larry enxergava o que seus olhos viam: nada inventado antes era mais completo e eficiente do que o Modelo de Dados Relacional. Naqueles anos 70, era o futuro. E foi mesmo. E esse futuro ainda não acabou.

Surgiu o Oracle Database como o primeiro sistema gerenciador de banco de dados baseado no Modelo de Dados Relacional. Uma locomotiva de inovação a toda velocidade passou entre os anos 80, 90 e 2000. Foi incrível a quantidade de recursos criados, e até hoje alguns desses recursos ainda são exclusivos.

Muitas outras tecnologias surgiram: ora morriam pelo caminho, ora apenas sobreviviam com uma vida de ócio e defasagem diante da constante evolução do Oracle.

CAP, e os 300

O ano 2000 entra, a internet começa, e o suposto declínio do Modelo Relacional é ensaiado. A internet é um colosso perto dos sistemas empresariais. Por um minuto você tem 10 usuários, e no momento seguinte, instantaneamente, você tem 1 milhão. Projetado para funcionar em servidores que escalam verticalmente, os Bancos de Dados Relacionais realmente não funcionam nesse novo paradigma.

Eric Brewer formula uma conjectura que alguns anos mais tarde se tornara um teorema, o teorema CAP. Esse teorema basicamente forma a base para a criação de estruturas de dados que seriam mais adequados para a internet do que o Banco de Dados Relacional. Em essência, são estruturas de dados que escalam horizontalmente.

O teorema CAP é realmente uma ideia estranha, mas foi provada matematicamente. A letra A do CAP significa “sempre disponível para leitura e escrita“, o que implica em uma disponibilidade de 100%. Ora, nada nesse mundo tem 100% de disponibilidade. Nem o mundo tem 100% de disponibilidade. Bom, essa é uma premissa que não concordo, mas em função dela o CAP é um teorema, e com teoremas não se discute.

A ideia colou, mais de 300 bancos de dados Não-Relacionais foram criados, e a internet explode. Todo hype é então construído baseado em estruturas Não-Relacionais.

Dá-se então início a vários formatos de dados diferentes.

Timidamente, primeiro foram as estruturas Objeto-Relacionais. Essas morreram rápido. Prometeram de mais, fizeram de menos. Cometeram o mesmo pecado do ISAM: era necessário compreender os Objetos para entender os dados. Retrocesso. Curiosamente, os Bancos Relacionais absorveram o modelo lógico Objeto-Relacional, junto do Relacional, para quem quisesse usar, inclusive, no mesmo comando SQL.

É o início dos Bancos de Dados Multi-Modelo. Relacional com Objeto.

Depois surgiram as estruturas de armazenamento XML. Menos complexas do que os Objeto-Relacionais, pois elas tinham schema, e XPath para percorrer os dados. XML ficou bons anos em evidência. Mas também falhou. E novamente, os bancos relacionais absorveram o modelo lógico XML, junto do Relacional, para quem quisesse usar, no mesmo comando SQL.

Relacional, Objeto e Documento XML.

Em paralelo surgiu a necessidade de bancos de dados com capacidades de geo-processamento, os Bancos de Dados Espaciais. Nenhum fez sucesso como um produto a parte, porém, essa camada de persistência se juntou aos Relacionais, como uma outra camada lógica, e ainda é usada hoje, desta forma, como parte dos bancos relacionais.

Relacional, Objeto, Documento XML e Geo-Espacial.

Mais próximo dos tempos atuais, despontou como uma forma de armazenamento bastante comum o formato JSON, com grande flexibilidade e agilidade: os desenvolvedores adoram. No entanto, apesar de alguns bancos de dados puramente baseados nesse formato terem se tornado relevantes, os bancos de dados relacionais que outrora haviam absorvido vários outros modelos lógicos, também absorveram o formato JSON.

A partir de agora é muito mais fácil um Banco de Dados Multi-Modelo acrescentar uma nova estrutura lógica, do que um banco de dados especializado implementar as capacidades mais básicas, como Consistência Forte, ou mesmo SQL, por exemplo.

Relacional, Objeto, Documento XML e JSON, e Geo-Espacial.

Outras estruturas de dados com usos mais específicos também fizeram parte do big bang causado pelo teorema CAP: Grafos, Chave/Valor, Colunares e Motores de Busca. Adivinha? Os bancos de dados relacionais adicionaram mais camadas lógicas para suportar essas estruturas.

Destaco que de todos esses, o Modelo de Grafos é o mais diferente. É o mais engenhoso. E provavelmente é o que eu gosto mais. É também assunto para outro artigo.

Enquanto que do ponto de vista de estrutura de dados os Bancos Relacionais fizeram grandes intervenções para suportar tudo o que o mercado demandava, por outro lado, num passe metamorfósico, esses bancos também passaram a escalar horizontalmente, para os lados e não somente para cima, como todos os outros 300 bancos de dados Não-Relacionais já faziam. Dá-se o nome de NewSQL, uma alusão aos bancos Não-Relacionais [NoSQL] com a consistência forte dos Relacionais e o suporte completo ao SQL [basicamente, SQL é um sonho de toda a vida de um NoSQL, Apache Hive que o diga].

Esses super Bancos de Dados Relacionais, que suportam vários modelos de dados, e escalam tanto para cima como para os lados, são chamados finalmente de Bancos de Dados Poliglotas Multi-Modelo. Eles possuem toda a herança de inovação dos fundamentos essenciais para gestão de dados, o suporte aos principais formatos de dados dos últimos 40 anos, e a capacidade de escala que é um dos requerimentos para aplicações modernas de internet e Big Data.

Um outro fator relevante se soma a esses super bancos de dados: cloud automation. Poucos cliques ou chamadas de API permitem que toda essa estrutura seja criada, e mantida automaticamente.

De Codd ao Uber

As arquiteturas de dados são como um trem em alta velocidade, com curvas e hypes pelo caminho. Sempre surgem aquelas tecnologias que só elas fazem aquilo, que são mais típicas de “aplicações de ponta”, mas depois elas acabam convergindo para o mainstream porque as aplicações de ponta hoje são as aplicações mainstream de amanhã.

É por isso que tenho uma preferência particular por Bancos de Dados Multi-Modelo. Você absorve todas as inovações que surgem ao longo dos anos, e herda todas as capacidades fundamentais que as aplicações de ponta ainda não têm.

Meu Uber chegou! A propósito, ele me localizou com geo-processamento, o pagamento da minha corrida vai persistir com a integridade de um modelo relacional, e todas as outras coisas devem estar agregadas em documentos JSON em uma estrutura scale-out, pois afinal é mobile e precisa ser escalável.

Como poderia dizer Codd: as pessoas não deveriam ter que se preocupar em como os dados estão organizados para entendê-los. Isso pode se tornar um caos num futuro reservado para os Pegabytes…