Utilizzo del repository git come backend del database


119

Sto realizzando un progetto che si occupa di database di documenti strutturati. Ho un albero di categorie (~ 1000 categorie, fino a ~ 50 categorie per ogni livello), ogni categoria contiene diverse migliaia (fino a, diciamo, ~ 10000) di documenti strutturati. Ogni documento è composto da diversi kilobyte di dati in una forma strutturata (preferirei YAML, ma potrebbe anche essere JSON o XML).

Gli utenti di questi sistemi eseguono diversi tipi di operazioni:

  • recupero di questi documenti tramite ID
  • ricerca di documenti tramite alcuni degli attributi strutturati al loro interno
  • modificare i documenti (ad esempio aggiungere / rimuovere / rinominare / unire); ogni operazione di modifica dovrebbe essere registrata come una transazione con qualche commento
  • visualizzare una cronologia delle modifiche registrate per un particolare documento (inclusa la visualizzazione di chi, quando e perché ha modificato il documento, ottenere una versione precedente e probabilmente tornare a questa se richiesto)

Ovviamente, la soluzione tradizionale sarebbe utilizzare una sorta di database di documenti (come CouchDB o Mongo) per questo problema - tuttavia, questa cosa del controllo della versione (cronologia) mi ha tentato a un'idea folle - perché non dovrei usare il gitrepository come backend del database per questa applicazione?

A prima vista, potrebbe essere risolto in questo modo:

  • categoria = directory, documento = file
  • ottenere il documento tramite ID => cambiare directory + leggere un file in una copia funzionante
  • modificare documenti con commenti di modifica => effettuare commit da vari utenti + memorizzare messaggi di commit
  • history => normale registro git e recupero di vecchie transazioni
  • ricerca => questa è una parte leggermente più complicata, immagino che richiederebbe l'esportazione periodica di una categoria in un database relazionale con l'indicizzazione delle colonne che consentiremo di cercare

Ci sono altre insidie ​​comuni in questa soluzione? Qualcuno ha già provato a implementare tale backend (cioè per qualsiasi framework popolare - RoR, node.js, Django, CakePHP)? Questa soluzione ha delle possibili implicazioni sulle prestazioni o sull'affidabilità, ovvero è dimostrato che git sarebbe molto più lento delle soluzioni di database tradizionali o ci sarebbero problemi di scalabilità / affidabilità? Presumo che un cluster di tali server che eseguono il push / pull del repository dell'altro dovrebbe essere abbastanza robusto e affidabile.

Fondamentalmente, dimmi se questa soluzione funzionerà e perché funzionerà o non funzionerà?


si prega di consultare youtube.com/watch?v=nPPlyjMlQ34
Assaf S.

Risposte:


58

Rispondere alla mia domanda non è la cosa migliore da fare, ma, poiché alla fine ho abbandonato l'idea, mi piacerebbe condividere la logica che ha funzionato nel mio caso. Vorrei sottolineare che questa logica potrebbe non essere applicabile a tutti i casi, quindi spetta all'architetto decidere.

In generale, il primo punto principale che manca alla mia domanda è che ho a che fare con sistemi multiutente che funzionano in parallelo, contemporaneamente, utilizzando il mio server con un thin client (cioè solo un browser web). In questo modo, devo mantenere lo stato per tutti loro. Ci sono diversi approcci a questo, ma tutti sono troppo impegnativi per le risorse o troppo complessi da implementare (e quindi in qualche modo uccidono lo scopo originale di scaricare tutte le cose difficili di implementazione su git in primo luogo):

  • Approccio "schietto": 1 utente = 1 stato = 1 copia funzionante completa di un repository che il server mantiene per l'utente. Anche se stiamo parlando di un database di documenti abbastanza piccolo (ad esempio, 100 MiB) con ~ 100K di utenti, mantenere il clone completo del repository per tutti loro fa sì che l'utilizzo del disco sia sfrenato (es. 100K di utenti per 100MiB ~ 10 TiB) . Quel che è peggio, la clonazione di un repository da 100 MiB ogni volta richiede diversi secondi di tempo, anche se eseguita in modo abbastanza efficace (cioè non usando git e roba unpacking-repacking), il che non è accettabile, IMO. E ancora peggio: ogni modifica che applichiamo a un albero principale dovrebbe essere trasferita al repository di ogni utente, il che è (1) il maiale delle risorse, (2) potrebbe portare a conflitti di modifica irrisolti in caso generale.

    Fondamentalmente, potrebbe essere negativo come O (numero di modifiche × dati × numero di utenti) in termini di utilizzo del disco, e tale utilizzo del disco significa automaticamente un utilizzo piuttosto elevato della CPU.

  • Approccio "Solo utenti attivi": mantenere la copia di lavoro solo per gli utenti attivi. In questo modo, generalmente non archivi un repo-clone-completo-per-utente, ma:

    • Quando l'utente accede, cloni il repository. Sono necessari diversi secondi e ~ 100 MiB di spazio su disco per utente attivo.
    • Mentre l'utente continua a lavorare sul sito, lavora con la copia di lavoro fornita.
    • Quando l'utente si disconnette, il suo clone del repository viene copiato di nuovo nel repository principale come ramo, memorizzando così solo le sue "modifiche non applicate", se ce ne sono, il che è abbastanza efficiente in termini di spazio.

    Pertanto, l'utilizzo del disco in questo caso raggiunge il picco O (numero di modifiche × dati × numero di utenti attivi), che di solito è ~ 100..1000 volte inferiore al numero di utenti totali, ma rende l'accesso / disconnessione più complicato e più lento , poiché implica la clonazione di un ramo per utente su ogni accesso e il ritiro di queste modifiche al logout o alla scadenza della sessione (cosa che dovrebbe essere eseguita a livello transazionale => aggiunge un altro livello di complessità). In numeri assoluti, nel mio caso fa scendere 10 TiB di utilizzo del disco a 10..100 GiB, che potrebbe essere accettabile, ma, ancora una volta, stiamo parlando di database abbastanza piccolo di 100 MiB.

  • Approccio "sparse checkout": fare "sparse checkout" invece di un vero e proprio clone repo per utente attivo non aiuta molto. Potrebbe risparmiare ~ 10 volte l'utilizzo dello spazio su disco, ma a scapito di un carico di CPU / disco molto più elevato nelle operazioni che coinvolgono la cronologia, il che elimina lo scopo.

  • Approccio "gruppo di lavoro": invece di fare cloni in piena regola ogni volta per persona attiva, potremmo mantenere un gruppo di cloni "lavoratore", pronti per essere utilizzati. In questo modo, ogni volta che un utente accede, occupa un "lavoratore", estraendovi il suo ramo dal repository principale e, quando si disconnette, libera il "lavoratore", che esegue un intelligente ripristino hardware di git per tornare a essere solo un clone del repository principale, pronto per essere utilizzato da un altro utente che accede. Non aiuta molto con l'utilizzo del disco (è ancora piuttosto alto - solo un clone completo per utente attivo), ma almeno rende più veloce il log in / out, a spese di ancora più complessità.

Detto questo, nota che ho calcolato intenzionalmente un numero di database e una base di utenti abbastanza piccoli: 100.000 utenti, 1.000.000 di utenti attivi, 100 MiB di database totale + cronologia delle modifiche, 10 MiB di copia funzionante. Se guardi a progetti di crowdsourcing più importanti, ci sono numeri molto più alti lì:

│              │ Users │ Active users │ DB+edits │ DB only │
├──────────────┼───────┼──────────────┼──────────┼─────────┤
│ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
│ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
│ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │

Ovviamente, per quella quantità di dati / attività, questo approccio sarebbe assolutamente inaccettabile.

In generale, avrebbe funzionato, se si potesse usare il browser web come un client "spesso", cioè eseguire operazioni git e archiviare praticamente l'intero checkout sul lato client, non sul lato server.

Ci sono anche altri punti che mi sono persi, ma non sono così male rispetto al primo:

  • Il modello stesso di avere uno stato di modifica "spesso" dell'utente è controverso in termini di ORM normali, come ActiveRecord, Hibernate, DataMapper, Tower, ecc.
  • Per quanto ho cercato, non esiste alcuna base di codice gratuita esistente per fare quell'approccio a git da framework popolari.
  • C'è almeno un servizio che in qualche modo riesce a farlo in modo efficiente - che è ovviamente github - ma, ahimè, la loro base di codice è closed source e sospetto fortemente che non utilizzino normali server git / tecniche di archiviazione repo all'interno, cioè fondamentalmente implementati git "big data" alternativo.

Quindi, in conclusione : è possibile, ma per la maggior parte dei casi d'uso attuali non sarà neanche lontanamente la soluzione ottimale. Il rollup della propria implementazione della cronologia di modifica del documento in SQL o il tentativo di utilizzare un database di documenti esistente sarebbe probabilmente un'alternativa migliore.


16
Probabilmente un po 'in ritardo per la festa, ma avevo un requisito simile a questo e in realtà ho seguito la strada del git. Dopo aver scavato un po 'con gli interni di git, ho trovato un modo per farlo funzionare. L'idea è di lavorare con un semplice repository. Ci sono alcuni inconvenienti, ma trovo che sia praticabile. Ho scritto tutto in un post che potresti voler controllare (semmai, per interesse): kenneth-truyers.net/2016/10/13/git-nosql-database
Kenneth

Un altro motivo per cui non lo faccio sono le capacità di query. Gli archivi di documenti spesso indicizzano i documenti, facilitando la ricerca al loro interno. Questo non sarà semplice con git.
FrankyHollywood

12

Un approccio davvero interessante. Direi che se hai bisogno di memorizzare dati, usa un database, non un repository di codice sorgente, che è progettato per un'attività molto specifica. Se potessi usare Git out-of-the-box, allora va bene, ma probabilmente dovrai creare un livello di repository di documenti su di esso. Quindi potresti crearlo anche su un database tradizionale, giusto? E se ti interessa il controllo della versione integrato, perché non utilizzare semplicemente uno degli strumenti di repository di documenti open source ? Ce ne sono tantissime tra cui scegliere.

Bene, se decidi di utilizzare comunque il backend Git, fondamentalmente funzionerebbe per le tue esigenze se lo implementassi come descritto. Ma:

1) Hai menzionato "cluster di server che si spingono / tirano l'un l'altro" - Ci ho pensato per un po 'e ancora non ne sono sicuro. Non è possibile eseguire il push / pull di più repository come operazione atomica. Mi chiedo se potrebbe esserci la possibilità di qualche pasticcio di unione durante il lavoro simultaneo.

2) Forse non ne hai bisogno, ma una funzionalità ovvia di un repository di documenti che non hai elencato è il controllo degli accessi. Potresti limitare l'accesso ad alcuni percorsi (= categorie) tramite sottomoduli, ma probabilmente non sarai in grado di concedere facilmente l'accesso a livello di documento.


11

il mio valore di 2 pence. Un po 'di nostalgia ma ... avevo un requisito simile in uno dei miei progetti di incubazione. Simile al tuo, i miei requisiti chiave dove un database di documenti (xml nel mio caso), con il controllo delle versioni dei documenti. Era per un sistema multiutente con molti casi d'uso di collaborazione. La mia preferenza era quella di utilizzare le soluzioni opensource disponibili che supportano la maggior parte dei requisiti chiave.

Per andare al sodo, non sono riuscito a trovare nessun prodotto che fornisse entrambi, in un modo sufficientemente scalabile (numero di utenti, volumi di utilizzo, risorse di archiviazione e di calcolo). soluzioni (probabili) che si potrebbero ricavare da esso. Mentre giocavo di più con l'opzione git, passare da una prospettiva per utente singolo a una prospettiva per utenti multipli (milli) è diventata una sfida ovvia. Sfortunatamente, non sono riuscito a fare un'analisi sostanziale delle prestazioni come hai fatto tu. (.. pigro / esci presto .... per la versione 2, mantra) Potere a te !. Ad ogni modo, la mia idea distorta da allora si è trasformata nella successiva alternativa (ancora parziale): un insieme di strumenti che sono i migliori nelle loro sfere separate, database e controllo delle versioni.

Mentre ancora work in progress (... e leggermente trascurato) la versione trasformata è semplicemente questa.

  • sul frontend: (userfacing) utilizza un database per l'archiviazione di 1 ° livello (interfacciamento con applicazioni utente)
  • sul backend, utilizza un sistema di controllo della versione (VCS) (come git) per eseguire il controllo delle versioni degli oggetti dati nel database

In sostanza, equivarrebbe ad aggiungere un plug-in per il controllo della versione al database, con un po 'di colla per l'integrazione, che potresti dover sviluppare, ma potrebbe essere molto più semplice.

Il modo in cui dovrebbe (dovrebbe funzionare) è che gli scambi di dati dell'interfaccia multiutente primaria avvengano attraverso il database. Il DBMS gestirà tutte le questioni divertenti e complesse come multiutente, concorrenza e operazioni atomiche, ecc. Sul backend il VCS eseguirà il controllo della versione su un singolo set di oggetti dati (nessuna concorrenza o problemi multiutente). Per ogni transazione effettiva sul database, il controllo della versione viene eseguito solo sui record di dati che sarebbero stati effettivamente modificati.

Per quanto riguarda la colla di interfaccia, sarà sotto forma di una semplice funzione di interazione tra il database e il VCS. In termini di progettazione, un approccio semplice sarebbe un'interfaccia guidata dagli eventi, con gli aggiornamenti dei dati dal database che attivano le procedure di controllo della versione (suggerimento: assumendo Mysql, uso di trigger e sys_exec () blah blah ...). In termini di complessità di implementazione, si va dal semplice ed efficace (es. scripting) al complesso e meraviglioso (qualche interfaccia di connettore programmato). Tutto dipende da quanto pazzo vuoi andare con esso e quanto capitale di sudore sei disposto a spendere. Penso che un semplice scripting dovrebbe fare la magia. E per accedere al risultato finale, alle varie versioni dei dati, una semplice alternativa è popolare un clone del database (più un clone della struttura del database) con i dati referenziati dal tag di versione / id / hash nel VCS. ancora una volta questo bit sarà un semplice lavoro di query / traduzione / mappatura di un'interfaccia.

Ci sono ancora alcune sfide e incognite da affrontare, ma suppongo che l'impatto e la rilevanza della maggior parte di questi dipenderanno in gran parte dai requisiti dell'applicazione e dai casi d'uso. Alcuni potrebbero finire per non essere problemi. Alcuni dei problemi includono la corrispondenza delle prestazioni tra i 2 moduli chiave, il database e il VCS, per un'applicazione con attività di aggiornamento dei dati ad alta frequenza, il ridimensionamento delle risorse (archiviazione e potenza di elaborazione) nel tempo sul lato git come i dati e gli utenti crescere: costante, esponenziale o eventualmente plateau

Del cocktail sopra, ecco cosa sto preparando al momento

  • utilizzando Git per il VCS (inizialmente considerato il buon vecchio CVS per via dell'uso solo di changeset o delta tra 2 versioni)
  • utilizzando mysql (a causa della natura altamente strutturata dei miei dati, xml con schemi xml rigorosi)
  • giocherellare con MongoDB (per provare un database NoSQl, che corrisponde strettamente alla struttura del database nativa utilizzata in git)

Alcuni fatti divertenti: git in realtà fa cose chiare per ottimizzare l'archiviazione, come la compressione e l'archiviazione dei soli delta tra le revisioni degli oggetti - SÌ, git memorizza solo i changeset o i delta tra le revisioni degli oggetti dati, dove è applicabile (sa quando e come) . Riferimento: packfiles, nel profondo delle viscere degli interni di Git - Revisione dell'object storage di git (file system indirizzabile dal contenuto), mostra strette somiglianze (dal punto di vista concettuale) con database noSQL come mongoDB. Ancora una volta, a scapito del capitale del sudore, potrebbe fornire possibilità più interessanti per l'integrazione del 2 e il miglioramento delle prestazioni

Se sei arrivato a questo punto, lasciami se quanto sopra può essere applicabile al tuo caso, e supponendo che lo sarebbe, come si sarebbe allineato ad alcuni degli aspetti nella tua ultima analisi completa delle prestazioni


4

Ho implementato una libreria Ruby in cima alla libgit2quale è abbastanza facile da implementare ed esplorare. Ci sono alcune ovvie limitazioni, ma è anche un sistema piuttosto liberatorio dato che ottieni la toolchain git completa.

La documentazione include alcune idee su prestazioni, compromessi, ecc.


2

Come hai detto, il caso multiutente è un po 'più complicato da gestire. Una possibile soluzione sarebbe quella di utilizzare file di indice Git specifici dell'utente risultanti in

  • nessuna necessità di copie di lavoro separate (l'utilizzo del disco è limitato ai file modificati)
  • nessuna necessità di lavoro preparatorio che richiede tempo (per sessione utente)

Il trucco è combinare la GIT_INDEX_FILEvariabile ambientale di Git con gli strumenti per creare manualmente commit Git:

Segue uno schema della soluzione (hash SHA1 effettivi omessi dai comandi):

# Initialize the index
# N.B. Use the commit hash since refs might changed during the session.
$ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>

#
# Change data and save it to `changed_file`
#

# Save changed data to the Git object database. Returns a SHA1 hash to the blob.
$ cat changed_file | git hash-object -t blob -w --stdin
da39a3ee5e6b4b0d3255bfef95601890afd80709

# Add the changed file (using the object hash) to the user-specific index
# N.B. When adding new files, --add is required
$ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file

# Write the index to the object db. Returns a SHA1 hash to the tree object
$ GIT_INDEX_FILE=user_index_file git write-tree
8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53

# Create a commit from the tree. Returns a SHA1 hash to the commit object
# N.B. Parent commit should the same commit as in the first phase.
$ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
3f8c225835e64314f5da40e6a568ff894886b952

# Create a ref to the new commit
git update-ref refs/heads/users/user_x_change_y <new_commit_hash>

A seconda dei dati, è possibile utilizzare un cron job per unire i nuovi riferimenti a, masterma la risoluzione dei conflitti è probabilmente la parte più difficile qui.

Le idee per renderlo più facile sono benvenute.


Questo è generalmente un approccio che non porta da nessuna parte, a meno che tu non voglia avere un concetto completo di transazione e interfaccia utente per la risoluzione manuale dei conflitti. L'idea generale per i conflitti è quella di far sì che l'utente lo risolva direttamente al momento del commit (cioè "mi dispiace, qualcun altro ha modificato il documento che stavi modificando -> per favore guarda le sue modifiche e le tue modifiche e uniscile"). Quando consenti a due utenti di eseguire correttamente il commit e poi scoprono in un cronjob asincrono che le cose sono andate male, generalmente non c'è nessuno disponibile per risolvere le cose.
GreyCat
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.