Favorecendo a imutabilidade no design do banco de dados

26

Um dos itens no Effective Java de Joshua Bloch é a noção de que as classes devem permitir a mutação de instâncias o mínimo possível e, de preferência, de maneira alguma.

Muitas vezes, os dados de um objeto são mantidos em um banco de dados de alguma forma. Isso me levou a pensar na idéia de imutabilidade em um banco de dados, especialmente nas tabelas que representam uma única entidade em um sistema maior.

Algo com o qual tenho experimentado recentemente é a ideia de tentar minimizar as atualizações que faço nas linhas da tabela que representam esses objetos e tentar executar inserções o máximo possível.

Um exemplo concreto de algo que eu estava experimentando recentemente. Se eu souber que posso anexar um registro com dados adicionais posteriormente, criarei outra tabela para representá-lo, como as duas definições de tabela a seguir:

create table myObj (id integer, ...other_data... not null);
create table myObjSuppliment (id integer, myObjId integer, ...more_data... not null);

Espero que seja óbvio que esses nomes não sejam verbais, mas apenas para demonstrar a ideia.

Essa é uma abordagem razoável para a modelagem de persistência de dados? Vale a pena tentar limitar as atualizações executadas em uma tabela, especialmente para preencher nulos para dados que podem não existir quando o registro é criado originalmente? Há momentos em que uma abordagem como essa pode causar dor intensa mais tarde?

Ed Carrel
fonte
7
Sinto que esta é uma solução sem problemas ... Você deve estar atualizando, em vez de criar adaptações elaboradas para evitar atualizações.
Fosco
Eu acho que era mais uma questão de ter uma idéia intuitiva de uma solução em mente, e querer executá-la por tantas pessoas quanto possível, e no processo percebendo que essa pode não ser a melhor solução para o problema que tenho. Posso abrir uma pergunta diferente com o problema, desde que não a encontre em outro lugar.
Ed Carrel
1
Pode haver boas razões para evitar atualizações nos bancos de dados. No entanto, quando essas razões aparecem, é mais um problema de otimização e, como tal, não deve ser feito sem a prova de que há um problema.
dietbuddha
6
Eu acho que há um forte argumento para imutabilidade nos bancos de dados. Resolve muitos problemas. Eu acho que os comentários negativos não vieram de pessoas de mente aberta. As atualizações no local são a causa de muitos problemas. Eu diria que temos tudo ao contrário. As atualizações no local são a solução legada para um problema que não existe mais. O armazenamento é barato. Por que fazê-lo? Quantos sistemas de banco de dados possuem logs de auditoria, sistemas de controle de versão, necessidade de replicação distribuída que, como todos sabemos, requer a capacidade de suportar latência para escala. A imutabilidade resolve tudo isso.
quer
@Fosco Alguns sistemas são absolutamente necessários para nunca excluir dados (incluindo o uso UPDATE). Como os registros médicos do médico.
precisa saber é o seguinte

Respostas:

25

O principal objetivo da imutabilidade é garantir que não haja instantes no tempo em que os dados na memória estejam em um estado inválido. (A outra é porque as notações matemáticas são principalmente estáticas e, portanto, as coisas imutáveis ​​são mais fáceis de conceituar e modelar matematicamente.) Na memória, se outro encadeamento tentar ler ou gravar dados enquanto estiver sendo trabalhado, ele poderá ficar corrompido ou ele próprio pode estar em um estado corrupto. Se você tiver várias operações de atribuição nos campos de um objeto, em um aplicativo multithread, outro encadeamento poderá tentar trabalhar com ele em algum momento intermediário - o que pode ser ruim.

A imutabilidade corrige isso escrevendo primeiro todas as alterações em um novo local na memória e depois executando a atribuição final como uma etapa rápida de reescrever o ponteiro para o objeto para apontar para o novo objeto - que em todas as CPUs é um átomo Operação.

Os bancos de dados fazem a mesma coisa usando transações atômicas : quando você inicia uma transação, ele grava todas as novas atualizações em um novo local no disco. Quando você termina a transação, ele muda o ponteiro do disco para onde estão as novas atualizações - o que ocorre em um breve instante durante o qual outros processos não podem tocá-lo.

Isso também é exatamente o mesmo que sua ideia de criar novas tabelas, exceto mais automática e mais flexível.

Portanto, para responder sua pergunta, sim, a imutabilidade é boa nos bancos de dados, mas não, você não precisa criar tabelas separadas apenas para esse fim; você pode apenas usar os comandos de transação atômica disponíveis para o seu sistema de banco de dados.

Rei Miyasaka
fonte
Obrigado pela resposta. Essa perspectiva era exatamente o que eu precisava para perceber que minha intuição tentava confundir algumas idéias diferentes em um único padrão.
Ed Carrel
8
Há um pouco mais do que atmoicidade. O argumento que vejo mais frequentemente em favor da imutabilidade em um contexto de POO é que objetos imutáveis ​​exigem apenas que você valide seu estado uma vez, no construtor. Se eles são mutáveis, todos os métodos que podem alterar seu estado também são necessários para verificar se o estado resultante ainda é válido, o que pode adicionar complexidade significativa à classe. Esse argumento também se aplica potencialmente aos bancos de dados, mas é muito mais fraco, pois as regras de validação de banco de dados tendem a ser declarativas e não procedimentais, portanto, elas não precisam ser duplicadas para cada consulta.
Dave Sherohman
24

Depende dos benefícios que você espera obter da imutabilidade. A resposta de Rei Miyasaka abordou uma (evitar estados intermediários inválidos), mas aqui está outra.

A mutação às vezes é chamada de atualização destrutiva : quando você modifica um objeto, o estado antigo é perdido (a menos que você tome medidas adicionais para preservá-lo explicitamente de alguma forma). Por outro lado, com dados imutáveis, é trivial representar simultaneamente o estado antes e depois de alguma operação ou representar vários estados sucessores. Imagine tentar implementar uma pesquisa abrangente, modificando um único objeto de estado.

Isso provavelmente aparece no mundo do banco de dados com mais frequência como dados temporais . Digamos no mês passado que você estava no plano Básico, mas no dia 16 você mudou para o plano Premium. Se simplesmente substituirmos algum campo que indica em qual plano você está, podemos ter dificuldades em acertar o faturamento. Também podemos perder a capacidade de analisar tendências. (Ei, veja o que essa campanha publicitária local fez!)

É o que me vem à cabeça quando você diz "imutabilidade no design do banco de dados", de qualquer maneira.

Ryan Culpepper
fonte
2
Discordo do seu terceiro parágrafo. Se você deseja ter um histórico (log de auditoria, log de alterações no plano etc.), é necessário criar uma tabela separada para isso. Duplicar todos os 50 campos da Customertabela apenas para lembrar que o usuário alterou o plano não traz nada, exceto enorme desvantagem de desempenho, seleções mais lentas ao longo do tempo, mineração de dados mais complicada (em comparação com logs) e mais espaço desperdiçado.
Arseni Mourzenko
6
@ MainMa: talvez eu devesse ter dito apenas "leia sobre bancos de dados temporais". Meu exemplo foi planejado como um esboço do que são dados temporais; Não afirmo que seja sempre a melhor maneira de representar dados alterados. Por outro lado, embora o suporte a dados temporais seja bastante ruim atualmente, espero que a tendência seja acomodar dados temporais no próprio banco de dados, em vez de relegá-los a representações de "segunda classe", como registros de alterações.
Ryan Culpepper
E se mantivermos um histórico de alterações em uma tabela de auditoria (inicialização por mola e hibernação, por exemplo, prejudicam esse recurso)?
Mohammad Najar
14

Se você estiver interessado nos benefícios que pode obter da imutabilidade em um banco de dados, ou pelo menos em um banco de dados que ofereça a ilusão de imutabilidade, verifique Datomic.

Datomic é um banco de dados inventado por Rich Hickey em aliança com a Think Relevance, existem muitos vídeos nos quais eles explicam a arquitetura, os objetivos e o modelo de dados. Pesquise infoq, um em particular é intitulado Datomic, Database como um valor . Em confreaks, você pode encontrar uma palestra que Rich Hickey fez na conferência euroclojure em 2012. confreaks.com/videos/2077-euroclojure2012-day-2-keynote-the-datomic-architecture-and-data-model

Há uma conversa no vimeo.com/53162418 que é mais orientada para o desenvolvimento.

Aqui está outra de stuart halloway em.pscdn.net/008/00102/videoplatform/kv/121105techconf_close.html

  • Datomic é um banco de dados de fatos no tempo, chamado datums, em cinco tuplas [E, A, V, T, O]
    • E ID da entidade
    • Um nome de atributo na entidade (pode ter espaços para nome)
    • V Valor do atributo
    • T ID da transação, com isso você tem noção de tempo.
    • O Uma operação de afirmação (valor presente ou atual), rejeição (valor passado);
  • Utiliza seu próprio formato de dados, chamado EDN (Extensible Data Notation)
  • As transações são ACID
  • Utiliza o registro de dados como linguagem de consulta, declarativa como consultas SQL + recursivas. As consultas são representadas com estruturas de dados e, estendidas com a linguagem jvm, você não precisa usar o clojure.
  • O banco de dados é dissociado em 3 serviços separados (processos, máquinas):
    • Transação
    • Armazenamento
    • Mecanismo de consulta.
  • Você pode dimensionar separadamente cada serviço.
  • Não é de código aberto, mas existe a versão gratuita (como na cerveja) do Datomic.
  • Você pode indicar um esquema flexível.
    • conjunto de atributos está aberto
    • adicione novos atributos a qualquer momento
    • sem rigidez na definição ou consulta

Agora, como as informações são armazenadas como fatos no tempo:

  • tudo o que você faz é adicionar fatos ao banco de dados e nunca excluí-los (exceto quando exigido por lei)
  • você pode armazenar tudo em cache para sempre. O Query Engine vive no servidor de aplicativos como um banco de dados na memória (para idiomas jvm, os idiomas não-jvm têm acesso por meio de uma API REST.)
  • você pode consultar no passado.

O banco de dados é um valor e um parâmetro para o mecanismo de consulta, o QE gerencia a conexão e o cache. Como você pode ver o banco de dados como um valor e a estrutura de dados imutável na memória, é possível mesclá-lo com outra estrutura de dados feita com valores "no futuro" e transmiti-la ao QE e à consulta com valores futuros, sem alterar o banco de dados real .

Existe um projeto de código aberto da Rich Hickey, chamado codeq , que pode ser encontrado no github Datomic / codeq, que estende o modelo git e armazena referências a objetos git em um banco de dados livre de datomic e faz consultas ao seu código. pode ver um exemplo de como usar datomic.

Você pode pensar em datômico como um ACID NoSQL, com dados que você pode modelar tabelas ou documentos ou lojas Kv ou gráficos.

kisai
fonte
7

A idéia de evitar atualizações e preferir inserções é um dos pensamentos por trás da criação de seu armazenamento de dados como uma Fonte de Eventos, uma ideia que você encontrará frequentemente usada junto com o CQRS. Em um modelo de origem de eventos, não há atualização: um agregado é representado como a sequência de sua "transformação" (eventos) e, como resultado, o armazenamento é apenas anexado.
Este site contém discussões interessantes sobre CQRS e fornecimento de eventos, se você estiver curioso sobre isso!

Mathias
fonte
Atualmente, o CQRS e o sourcing de eventos estão em destaque.
precisa
6

Isso mantém uma relação muito próxima com o que é conhecido como "Dimensões de alteração lenta" no mundo do data warehousing e as tabelas "Temporal" ou "Bi-Temporal" em outros domínios.

A construção básica é:

  1. Sempre use uma chave substituta gerada como chave primária.
  2. O identificador exclusivo do que você está descrevendo se torna a "chave lógica".
  3. Cada linha deve ter pelo menos um registro de data e hora "ValidFrom" e, opcionalmente, um registro de data e hora "ValidTo" e ainda mais opcionalmente um sinalizador "Versão mais recente".
  4. Na "criação" de uma entidade lógica, insira uma nova linha com um "Válido de" do carimbo de data / hora atual. O ValidTo opcional definido como "para sempre" (9999-12-31 23:59:59) e a Última versão como "True".
  5. Em uma atualização subsequente da entidade lógica. Você pelo menos insere uma nova linha como acima. Também pode ser necessário ajustar o ValidTo na versão anterior para "now () - 1 second" e a versão mais recente em "False"
    1. Na exclusão lógica (isso funciona apenas com o carimbo de data / hora ValidTo!), Você define o sinalizador ValidTo na linha atual como "now () -1 second".

As vantagens desse esquema são que você pode recriar o "estado" de sua entidade lógica a qualquer momento, ter um histórico de sua entidade ao longo do tempo e minimizar a contenção se sua "entidade lógica" for muito usada.

As desvantagens são que você armazena muito mais dados e precisa manter mais índices (pelo menos em Chave lógica + ValidFrom + ValidTo). Um índice na Chave Lógica + Versão Mais Recente acelera bastante a maioria das consultas. Também complica seu SQL!

Se vale a pena fazer isso, a menos que você realmente precise manter um histórico e tenha um requisito para recriar o estado de suas entidades em um determinado momento, depende de você.

James Anderson
fonte
1

Outro motivo possível para ter um banco de dados imutável seria oferecer suporte ao melhor processamento paralelo. As atualizações que estão fora de ordem podem atrapalhar os dados permanentemente; portanto, é necessário bloquear para impedir isso, destruindo o desempenho paralelo. Muitas inserções de eventos podem ocorrer em qualquer ordem, e o estado estará no mínimo correto , desde que todos os eventos sejam processados. No entanto isso é tão difícil de trabalhar na prática, comparado a fazer atualizações de banco de dados que você teria que realmente precisa de um monte de paralelismo considerar fazer as coisas desta maneira - eu estou não recomendá-lo.

psr
fonte
0

Disclaimer: Eu sou praticamente um novato no DB: p

Dito isto, essa abordagem de saturação de dados tem um impacto imediato no desempenho:

  • Bom menos tráfego na tabela principal
  • Boas linhas menores na tabela principal
  • A exigência de dados de satélite incorretos significa que é necessária outra pesquisa
  • Bad mais espaço ocupado se existem todos os objetos em ambas as tabelas

dependendo de seus requisitos, você pode aceitar isso ou não, mas certamente é um ponto a considerar.

Matthieu M.
fonte
-1

Não vejo como seu esquema possa ser chamado de "imutável".

O que acontece quando um valor armazenado na tabela suplementar é alterado? Parece que você precisaria executar uma atualização nessa tabela.

Para que um banco de dados seja realmente imutável, ele precisará ser mantido apenas por "INSERTS". Para isso, você precisa de algum método para identificar a linha "atual". Isso quase sempre acaba sendo terrivelmente ineficiente. Você precisa copiar todos os valores inalterados anteriores ou juntar o estado atual de vários registros ao consultar. A seleção da linha atual geralmente precisa de um SQL horrivelmente bagunçado como ( where updTime = (SELECT max(updTime) from myTab where id = ?).

Esse problema surge muito no DataWarehousing, no qual você precisa manter um histórico dos dados ao longo do tempo e poder selecionar o estado para qualquer ponto no tempo. A solução é geralmente tabelas "dimensionais". No entanto, enquanto eles resolvem o problema da DW "quem era o representante de vendas em janeiro passado". Eles não fornecem nenhuma das vantagens que as classes imutáveis ​​do Javas oferecem.

Em uma nota mais filosófica; existem bancos de dados para armazenar "estado" (saldo bancário, consumo de eletricidade, pontos de brownie no StackOverflow etc. etc.). Tentar criar um banco de dados "sem estado" parece um exercício inútil.

James Anderson
fonte
Para um único registro, WHERE id = {} ORDER BY updTime DESC LIMIT 1geralmente não é muito ineficiente.
Izkata
@Izkata - tente colocar no meio hte de três tabela de junção :-)
James Anderson