Versione che controlla il contenuto di un database

16

Sto lavorando a un progetto web che coinvolge contenuti modificabili dall'utente e mi piacerebbe essere in grado di eseguire il monitoraggio della versione del contenuto reale, che vive in un database. Fondamentalmente, voglio implementare le cronologie dei cambiamenti in stile wiki.

Facendo qualche ricerca di base, vedo molta documentazione su come eseguire la versione del tuo schema del database (il mio è in realtà già controllato), ma qualsiasi strategia esistente su come tracciare il tuo contenuto del database le modifiche sono perse nella valanga di elementi di versioning dello schema, almeno nelle mie ricerche.

Posso pensare ad alcuni modi per implementare il mio monitoraggio delle modifiche, ma sembrano tutti piuttosto grezzi:

  • Salva l'intera riga su ogni modifica, collega la riga all'ID sorgente con una chiave primaria (quello a cui mi propongo attualmente è la più semplice). Tuttavia, molte piccole modifiche potrebbero produrre un notevole aumento di volume.
  • salva prima / dopo / utente / data / ora per ogni modifica, con un nome di colonna per correlare la modifica alla colonna pertinente.
  • salva prima / dopo / utente / data / ora con una tabella per ogni colonna (risulterebbe in troppe tabelle).
  • salva diffs / user / timestamp per ogni modifica con una colonna (questo significherebbe che dovresti percorrere l'intera cronologia delle modifiche intervenute per tornare ad una certa data).

Qual è l'approccio migliore qui? Rolling my own sembra che io stia probabilmente reinventando il codebase (migliore) di qualcun altro.

Punti bonus per PostgreSQL.

    
posta Fake Name 26.04.2015 - 07:03
fonte

5 risposte

1

Bene, ho finito con l'opzione più semplice, un trigger che copia la vecchia versione di una riga in un log di cronologia per tabella.

Se finisco con un eccesso di database, posso guardare eventualmente a ridurre alcune delle modifiche minori alla cronologia, se necessario.

La soluzione è risultata piuttosto disordinata, poiché volevo generare automaticamente le funzioni di trigger. Sono SQLAlchemy, quindi sono stato in grado di produrre la tabella della cronologia eseguendo alcuni hij dell'ereditarietà, il che era bello, ma le funzioni di trigger effettive richiedevano alcune stringhe ming per generare correttamente le funzioni di PostgreSQL e mappare le colonne da una tabella a un altro correttamente.

Comunque, è tutto su github qui .

    
risposta data 03.05.2015 - 20:31
fonte
11

La tecnica che ho usato normalmente è di salvare il record completo, con un campo end_timestamp. Esiste una regola aziendale che solo una riga può avere un end_timestamp nullo, e questo è ovviamente il contenuto attivo.

Se adotti questo sistema, ti consiglio vivamente di aggiungere un indice o un vincolo per applicare la regola. Questo è facile con Oracle, poiché un indice univoco può contenere un solo null. Altri database potrebbero essere più un problema. Avere il database impone la regola manterrà il tuo codice onesto.

Hai ragione nel ritenere che un sacco di piccole modifiche creeranno un eccesso, ma è necessario scambiarlo con il codice e semplificare i rapporti.

    
risposta data 26.04.2015 - 08:23
fonte
8

Si noti che se si utilizza Microsoft SQL Server, esiste già una funzione per quella chiamata Modifica acquisizione dati . Dovrai comunque scrivere codice per accedere alle versioni precedenti (CDC crea visualizzazioni specifiche per questo), ma almeno non devi modificare lo schema delle tue tabelle, né implementare il rilevamento delle modifiche stesso.

Sotto il cofano , ciò che accade è questo:

  • CDC crea una tabella aggiuntiva contenente le revisioni,

  • La tua tabella originale è usata come era prima, cioè qualsiasi aggiornamento si riflette direttamente in questa tabella,

  • La tabella CDC memorizza solo i valori modificati, il che significa che la duplicazione dei dati è ridotta al minimo.

Il fatto che le modifiche siano archiviate in una tabella diversa ha due importanti conseguenze:

  • Seleziona dalla tabella originale sono veloci come senza CDC. Se ricordo bene, CDC succede dopo l'aggiornamento, quindi gli aggiornamenti sono ugualmente veloci (anche se non ricordo bene come CDC gestisca la coerenza dei dati).

  • Alcune modifiche allo schema della tabella originale portano alla rimozione di CDC. Ad esempio, se aggiungi una colonna, CDC non sa come gestirlo. D'altra parte, aggiungere un indice o un vincolo dovrebbe andare bene. Questo diventa rapidamente un problema se abiliti il CDC su un tavolo soggetto a frequenti cambiamenti. Potrebbe esserci una soluzione che consente di modificare lo schema senza perdere CDC, ma non l'ho cercato.

risposta data 26.04.2015 - 10:44
fonte
6

Risolvi il problema "filosoficamente" e prima nel codice. E poi "negozia" con il codice e il database per farlo accadere.

Come esempio , se hai a che fare con articoli generici, un concetto iniziale per un articolo potrebbe assomigliare a questo:

class Article {
  public Int32 Id;
  public String Body;
}

E al livello di base successivo, voglio mantenere un elenco di revisioni:

class Article {
  public Int32 Id;
  public String Body;
  public List<String> Revisions;
}

E potrebbe sorgermi che il corpo corrente è solo l'ultima revisione. E questo significa due cose: ho bisogno che ogni revisione sia datata o numerata:

class Revision {
  public Int32 Id;
  public Article ParentArticle;
  public DateTime Created;
  public String Body;
}

E ... e il corpo corrente dell'articolo non deve essere distinto dall'ultima revisione:

class Article {
  public Int32 Id;
  public String Body {
    get {
      return (Revisions.OrderByDesc(r => r.Created))[0];
    }
    set {
      Revisions.Add(new Revision(value));
    }
  }
  public List<Revision> Revisions;
}

Mancano alcuni dettagli; ma illustra che probabilmente vuoi due entità . Uno rappresenta l'articolo (o un altro tipo di intestazione) e l'altro è un elenco di revisioni (raggruppando qualsiasi campo faccia in modo che un buon senso "filosofico" sia raggruppato). Non hai bisogno di particolari vincoli di database inizialmente, perché il tuo codice non si preoccupa di nessuna delle revisioni di per sé: sono proprietà di un articolo che conosce le revisioni.

Quindi, non è necessario preoccuparsi di contrassegnare le revisioni in un modo speciale o di appoggiarsi a un vincolo del database per contrassegnare l'articolo "corrente". È sufficiente un timestamp (anche se un ID di auto-inserimento sarebbe OK), renderli correlati all'articolo dei genitori e lasciare che l'articolo si occupi di sapere che l'ultimo è il più pertinente.

E lasci che un ORM gestisca i dettagli meno filosofici - o li nascondi in una classe di utility personalizzata se non stai utilizzando un ORM immediato.

Molto tempo dopo, dopo aver effettuato alcuni test di stress, potresti pensare di rendere pigra la proprietà di revisione, o di caricare pigro solo la revisione più in alto nell'attributo Body. Tuttavia, la struttura dei dati in questo caso non dovrebbe essere modificata per adattarsi a tali ottimizzazioni.

    
risposta data 30.04.2015 - 23:42
fonte
2

Esiste una pagina wiki PostgreSQL per un trigger di verifica del controllo che ti guida attraverso come configurare un registro di controllo che fai quello che ti serve.

Tiene traccia dei dati originali completi di una modifica, nonché dell'elenco di nuovi valori per gli aggiornamenti (per inserimenti ed eliminazioni, c'è un solo valore). Se si desidera ripristinare una versione precedente, è possibile prelevare la copia dei dati originali dal record di controllo. Tieni presente che se i tuoi dati riguardano chiavi esterne, è possibile che tali dati debbano essere ripristinati per mantenere la coerenza.

In generale, se l'applicazione di database trascorre la maggior parte del suo tempo solo sui dati correnti, penso che sia meglio tracciare versioni alternative in una tabella separata dai dati correnti. Ciò manterrà gli indici della tabella attivi più gestibili.

Se le righe che stai tracciando sono molto grandi e lo spazio è un problema serio, potresti provare a suddividere le modifiche e memorizzare le differenze / patch minime, ma questo è sicuramente più lavoro per coprire tutti i tipi di tipi di dati. L'ho già fatto in passato, ed è stato doloroso ricostruire vecchie versioni di dati passando a ritroso tutte le modifiche, una alla volta.

    
risposta data 30.04.2015 - 22:55
fonte

Leggi altre domande sui tag