Favorire l'immutabilità nella progettazione di database


26

Uno degli elementi in Effective Java di Joshua Bloch è l'idea che le classi dovrebbero consentire la mutazione delle istanze il meno possibile e preferibilmente non del tutto.

Spesso, i dati di un oggetto vengono conservati in un database di qualche forma. Questo mi ha portato a pensare all'idea di immutabilità all'interno di un database, in particolare per quelle tabelle che rappresentano una singola entità all'interno di un sistema più grande.

Qualcosa che ho sperimentato di recente è l'idea di cercare di ridurre al minimo gli aggiornamenti che faccio alle tabelle che rappresentano questi oggetti e di provare a eseguire inserimenti il ​​più possibile.

Un esempio concreto di qualcosa che stavo sperimentando di recente. Se so che potrei aggiungere un record con dati aggiuntivi in ​​seguito, creerò un'altra tabella per rappresentarlo, un po 'come le seguenti definizioni di due tabelle:

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

Si spera ovvio che questi nomi non siano testuali, ma solo per dimostrare l'idea.

È un approccio ragionevole alla modellizzazione della persistenza dei dati? Vale la pena provare a limitare gli aggiornamenti eseguiti su una tabella, in particolare per la compilazione di valori null per i dati che potrebbero non esistere al momento della creazione del record? Ci sono momenti in cui un approccio come questo potrebbe causare un forte dolore in seguito?


7
Sento che questa è una soluzione senza problemi ... Dovresti aggiornare, piuttosto che creare elaborati adattamenti per evitare l'aggiornamento.
Fosco,

Penso che sia stata più una questione di avere un'idea intuitiva di una soluzione in mente, e di volerla gestire da quante più persone possibile, e nel frattempo rendersi conto che questa potrebbe non essere la migliore soluzione al problema che ho. Potrei aprire una domanda diversa con il problema, a condizione che non riesca a trovarlo altrove.
Ed Carrel,

1
Ci possono essere buoni motivi per evitare gli aggiornamenti nei database. Tuttavia, quando emergono questi motivi, si tratta più di un problema di ottimizzazione e come tale non dovrebbe essere fatto senza la prova che esiste un problema.
dietbuddha,

6
Penso che ci sia un forte argomento per l'immutabilità all'interno dei database. Risolve molti problemi. Penso che i commenti negativi non provengano da persone di mentalità aperta. Gli aggiornamenti sul posto sono la causa di tanti problemi. Direi che abbiamo tutto all'indietro. Gli aggiornamenti sul posto sono la soluzione legacy a un problema che non esiste più. Lo stoccaggio è economico. Perché farlo Quanti sistemi DB hanno registri di controllo, sistemi di controllo delle versioni, necessità di repliche distribuite che, come tutti sappiamo, richiedono la capacità di supportare la latenza per la scalabilità. L'immutabilità risolve tutto questo.
cirrus,

@Fosco Alcuni sistemi sono assolutamente tenuti a non cancellare mai i dati (incluso l'utilizzo UPDATE). Come le cartelle cliniche del dottore.
Izkata,

Risposte:


25

Lo scopo principale dell'immutabilità è quello di garantire che non vi siano istanti temporali in cui i dati in memoria si trovano in uno stato non valido. (L'altro è perché le notazioni matematiche sono per lo più statiche e quindi le cose immutabili sono più facili da concettualizzare e modellare matematicamente.) In memoria, se un altro thread tenta di leggere o scrivere dati mentre viene lavorato, potrebbe finire per corrompersi, o potrebbe essere esso stesso in uno stato corrotto. Se hai più operazioni di assegnazione ai campi di un oggetto, in un'applicazione con multithreading, un altro thread potrebbe provare a lavorare con esso tra qualche tempo - il che potrebbe essere negativo.

L'immutabilità risolve questo problema scrivendo prima tutte le modifiche in un nuovo posto nella memoria, quindi eseguendo l'assegnazione finale come un passo in avanti in una riscrittura del puntatore all'oggetto per puntare al nuovo oggetto, che su tutte le CPU è un atomico operazione.

I database fanno la stessa cosa usando le transazioni atomiche : quando si avvia una transazione, scrive tutti i nuovi aggiornamenti in una nuova posizione sul disco. Al termine della transazione, cambia il puntatore sul disco nel punto in cui si trovano i nuovi aggiornamenti, cosa che fa in un breve istante durante il quale altri processi non possono toccarlo.

Questa è anche la stessa cosa della tua idea di creare nuove tabelle, ad eccezione di più automatica e più flessibile.

Quindi, per rispondere alla tua domanda, sì, l'immutabilità è buona nei database, ma no, non è necessario creare tabelle separate solo per quello scopo; puoi semplicemente usare qualsiasi comando di transazione atomica disponibile per il tuo sistema di database.


Grazie per la risposta. Questa prospettiva era proprio ciò di cui avevo bisogno per rendermi conto che la mia intuizione stava cercando confusivamente di combinare un paio di idee diverse in un unico modello.
Ed Carrel,

8
C'è un po 'di più oltre all'atmosfera. L'argomento che vedo più spesso a favore dell'immutabilità in un contesto OOP è che gli oggetti immutabili richiedono che tu convalidi il loro stato solo una volta, nel costruttore. Se sono modificabili, è necessario ogni metodo che può cambiare il loro stato per verificare che lo stato risultante sia ancora valido, il che può aggiungere una notevole complessità alla classe. Questo argomento si applica anche ai database, ma è molto più debole, poiché le regole di convalida del database tendono ad essere dichiarative piuttosto che procedurali, quindi non devono essere duplicate per ogni query.
Dave Sherohman,

24

Dipende dai benefici che ti aspetti di ottenere dall'immutabilità. La risposta di Rei Miyasaka era indirizzata a uno (elusione di stati intermedi non validi), ma eccone un altro.

La mutazione viene talvolta chiamata aggiornamento distruttivo : quando mutate un oggetto, il vecchio stato viene perso (a meno che non facciate ulteriori passi per preservarlo in qualche modo esplicitamente). Al contrario, con dati immutabili, è banale rappresentare contemporaneamente lo stato sia prima che dopo alcune operazioni, o rappresentare più stati successori. Immagina di provare a implementare una ricerca ampia prima mutando un singolo oggetto a stati.

Questo probabilmente appare nel mondo del database più spesso come dati temporali . Di 'che il mese scorso eri sul piano di base, ma il 16 sei passato al piano Premium. Se abbiamo appena sovrascritto un campo che indicava il piano in corso, potremmo avere difficoltà a ottenere la fatturazione corretta. Potremmo anche perdere la capacità di analizzare le tendenze. (Ehi, guarda cosa ha fatto questa campagna pubblicitaria locale!)

Questo è quello che mi viene in mente quando dici "immutabilità nella progettazione di database", comunque.


2
Non sono d'accordo con il tuo terzo paragrafo. Se si desidera avere una cronologia (registro di controllo, registro delle modifiche del piano, ecc.), È necessario creare una tabella separata per questo. La duplicazione di tutti i 50 campi della Customertabella solo per ricordare che l'utente ha modificato il piano non porta altro che un enorme svantaggio delle prestazioni, selezioni più lente nel tempo, un data mining più complicato (rispetto ai registri) e più spazio sprecato.
Arseni Mourzenko,

6
@MainMa: forse avrei dovuto semplicemente dire "vai a leggere sui database temporali" invece. Il mio esempio era inteso come uno schizzo di quali siano i dati temporali; Non pretendo che sia sempre il modo migliore per rappresentare la modifica dei dati. D'altra parte, mentre attualmente il supporto per i dati temporali è piuttosto scarso, mi aspetto che la tendenza sia quella di accomodare i dati temporali nel database stesso, piuttosto che relegarli in rappresentazioni di "seconda classe" come i log delle modifiche.
Ryan Culpepper,

Che cosa succede se manteniamo una cronologia delle modifiche in una tabella di controllo (avvio a molla e ibernazione, ad esempio offe questa capacità)?
Mohammad Najar,

14

Se sei interessato ai vantaggi che puoi ottenere dall'immutabilità in un database o almeno in un database che offre l'illusione dell'immutabilità, controlla Datomic.

Datomic è un database inventato da Rich Hickey in collaborazione con Think Relevance, ci sono molti video in cui spiegano l'architettura, gli obiettivi, il modello di dati. Cerca infoq, uno in particolare si chiama Datomic, Database as a Value . In confreaks puoi trovare un keynote che Rich Hickey ha tenuto alla conferenza euroclojure nel 2012. confreaks.com/videos/2077-euroclojure2012-day-2-keynote-the-datomic-architecture-and-data-model

Si parla in vimeo.com/53162418 che è più orientato allo sviluppo.

Eccone un altro di stuart halloway at.pscdn.net/008/00102/videoplatform/kv/121105techconf_close.html

  • Datomic è un database di fatti nel tempo, chiamati datum, in 5 tuple [E, A, V, T, O]
    • E ID entità
    • Un nome di attributo nell'entità (può avere spazi dei nomi)
    • V Valore dell'attributo
    • T ID transazione, con questo hai la nozione di tempo.
    • O Un'operazione di asserzione (valore attuale o attuale), rifiuto (valore passato);
  • Utilizza il proprio formato di dati, chiamato EDN (Extensible Data Notation)
  • Le transazioni sono ACID
  • Utilizza il log dati come linguaggio di query, che è dichiarativo come query ricorsive SQL +. Le query sono rappresentate con strutture di dati ed estese con il linguaggio jvm, non è necessario utilizzare clojure.
  • Il database è disaccoppiato in 3 servizi separati (processi, macchine):
    • Transazione
    • Conservazione
    • Motore di query.
  • È possibile ridimensionare separatamente ciascun servizio.
  • Non è open source, ma esiste la versione gratuita (come nella birra) di Datomic.
  • È possibile dichiarare uno schema flessibile.
    • set di attributi è aperto
    • aggiungi nuovi attributi in qualsiasi momento
    • nessuna rigidità nella definizione o nella query

Ora, poiché le informazioni sono archiviate come fatti nel tempo:

  • tutto ciò che fai è aggiungere fatti al database, non li elimini mai (tranne quando è richiesto dalla legge)
  • puoi mettere tutto nella cache per sempre. Query Engine, risiede nel server delle applicazioni come in un database di memoria (per le lingue jvm le lingue non jvm hanno accesso tramite un'API REST.)
  • puoi interrogare a partire dal tempo in passato.

Il database è un valore e un parametro per il motore di query, il QE gestisce la connessione e la memorizzazione nella cache. Dal momento che è possibile visualizzare il db come valore e una struttura di dati immutabile in memoria, è possibile unirlo con un'altra struttura di dati ricavata da valori "in futuro" e passarlo al QE e interrogarlo con valori futuri, senza modificare il database effettivo .

Esiste un progetto open source di Rich Hickey, chiamato codeq , puoi trovarlo in github Datomic / codeq, che estende il modello git e memorizza i riferimenti agli oggetti git in un database privo di anatomia e fai query sul tuo codice, tu può vedere un esempio di come usare Datomic.

Puoi pensare a Datomic come un ACID NoSQL, con i riferimenti puoi modellare tabelle o documenti o negozi Kv o grafici.


7

L'idea di evitare gli aggiornamenti e di preferire gli inserti è uno dei pensieri alla base della creazione dell'archiviazione dei dati come fonte di eventi, un'idea che troverete spesso usata insieme a CQRS. In un modello di origine di eventi non è disponibile alcun aggiornamento: un aggregato viene rappresentato come sequenza della sua "trasformazione" (eventi) e, di conseguenza, l'archiviazione è di sola aggiunta.
Questo sito contiene interessanti discussioni sul CQRS e sull'approvvigionamento di eventi, se sei curioso!


Oggigiorno CQRS e il sourcing di eventi stanno diventando importanti.
Gulshan,

6

Ciò ha una relazione molto stretta con quelle che sono conosciute come "dimensioni che cambiano lentamente" nel mondo del data warehousing e tabelle "temporali" o "bi-temporali" in altri domini.

Il costrutto di base è:

  1. Utilizzare sempre una chiave surrogata generata come chiave primaria.
  2. L'identificatore univoco di qualunque cosa tu stia descrivendo diventa la "chiave logica".
  3. Ogni riga dovrebbe avere almeno un timestamp "ValidFrom" e, facoltativamente, un timestamp "ValidTo" e ancora più facoltativamente un flag "Ultima versione".
  4. Alla "creazione" di un'entità logica si inserisce una nuova riga con un "valido da" del timestamp corrente. Il ValidTo opzionale impostato su "per sempre" (9999-12-31 23:59:59) e Ultima versione su "Vero".
  5. Su un successivo aggiornamento dell'entità logica. Devi almeno inserire una nuova riga come sopra. Potrebbe anche essere necessario regolare ValidTo sulla versione precedente su "now () - 1 secondo" e l'ultima versione su "False"
    1. Alla cancellazione logica (funziona solo con il timestamp ValidTo!) Imposti il ​​flag ValidTo nella riga corrente su "now () -1 second".

I vantaggi di questo schema sono che puoi ricreare lo "stato" della tua entità logica in qualsiasi momento, hai una storia della tua entità nel tempo e minimizzi la contesa se la tua "entità logica" viene usata pesantemente.

Gli svantaggi sono la memorizzazione di molti più dati e la necessità di conservare più indici (almeno su Logical Key + ValidFrom + ValidTo). Un indice su Logical Key + Ultima versione accelera notevolmente la maggior parte delle query. Inoltre complica il tuo SQL!

Se valga la pena farlo, a meno che tu non abbia davvero bisogno di conservare una storia e di avere un requisito per ricreare lo stato delle tue entità in un determinato momento, dipende da te.


1

Un'altra possibile ragione per avere un database immutabile sarebbe supportare una migliore elaborazione parallela. Gli aggiornamenti che si verificano in modo anomalo possono rovinare i dati in modo permanente, pertanto è necessario bloccarli per impedirlo, distruggendo le prestazioni parallele. Molti inserimenti di eventi possono andare in qualsiasi ordine e lo stato alla fine sarà giusto fino a quando tutti gli eventi verranno infine elaborati. Tuttavia questo è così difficile lavorare con, in pratica, rispetto a fare gli aggiornamenti del database che si dovrebbe davvero bisogno di un sacco di parallelismo da considerare fare le cose in questo modo - io non consigliarlo.


0

Disclaimer: sono praticamente un nuovo arrivato in DB: p

Detto questo, questo approccio ai dati satellitari ha un impatto immediato sulle prestazioni:

  • Buono il traffico meno sulla tabella primaria
  • Buone righe più piccole sulla tabella principale
  • Cattiva richiesta dei dati satellitari significa che è necessaria un'altra ricerca
  • Cattivo spazio occupato se tutti gli oggetti sono presenti in entrambe le tabelle

a seconda delle tue esigenze, puoi darti il ​​benvenuto o no, ma è sicuramente un punto da considerare.


-1

Non vedo come il tuo schema possa essere definito "immutabile".

Cosa succede quando cambia un valore memorizzato nella tabella supplementare? Sembra che dovresti eseguire un aggiornamento su quella tabella.

Affinché un database sia veramente immutabile, dovrebbe essere gestito esclusivamente da "INSERTS". Per questo è necessario un metodo per identificare la riga "corrente". Questo finisce quasi sempre per essere terribilmente inefficiente. È necessario copiare tutti i valori invariati precedenti sopra oppure riunire lo stato corrente da diversi record durante la query. La selezione della riga corrente di solito richiede un tipo di SQL orribilmente disordinato ( where updTime = (SELECT max(updTime) from myTab where id = ?).

Questo problema si presenta molto in DataWarehousing in cui è necessario mantenere una cronologia dei dati nel tempo e, in grado di selezionare lo stato per un dato momento. La soluzione è di solito tabelle "dimensionali". Tuttavia, mentre risolvono il problema DW "che era il rappresentante di vendita lo scorso gennaio". Non offrono alcun vantaggio rispetto alle classi immutabili di Javas.

Su una nota più filosofica; esistono database per memorizzare lo "stato" (saldo bancario, consumo di elettricità, punti brownie su StackOverflow ecc. ecc.) che cerca di creare un database "stateless" sembra un esercizio piuttosto inutile.


Per un singolo record, WHERE id = {} ORDER BY updTime DESC LIMIT 1generalmente non è troppo inefficiente.
Izkata,

@Izkata - prova a mettere nel mezzo di un join a tre tavoli :-)
James Anderson
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.