Utiliser le dépôt git en tant que backend de base de données

Je fais un projet qui traite de la base de données de documents structurés. J'ai un arbre de catégories (~ 1000 catégories, jusqu'à ~ 50 catégories sur chaque niveau), chaque catégorie contient plusieurs milliers (jusqu'à, disons, ~ 10000) de documents structurés. Chaque document contient plusieurs kilo-octets de données sous une forme structurée (je préférerais YAML, mais cela pourrait aussi bien être JSON ou XML).

Les utilisateurs de ce système effectuent plusieurs types d'opérations:

  • récupération de ces documents par ID
  • recherche de documents par certains des attributs structurés à l'intérieur
  • éditer des documents (c'est-à-dire ajouter/supprimer/renommer/fusionner); chaque opération d'édition doit être enregistrée comme une transaction avec un commentaire
  • afficher un historique des modifications enregistrées pour un document particulier (y compris afficher qui, quand et pourquoi a changé le document, obtenir une version antérieure - et revenir probablement à celui-ci si demandé)

Bien sûr, la solution traditionnelle serait d'utiliser une sorte de base de données de documents (comme CouchDB ou Mongo) pour ce problème - cependant, cette chose de contrôle de version (histoire) m'a tenté une idée folle - pourquoi ne pas utiliser git référentiel en tant que backend de base de données pour cette application?

Au premier coup d'œil, il pourrait être résolu comme ceci:

  • category = directory, document = file
  • getting document by ID => changing directories + reading a file in a working copy
  • editing documents with edit comments => making commits by various users + storing commit messages
  • history => normal git log and retrieval of older transactions
  • search => that's a slightly trickier part, I guess it would require periodic export of a category into relational database with indexing of columns that we'll allow to search by

Are there any other common pitfalls in this solution? Have anyone tried to implement such backend already (i.e. for any popular frameworks - RoR, node.js, Django, CakePHP)? Does this solution have any possible implications on performance or reliability - i.e. is it proven that git would be much slower than traditional database solutions or there would be any scalability/reliability pitfalls? I presume that a cluster of such servers that push/pull each other's repository should be fairly robust & reliable.

Fondamentalement, dites-moi si cette solution va fonctionner et pourquoi elle fera ou ne fera pas?

94
@sherbang - wow, vous venez de me faire un petit essai sur tout ce sujet - affiché comme une réponse. N'hésitez pas à discuter/poser des questions.
ajouté l'auteur GreyCat, source
Je suis curieux de savoir si vous avez suivi cette route et comment cela a fonctionné?
ajouté l'auteur sherbang, source
J'ai réfléchi à une solution comme celle-là pour le système de document que je suis sur le point de construire. Il a à peu près les mêmes exigences et il est encore en phase de planification. Je me demande comment cela s'est passé pour vous, si c'est le cas. S'il vous plaît partagez votre expérience avec nous.
ajouté l'auteur Omar Alves, source

5 Réponses

Répondre à ma propre question n'est pas la meilleure chose à faire, mais, comme j'ai finalement abandonné l'idée, j'aimerais partager la logique qui a fonctionné dans mon cas. J'aimerais souligner que cette justification pourrait ne pas s'appliquer à tous les cas, c'est donc à l'architecte de décider.

Généralement, le premier point que ma question omet est que j'ai affaire à système multi-utilisateur qui fonctionne en parallèle, en utilisant mon serveur avec un client léger (c'est-à-dire juste un navigateur web). De cette façon, je dois maintenir état pour chacun d'entre eux. Il y a plusieurs approches à celle-ci, mais toutes sont trop difficiles sur les ressources ou trop complexes à implémenter (et donc sorte de tuer l'objectif initial de décharger tout le matériel d'implémentation dur à git en premier lieu):

  • "Blunt" approach: 1 user = 1 state = 1 full working copy of a repository that server maintains for user. Even if we're talking about fairly small document database (for example, 100s MiBs) with ~100K of users, maintaining full repository clone for all of them makes disc usage run through the roof (i.e. 100K of users times 100MiB ~ 10 TiB). What's even worse, cloning 100 MiB repository each time takes several seconds of time, even if done in fairly effective maneer (i.e. not using by git and unpacking-repacking stuff), which is non acceptable, IMO. And even worse — every edit that we apply to a main tree should be pulled to every user's repository, which is (1) resource hog, (2) might lead to unresolved edit conflicts in general case.

    Basically, it might be as bad as O(number of edits × data × number of users) in terms of disc usage, and such disc usage automatically means pretty high CPU usage.

  • "Only active users" approach: maintain working copy only for active users. This way, you generally store not a full-repo-clone-per-user, but:

    • As user logs in, you clone the repository. It takes several seconds and ~100 MiB of disc space per active user.
    • As user continues to work on the site, he works with the given working copy.
    • As user logs out, his repository clone is copied back to main repository as a branch, thus storing only his "unapplied changes", if there are any, which is fairly space-efficient.

    Thus, disc usage in this case peaks at O(number of edits × data × number of active users), which is usually ~100..1000 times less than number of total users, but it makes logging in/out more complicated and slower, as it involves cloning of a per-user branch on every login and pulling these changes back on logout or session expiration (which should be done transactionally => adds another layer of complexity). In absolute numbers, it drops 10 TiBs of disc usage down to 10..100 GiBs in my case, that might be acceptable, but, yet again, we're now talking about fairly small database of 100 MiBs.

  • "Sparse checkout" approach: making "sparse checkout" instead of full-blown repo clone per active user doesn't help a lot. It might save ~10x of disc space usage, but at expense of much higher CPU/disc load on history-involving operations, which kind of kills the purpose.

  • "Workers pool" approach: instead of doing full-blown clones every time for active person, we might keep a pool of "worker" clones, ready to be used. This way, every time a users logs in, he occupies one "worker", pulling there his branch from main repo, and, as he logs out, he frees the "worker", which does clever git hard reset to become yet again just a main repo clone, ready to be used by another user logging in. Does not help much with disc usage (it's still pretty high — only full clone per active user), but at least it makes logging in/out faster, as expense of even more complexity.

Cela dit, notez que j'ai délibérément calculé le nombre de bases de données et d'utilisateurs relativement petits: 100 000 utilisateurs, 1 000 utilisateurs actifs, 100 bases de données totales de MiB + historique des modifications, 10 millions de copies de travail. Si vous regardez des projets d'externalisation ouverte plus importants, il y a des chiffres beaucoup plus élevés:

│              │ 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 │

Évidemment, pour cette quantité de données/activité, cette approche serait tout à fait inacceptable.

En général, cela aurait fonctionné, si l'on pouvait utiliser un navigateur Web comme client "épais", c'est-à-dire émettre des opérations git et stocker à peu près la totalité du paiement du côté du client, pas du côté du serveur.

Il y a aussi d'autres points que j'ai manqués, mais ils ne sont pas si mauvais comparés au premier:

  • Le modèle même d'avoir un état d'édition de l'utilisateur "épais" est controversé en termes de ORMs normaux, tels que ActiveRecord, Hibernate, DataMapper, Tower, etc.
  • Autant que j'ai cherché, il y a zéro base de code libre existante pour faire cette approche à git des cadres populaires.
  • Il y a au moins un service qui réussit à le faire efficacement - c'est évidemment github - mais, hélas, leur base de code est une source fermée et je soupçonne fortement qu'ils n'utilisent pas les techniques normales de stockage des serveurs git/repo à l'intérieur, c'est-à-dire qu'ils ont fondamentalement implémenté un git alternatif "big data".

Donc, ligne de fond : c'est possible, mais pour la plupart des cas d'utilisation actuels, il ne sera pas proche de la solution optimale. Faire rouler votre propre implémentation de document-edit-history-to-SQL ou essayer d'utiliser n'importe quelle base de documents existante serait probablement une meilleure alternative.

41
ajouté
Probablement un peu en retard à la fête, mais j'avais une exigence similaire à celle-ci et je suis tombé sur la route git. Après avoir creusé avec les internes git, j'ai trouvé un moyen de le faire fonctionner. L'idée est de travailler avec un référentiel nu. Il y a quelques inconvénients, mais je trouve que c'est faisable. J'ai tout écrit dans un post que vous voudrez peut-être vérifier (si quelque chose, par intérêt): kenneth-truyers.net/2016/10/13/git-nosql-database
ajouté l'auteur Kenneth, source
Une autre raison pour laquelle je ne fais pas cela est la capacité de requête. Les magasins de documents indexent souvent les documents, ce qui facilite la recherche en leur sein. Ce ne sera pas simple avec git.
ajouté l'auteur FrankyHollywood, source
Wow, merci pour la réponse détaillée. Je suis arrivé à l'idée d'utiliser git puisqu'il s'agit effectivement d'une base de données versionnée. La concurrence est moins un problème pour moi, mais j'ai des problèmes différents parce que les données de mes points de terminaison ne correspondent pas exactement.
ajouté l'auteur sherbang, source
@Kenneth, votre message que vous avez lié ici est très détaillé et professionnellement conçu. Je le recommande à tous ceux qui envisagent d'utiliser git comme base de données
ajouté l'auteur Andrzej Martyna, source

Une approche intéressante en effet. Je dirais que si vous avez besoin de stocker des données, utilisez une base de données, pas un référentiel de code source, qui est conçu pour une tâche très spécifique. Si vous pouvez utiliser Git prêt à l'emploi, alors c'est bien, mais vous devrez probablement créer un calque de référentiel de documents. Donc, vous pouvez aussi le construire sur une base de données traditionnelle, n'est-ce pas? Et si c'est le contrôle de version intégré qui vous intéresse, pourquoi ne pas utiliser outils de référentiel de documents open source Il y a beaucoup de choix.

Eh bien, si vous décidez d'opter pour Git backend de toute façon, alors cela fonctionnerait essentiellement pour vos besoins si vous l'implémentiez comme décrit. Mais:

1) Vous avez mentionné «un groupe de serveurs qui se poussent/tirent l'un l'autre» - j'y ai réfléchi pendant un moment et je ne suis toujours pas sûr. Vous ne pouvez pas pousser/tirer plusieurs repos comme opération atomique. Je me demande s'il pourrait y avoir une possibilité de confusion lors des travaux simultanés.

2) Peut-être que vous n'en avez pas besoin, mais une fonctionnalité évidente d'un référentiel de documents que vous n'avez pas répertorié est le contrôle d'accès. Vous pouvez éventuellement restreindre l'accès à certains chemins (= catégories) via des sous-modules, mais vous ne serez probablement pas en mesure d'accorder facilement l'accès au niveau du document.

12
ajouté
Bon aperçu sur le contrôle d'accès.
ajouté l'auteur engineerC, source

Ma valeur de 2 pence. Un peu envie, mais ...... J'avais une exigence similaire dans l'un de mes projets d'incubation. Similaire au vôtre, mes exigences clés où une base de données de documents (xml dans mon cas), avec le versionnage de documents. C'était pour un système multi-utilisateur avec beaucoup de cas d'utilisation de collaboration. Ma préférence était d'utiliser des solutions opensource disponibles qui prennent en charge la plupart des exigences clés.

Pour aller droit au but, je ne trouvais aucun produit fournissant les deux, d'une manière suffisamment évolutive (nombre d'utilisateurs, volumes d'utilisation, ressources de stockage et de calcul). Je privilégiais git pour toute la capacité prometteuse, et solutions (probables) que l'on pourrait en tirer. Comme je jouais avec l'option git plus, passer d'une perspective mono-utilisateur à une perspective multi-utilisateur est devenu un défi évident. Malheureusement, je n'ai pas fait d'analyse de performance substantielle comme vous l'avez fait. (.. paresseux/quitte tôt .... pour la version 2, mantra) Le pouvoir à vous !. Quoi qu'il en soit, mon idée biaisée s'est transformée en l'alternative suivante (toujours biaisée): un maillage d'outils qui sont les meilleurs dans leurs sphères séparées, bases de données et contrôle de version.

Bien que toujours en cours (... et légèrement négligé) la version morphée est simplement celle-ci.

  • sur le frontend: (userfacing) utilise une base de données pour le 1er niveau stockage (interfaçage avec des applications utilisateur)
  • sur le backend, utiliser un système de contrôle de version (VCS) (comme git) pour effectuer versionnage des objets de données dans la base de données

En substance, cela reviendrait à ajouter un plugin de contrôle de version à la base de données, avec un peu de colle d'intégration, que vous devrez peut-être développer, mais cela sera beaucoup plus facile.

Comment cela fonctionnerait (supposé) est que les échanges de données de l'interface multi-utilisateur primaire sont à travers la base de données. Le SGBD traitera toutes les questions amusantes et complexes telles que multi-utilisateur, e-concurrence, opérations atomiques etc. Sur le backend, le VCS effectuerait le contrôle de version sur un seul ensemble d'objets de données (pas de concurrence, ou multi-utilisateurs). Pour chaque transaction effective sur la base de données, le contrôle de version n'est effectué que sur les enregistrements de données qui auraient effectivement changé.

Quant à la colle d'interfaçage, elle se présentera sous la forme d'une simple fonction d'interfonctionnement entre la base de données et le VCS. En termes de conception, une approche simple serait une interface pilotée par les événements, avec des mises à jour de données de la base de données déclenchant les procédures de contrôle de version (indice: en supposant Mysql, utilisation des déclencheurs et sys_exec() bla bla ...). En termes de complexité de mise en œuvre, il va de le simple et efficace (par exemple le script) au complexe et merveilleux (une interface de connecteur programmée). Tout dépend de la façon dont vous voulez aller avec elle, et combien de capital que vous êtes prêt à dépenser. Je pense que les scripts simples devraient faire la magie. Et pour accéder au résultat final, les différentes versions de données, une alternative simple est de remplir un clone de la base de données (plus un clone de la structure de base de données) avec les données référencées par la balise/id/hash de version dans le VCS. encore ce bit sera un simple travail de requête/traduction/carte d'une interface.

Il y a encore des défis et des inconnues à traiter, mais je suppose que l'impact et la pertinence de la plupart d'entre eux dépendront en grande partie des exigences de votre application et des cas d'utilisation. Certains peuvent finir par ne pas être des problèmes. Certains des problèmes incluent la correspondance de performance entre les 2 modules clés, la base de données et le VCS, pour une application avec une activité de mise à jour de données haute fréquence, mise à l'échelle des ressources (stockage et puissance de traitement) croître: stable, exponentielle ou éventuellement plateau

Du cocktail ci-dessus, voici ce que je prépare actuellement

  • utiliser Git pour le VCS (initialement considéré comme un bon vieux CVS pour l'utilisation de seulement des changesets ou des deltas entre 2 versions)
  • en utilisant MySQL (en raison de la nature hautement structurée de mes données, xml avec des schémas xml stricts)
  • jouer avec MongoDB (pour essayer une base de données NoSQl, qui correspond étroitement à la structure de base de données native utilisée dans git)

Quelques faits amusants - git fait des choses claires pour optimiser le stockage, comme la compression, et le stockage des deltas seulement entre la révision des objets - OUI, git stocke uniquement les changesets ou les deltas entre les révisions des objets de données, où est-il applicable (il sait quand et comment). Référence: packfiles, deep dans le guts des internes de Git - La revue du stockage d'objet de git (système de fichiers adressable par contenu), montre des similitudes frappantes (du point de vue du concept) avec des bases de données de noSQL telles que mongoDB. Encore une fois, au détriment du capital de sueur, il peut fournir des possibilités plus intéressantes pour l'intégration du 2, et l'amélioration de la performance

Si vous en êtes arrivé là, permettez-moi si ce qui précède peut être applicable à votre cas, et en supposant que ce soit le cas, comment cela correspondrait à certains aspects de votre dernière analyse complète des performances

12
ajouté

Comme vous l'avez mentionné, le cas multi-utilisateur est un peu plus délicat à gérer. Une solution possible consisterait à utiliser des fichiers d'index Git spécifiques à

  • pas besoin de copies de travail séparées (l'utilisation du disque est limitée aux fichiers modifiés)
  • pas besoin de travail préparatoire fastidieux (par session utilisateur)

L'astuce consiste à combiner GIT_INDEX_FILE variable d'environnement avec les outils pour créer des commits Git manuellement:

Un plan de solution suit (les hachages SHA1 réels omis dans les commandes):

# 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 

#
# 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  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  -p 
3f8c225835e64314f5da40e6a568ff894886b952

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

En fonction de vos données, vous pouvez utiliser un travail cron pour fusionner les nouvelles références à master , mais la résolution de conflit est sans doute la partie la plus difficile ici.

Les idées pour le rendre plus facile sont les bienvenues.

2
ajouté
C'est généralement une approche qui ne mène nulle part, sauf si vous voulez avoir un concept complet de transaction et d'interface utilisateur pour la résolution manuelle des conflits. L'idée générale pour les conflits est de faire en sorte que l'utilisateur la résolve dès la validation (c.-à-d. «Désolé, quelqu'un d'autre a édité le document que vous étiez en train de modifier -> veuillez voir ses modifications et vos modifications»). Lorsque vous autorisez deux utilisateurs à s'engager, puis découvrez dans un cronjob asynchrone que les choses se sont déroulées dans le Sud, il n'y a généraleme
ajouté l'auteur GreyCat, source

J'ai mis en place une bibliothèque ruby en plus de libgit2 , ce qui rend cette opération très facile mettre en œuvre et explorer. Il y a quelques limitations évidentes, mais c'est aussi un système assez libérateur puisque vous obtenez la chaîne d'outils git complète.

La documentation comprend des idées sur la performance, les compromis, etc.

1
ajouté