Pensei em uma estrutura de banco de dados incomum e me pergunto se alguém já viu isso em uso antes. É basicamente usando 2 bancos de dados:
- O primeiro banco de dados retém apenas os dados atualmente válidos
- O segundo banco de dados mantém o histórico de tudo que já foi inserido, atualizado ou excluído no primeiro banco de dados
Cenário
Estou trabalhando em um projeto no qual sou obrigado a registrar tudo o que acontece e onde os dados são alterados com frequência.
Exemplo (não o real)
Você precisa fazer o design do banco de dados para uma liga de futebol. Nesta liga existem jogadores e equipes. Os jogadores geralmente trocam de time.
- Primeiro requisito : O banco de dados deve conter as informações necessárias para jogar a próxima partida. Isso significa uma lista de todos os jogadores, equipes e em qual equipe cada jogador está atualmente.
- Segundo requisito : O banco de dados deve conter valores históricos que usaremos para gerar estatísticas. Isso significa a lista de todos os jogadores que fizeram parte de um time ou a lista de todos os times dos quais um jogador fez parte.
O problema
Esses dois requisitos são meio opostos um ao outro. Eu tentei fazer tudo no mesmo banco de dados, mas não faz sentido. O primeiro requisito se preocupa apenas em "jogar a próxima partida", enquanto o segundo requisito se preocupa apenas em "gerar estatísticas".
Para fazer tudo no mesmo banco de dados, fui com uma espécie de banco de dados "apenas inserção" usando a óbvia exclusão macia para excluir / atualizar informações ...
O que inicialmente parecia uma tarefa fácil, mantendo uma lista de jogadores, equipes e a equipe atual de cada jogador, de repente se torna muito mais difícil. A lógica do aplicativo necessária para executar a próxima partida já é bastante complicada, mas agora o banco de dados tem um design muito inútil, no qual o aplicativo é obrigado a adicionar a verificação "é excluída" em cada consulta apenas para executar a próxima partida.
Você gostaria de ser o treinador que grita "todos os jogadores do time, venham até mim" e depois 2000 jogadores vêm até você. Nesse ponto, você provavelmente gritará "todos os jogadores que não foram excluídos do time, venham até mim" (enquanto juram sobre esse design estúpido).
Minha conclusão
Eu me perguntei por que você precisa colocar tudo no mesmo banco de dados. A exclusão reversível não apenas faz um trabalho ruim no registro de tudo, a menos que você adicione muitas colunas (time_created, who_created_it, time_deleted, who_deleted_it), mas também complica tudo. Isso complica o design do banco de dados e o design do aplicativo.
Além disso, recebo esses 2 requisitos como parte de um único aplicativo que não pode ser dividido, mas fico pensando: são dois aplicativos completamente distintos. Por que estou tentando fazer tudo juntos.
Foi quando pensei em dividir o banco de dados em dois. Um banco de dados operacional usado apenas para executar a próxima partida e conter apenas as informações atualmente válidas e um banco de dados histórico que contém todas as informações que já existiram, quando foram criadas, excluídas e quem as fez.
O objetivo é manter o primeiro banco de dados (operacional) e o aplicativo o mais simples possível, mantendo o máximo de informações possível no segundo banco de dados (histórico).
Questões
- Você já viu esse design antes? Tem nome?
- Há alguma armadilha óbvia que estou perdendo?
EDIT 2015-03-16
Arquitetura atual
Você pode basicamente pensar em toda a arquitetura como um processo de 2 etapas.
Passo 1 :
- O aplicativo está sendo executado e os usuários estão executando algumas ações
- Cada vez que um evento acontece, ele é gravado automaticamente (solução de auditoria) em uma tabela de eventos
- Em seguida, a linha correta, no banco de dados operacional, é atualizada
Passo 2 :
- Um trabalho lê a inserção mais recente na tabela de eventos e insere esses novos dados no banco de dados histórico.
- Os usuários consultam o banco de dados histórico para recuperar as informações necessárias.
Apenas na tabela de eventos, você pode reconstruir as informações para qualquer ponto no tempo. O problema é que essa tabela de eventos não é facilmente consultável. É aqui que o banco de dados histórico entra em ação; apresentar os dados de maneira que seja fácil recuperar exatamente o que queremos.
Problemas adicionais ao colocar tudo nas mesmas tabelas
Eu já expressei minha preocupação com a complexidade adicional de verificar "é excluído" em cada consulta. Mas há outra questão: integridade .
Faço uso pesado de chave estrangeira e restrição para garantir que, a qualquer momento, os dados que estão no meu banco de dados sejam válidos.
Vejamos um exemplo:
Restrição: Só pode haver um goleiro por equipe.
É fácil adicionar um índice único que verifique se existe apenas um goleiro por equipe. Mas então o que acontece quando você muda o goleiro? Você ainda precisa preservar as informações sobre a anterior, mas agora você tem 2 goleiros nas mesmas equipes, uma ativa e outra inativa, o que contradiz sua restrição.
Claro que é fácil adicionar uma verificação à sua restrição, mas é outra coisa para gerenciar e pensar.
Respostas:
Isso acontece com bastante frequência, embora o histórico (às vezes conhecido como registros de auditoria) seja mantido na mesma tabela ou em um separado no mesmo banco de dados.
Por exemplo, eu costumava trabalhar com um sistema em que quaisquer atualizações em uma tabela seriam implementadas como uma inserção, o antigo registro 'atual' teria um sinalizador dizendo que era um registro histórico e o carimbo de data e hora quando atualizado gravado em uma coluna.
Hoje trabalho em um sistema em que todas as alterações são gravadas em uma tabela de auditoria dedicada e a atualização ocorre na tabela.
O último é mais escalável, mas não tão fácil de implementar de maneira genérica.
A maneira mais fácil de atingir seu objetivo de simplificar as consultas e não exigir a adição do sinalizador 'é atual' é permitir apenas consultas de leitura por meio de um modo de exibição ou procedimento armazenado. Em seguida, você faz uma ligação para dizer "obtenha todos os jogadores" e o processo armazenado retornará apenas os jogadores atuais (você pode implementar um segundo procedimento para retornar jogadores com mais controle sobre quais são devolvidos). Isso funciona bem para escrever também. Um procedimento armazenado para atualizar um player pode, então, escrever os detalhes do histórico necessários e atualizar o player - sem que o cliente saiba qual é o mecanismo do histórico. Por esse motivo, os procedimentos armazenados são melhores do que uma exibição que retorna apenas os players atuais, pois mantém todo o mecanismo de acesso ao banco de dados o mesmo para leitura e gravação - tudo passa por um sproc.
fonte
Ao dividir um banco de dados em dois, você perderá todos os benefícios das referências relacionais e da verificação de integridade referencial. Eu nunca tentei uma coisa dessas, mas meu palpite é que isso se tornaria um grande pesadelo.
Acredito que todo o conjunto de dados que descreve um determinado sistema pertence a um único banco de dados. Questões de conveniência no acesso aos dados quase nunca são um bom motivo para tomar decisões sobre a organização dos dados.
Questões de conveniência no acesso aos seus dados devem ser tratadas, utilizando os recursos de conveniência oferecidos pelo seu RDBMS.
Portanto, em vez de ter um banco de dados 'atual' e um banco de dados 'histórico', você deve ter apenas um banco de dados e todas as tabelas nele devem ser prefixadas com 'histórico'. Em seguida, você deve criar um conjunto de visualizações, uma para cada tabela que deseja ver como 'atual', e cada uma filtrar as linhas históricas que você não deseja ver e deixar passar apenas as atuais.
Essa é uma solução adequada para o seu problema, pois utiliza um recurso de conveniência do RDBMS para tratar de um problema de conveniência do programador, deixando intacto o design do banco de dados.
Um exemplo de um problema que você provavelmente encontrará (muito tempo para um comentário)
Suponha que você esteja olhando para uma tela que mostra as informações atuais sobre um time, digamos team.id = 10, team.name = "Manchester United" e clique no botão que diz "mostrar histórico". Nesse ponto, você desejará mudar para uma tela que mostra informações históricas sobre a mesma equipe. Então, você pega o id 10, que você sabe que no banco de dados "atual" significa "Manchester United" e terá que esperaresse número de identificação 10 também significa "Manchester United" no banco de dados histórico. Não existe uma regra de integridade referencial que imponha que o ID se refira exatamente à mesma entidade nos dois bancos de dados; portanto, essencialmente, você terá dois conjuntos de dados totalmente disjuntos com conexões implícitas que são apenas conhecidas, honradas e prometidas para serem mantidas. por, código fora do banco de dados.
E é claro que isso se aplica não apenas às mesas principais, como a tabela "Times", mas também à menor mesa que você terá ao lado, como "Posições dos jogadores: atacante, meio-campista, goleiro etc."
Alcançar histórico dentro do mesmo banco de dados
Existem vários métodos para manter a historicidade e, embora estejam além do escopo desta questão, que é basicamente o que armadilhas essa idéia em particular pode ter, aqui está uma idéia:
Você pode manter uma tabela de log contendo uma entrada para cada alteração que já foi feita no banco de dados. Dessa forma, todas as tabelas "atuais" podem ser completamente limpas dos dados e totalmente reconstruídas, repetindo as alterações registradas. Obviamente, se você pode reconstruir as tabelas "atuais", reproduzindo as alterações desde o início dos tempos até agora, também pode criar um conjunto temporário de tabelas para obter uma visão do banco de dados em uma coordenada de tempo específica, reproduzindo as alterações do início do tempo até a coordenada específica do tempo.
Isso é conhecido como "Fonte de Eventos" (artigo de Martin Fowler.)
fonte
Primeiramente , sua base de código já oferece uma separação clara de preocupações, onde a lógica de negócios (de escolher jogadores para jogar na próxima partida) se distingue da lógica de acesso ao banco de dados (uma camada que simplesmente se conecta ao banco de dados e mapeia suas estruturas de dados nas linhas do banco de dados e vice-versa)? A resposta para isso ajudará bastante a explicar por que você está lidando com isso:
Agora...
Supondo que você esteja falando sobre RDBMS, ainda é possível ter um banco de dados bitemporal que captura todos os dados válidos passados, presentes e possivelmente futuros e , em seguida, use uma biblioteca / estrutura de ORM robusta o suficiente para lidar com a lógica de consulta de banco de dados para você. Você pode até usar uma exibição de banco de dados para ajudar na sua seleção. Em seguida, as partes da lógica comercial do seu código não precisam conhecer os campos temporais subjacentes, o que eliminará o problema descrito acima.
Por exemplo, em vez de precisar codificar uma consulta SQL no seu aplicativo:
(usando
?
como ligações de parâmetro)Uma biblioteca hipotética de acesso ao banco de dados pode permitir que você consulte como (pseudo-código):
Ou usando a abordagem de exibição de banco de dados:
Você pode consultar jogadores durante esse período como:
De volta ao seu modelo de banco de dados sugerido , como você pretende lidar com a remoção de dados históricos do seu banco de dados operacional? Você simplesmente cria
INSERT
duas linhas semelhantes nos bancos de dados operacionais e históricos e executa umaDELETE
no banco de dados operacional sempre que houver uma ação do usuário para excluir dados históricos?Pense grande
Se você estiver falando sobre processamento de dados em larga escala, em que seu 'banco de dados' é uma solução de processamento de cluster / fluxo de banco de dados distribuído em escala, sua abordagem soará vagamente semelhante (provavelmente apenas em alguns dos termos definidos) ao Lambda Arquitetura , na qual os dados 'históricos' (isto é, em tempo real) são processados em lote separadamente para executar o tipo de estatística que você está procurando, e os dados 'operacionais' (ou seja, em tempo real) permanecem com capacidade de consulta. um limite predefinido antes do processamento em lote persistir. No entanto, a base dessa abordagem é impulsionada mais pelas vantagens e limitações das implementações atuais de Big Data do que pela mera simplificação da lógica da aplicação.
editar (após a edição do OP)
Eu deveria ter respondido a isso antes, mas de qualquer maneira:
Isso geralmente ocorre porque os usuários finais tendem a pensar em termos de recursos, não no número de bancos de dados necessários .
Você também menciona que:
Ótimo! Então agora você tem uma tabela de eventos, que presumo ser o conceito de sourcing de eventos mencionado pelas outras respostas. No entanto, o que lê sua tabela de eventos e atualiza a linha correta no banco de dados operacional ? É o mesmo que o trabalho que lê o último evento e o insere no banco de dados histórico ?
Mais um ponto em relação ao seu exemplo de restrição:
"Algum ponto no tempo " refere-se a tempo válido ou tempo de transação ?
O que acontece quando temos um terceiro novo goleiro? Você cria uma restrição exclusiva nos campos temporais no banco de dados histórico para manter os dados de dois goleiros "antigos" válidos?
fonte
Na verdade, é semelhante à maneira como as transações do banco de dados geralmente são implementadas, exceto que os dados históricos geralmente são descartados após serem gravados no banco de dados operacional. O padrão de programação mais próximo que consigo pensar é a fonte de eventos .
Eu acho que dividir esses dois bancos de dados é a jogada certa. Mais especificamente, eu consideraria o banco de dados "operacional" como um cache, pois os dados históricos serão suficientes para reconstruir o banco de dados operacional a qualquer momento. Dependendo da natureza do aplicativo e dos requisitos de desempenho, pode ser desnecessário manter esse cache como um banco de dados separado se for razoável reconstruir o estado atual a partir dos dados históricos na memória toda vez que o programa for iniciado.
No que diz respeito às armadilhas, o principal problema com o qual você pode se deparar é se precisar de qualquer tipo de simultaneidade (no mesmo programa ou com vários clientes usando o banco de dados ao mesmo tempo). Nesse caso, você deseja garantir que as modificações nos bancos de dados históricos e operacionais sejam feitas atomicamente. No caso de simultaneidade dentro do mesmo programa, sua melhor aposta é provavelmente algum tipo de mecanismo de bloqueio. Para vários clientes interagindo com o mesmo banco de dados, a maneira mais fácil seria manter as duas tabelas no mesmo banco de dados e usar transações para manter o banco de dados consistente.
fonte
O suporte à versão de dados nos bancos de dados é um tópico bem estabelecido e alguns DBMSs suportam esse recurso. Lembro-me de ler que o MariaDB suporta versão de dados ( https://mariadb.com/resources/blog/automatic-data-versioning-in-mariadb-server-10-3/ ) e uma pesquisa rápida descobriu algo chamado OrpheusDB ( https: / /medium.com/data-people/painless-data-versioning-for-collaborative-data-science-90cf3a2e279d )
fonte