Używanie repozytorium git jako zaplecza bazy danych

Robię projekt, który zajmuje się strukturyzowaną bazą dokumentów. Mam Drzewo kategorii (~1000 kategorii, do ~50 kategorii na każdym poziomie), każda kategoria zawiera kilka tysięcy (do, powiedzmy, ~10000) uporządkowanych dokumentów. Każdy dokument to kilka kilobajtów danych w jakiejś ustrukturyzowanej formie (wolałbym YAML, ale równie dobrze może to być JSON lub XML).

Użytkownicy tego systemu wykonują kilka rodzajów operacji:

  • Odzyskiwanie tych dokumentów przez ID
  • wyszukiwanie dokumentów według niektórych atrybutów strukturalnych w nich
  • edycja dokumentów (np. dodawanie/usuwanie/zmiana nazwy/łączenie); każda operacja edycji powinna być zapisana jako transakcja z komentarzem
  • przeglądanie historii zarejestrowanych zmian dla konkretnego dokumentu (w tym przeglądanie kto, kiedy i dlaczego zmienił dokument, uzyskanie wcześniejszej wersji - i prawdopodobnie powrót do tej, jeśli jest to wymagane)

Oczywiście tradycyjnym rozwiązaniem byłoby CouchDB lub Mongo) dla tego problemu - jednak ta kontrola wersji (historia) skusiła mnie do szalonego pomysłu-dlaczego nie powinienem używać repozytorium git jako zaplecza bazy danych dla tej aplikacji?

Na pierwszy rzut oka można to rozwiązać tak:

  • Kategoria = Katalog, dokument = Plik
  • uzyskanie dokumentu przez ID = > zmiana katalogów + odczyt pliku w kopii roboczej
  • edycja dokumentów z komentarzami edycji => tworzenie commitów przez różnych użytkowników + przechowywanie wiadomości commit
  • historia = > zwykły dziennik git i pobieranie starszych transakcji
  • search = > to nieco trudniejsza część, chyba wymagałaby okresowego eksportu kategorii do relacyjnej bazy danych z indeksowaniem kolumn, które pozwolimy przeszukiwać według

Czy są jakieś inne typowe pułapki w tym rozwiązaniu? Czy ktoś już próbował zaimplementować taki backend (np. dla dowolnych popularnych frameworków-RoR, node.js, Django, CakePHP)? Czy to rozwiązanie ma jakikolwiek wpływ na wydajność lub niezawodność - tj. czy udowodniono, że git byłby znacznie wolniejszy od tradycyjnych rozwiązań bazodanowych lub pojawiłyby się jakieś pułapki skalowalności/niezawodności? Zakładam, że klaster takich serwerów, które pchają / ciągną repozytorium innych, powinien być dość solidny i niezawodny.

W zasadzie powiedz mi czy To rozwiązanie zadziała i dlaczego zadziała czy nie?

Author: GreyCat, 2013-11-22

5 answers

Odpowiedź na moje pytanie nie jest najlepszą rzeczą do zrobienia, ale, ponieważ ostatecznie porzuciłem ten pomysł, chciałbym podzielić się uzasadnieniem, które zadziałało w moim przypadku. Chciałbym podkreślić, że to uzasadnienie może nie dotyczyć wszystkich przypadków, więc decyzja należy do architekta.

Ogólnie rzecz biorąc, pierwszą główną kwestią, której nie dostrzegam w moim pytaniu, jest to, że mam do czynienia z systemem multi-user, który działa równolegle, jednocześnie, używając mojego serwera z cienkim klientem (tj. tylko przeglądarką internetową). W ten sposób mam aby utrzymać Stan dla wszystkich z nich. Istnieje kilka podejść do tego, ale wszystkie z nich są albo zbyt trudne dla zasobów, albo zbyt skomplikowane do wdrożenia (i tym samym zabijają pierwotny cel odcięcia wszystkich trudnych implementacji do git): {]}

  • "Blunt" approach: 1 user = 1 state = 1 full working copy of a repozytorium that server mains for user. Nawet jeśli mówimy o dość małej bazie danych dokumentów (na przykład MIBs 100s) z ~100k użytkowników, utrzymanie pełnego klon repozytorium dla wszystkich z nich sprawia, że korzystanie z dysku przebiega przez dach (tj. 100k użytkowników razy 100mib ~ 10 TiB). Co gorsza, klonowanie repozytorium 100 MiB za każdym razem zajmuje kilka sekund, nawet jeśli odbywa się to w dość efektywnym maneerze (tzn. nieużywanie przez git i rozpakowywanie-przepakowywanie rzeczy), co jest niedopuszczalne IMO. Co gorsza-każda edycja, którą zastosujemy do drzewa głównego powinna być ściągana do repozytorium każdego użytkownika, czyli (1) resource hog, (2) może prowadzić do nierozwiązanych konfliktów edycji w ogólnych przypadkach.

    Zasadniczo może być tak źle jak O (liczba edycji × dane × Liczba użytkowników) pod względem użycia dysku, a takie użycie dysku automatycznie oznacza dość wysokie zużycie procesora.

  • Podejście"tylko aktywni użytkownicy": zachowaj kopię roboczą tylko dla aktywnych użytkowników. W ten sposób zazwyczaj nie przechowuje się pełnego-repo-clone-per-user, ale:

    • gdy użytkownik się loguje, klonujesz repozytorium. Zajmuje kilka sekund i ~100 MiB miejsca na dysku na aktywnego użytkownika.
    • jako Użytkownik kontynuuje pracę na stronie, pracuje z danym egzemplarzem roboczym.
    • gdy użytkownik się wyloguje, jego klon repozytorium jest kopiowany z powrotem do głównego repozytorium jako gałąź, przechowując w ten sposób tylko jego "nieprzepisane zmiany", jeśli takie istnieją, co jest dość oszczędne miejsce.

    Tak więc, użycie dysku w tym przypadku osiąga wartość O (liczba edycji × dane × liczba aktywnych użytkowników), która zwykle wynosi ~100..1000 razy mniej niż liczba wszystkich użytkowników, ale to sprawia, że logowanie jest bardziej skomplikowane i wolniejsze, ponieważ polega na klonowaniu gałęzi dla każdego Użytkownika przy każdym logowaniu i wycofywaniu tych zmian po wylogowaniu lub wygaśnięciu sesji (co powinno być wykonane transakcyjnie = > dodaje kolejną warstwę złożoności). W liczbach bezwzględnych spada 10 piszczałek zużycia dysku do 10..100 Gib w moim przypadku, to może być do przyjęcia, ale, po raz kolejny, mówimy teraz o dość małej bazie 100 MIB.

  • "Sparse checkout" podejście: dokonywanie "oszczędnej kasy" zamiast pełnowymiarowego klonu repo na aktywnego użytkownika nie pomaga zbyt wiele. Może to zaoszczędzić ~10x miejsca na dysku, ale kosztem znacznie większego obciążenia procesora / dysku w operacjach związanych z historią, które zabijają cel.

  • "podejście" Workers pool": zamiast robić pełnowymiarowe klony za każdym razem dla aktywnej osoby, możemy zachować pulę klonów "worker", gotowych do użycia. W ten sposób, za każdym razem, gdy użytkownik loguje się, zajmuje jednego "pracownika", ciągnąc tam jego branch z głównego repo, a gdy się wyloguje, uwalnia "workera", który robi clever Git hard reset, aby ponownie stać się głównym klonem repo, gotowym do użycia przez innego logującego się użytkownika. Nie pomaga zbytnio w użyciu dysku (nadal jest dość wysoki - Tylko pełny klon NA aktywnego użytkownika), ale przynajmniej sprawia, że logowanie/wylogowanie się jest szybsze, jako koszt jeszcze większej złożoności.

To powiedziawszy, zauważ, że celowo obliczyłem liczby dość małej bazy danych i bazy użytkowników: 100k użytkowników, 1K aktywni użytkownicy, całkowita baza danych 100 MiBs + Historia edycji, 10 MiBs kopii roboczej. Jeśli spojrzysz na bardziej znaczące projekty crowd-sourcing, są tam znacznie wyższe liczby:

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

Oczywiście, dla takiej ilości danych/aktywności, takie podejście byłoby całkowicie nie do przyjęcia.

Ogólnie by to działało, gdyby można było używać przeglądarki internetowej jako "grubego" klienta, tzn. wystawiać operacje Gita i przechowywać prawie całą kasę po stronie klienta, a nie na serwerze bok.

Są też inne punkty, które przeoczyłem, ale nie są takie złe w porównaniu z pierwszym:

  • sam wzorzec posiadania "grubego" stanu edycji użytkownika jest kontrowersyjny pod względem normalnych ORM, takich jak ActiveRecord, Hibernate, DataMapper, Tower, itp.
  • tyle, ile Szukałem, nie ma żadnej wolnej bazy kodowej do robienia takiego podejścia do Gita z popularnych frameworków.
  • jest przynajmniej jedna usługa, która jakoś to potrafi efektywnie-to oczywiście github - ale, niestety, ich baza kodowa jest zamknięta i mocno podejrzewam, że nie używają zwykłych serwerów git / technik przechowywania repo wewnątrz, tzn. w zasadzie zaimplementowali alternatywny" big data " git.

Tak więc, bottom line: tojest możliwe, ale dla większości obecnych zastosowań nie będzie to optymalne rozwiązanie. Tworzenie własnej implementacji dokumentu-edycji-historii-do-SQL lub próba użycia dowolnej istniejącej Baza dokumentów byłaby prawdopodobnie lepszą alternatywą.

 45
Author: GreyCat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-12 04:43:07

Rzeczywiście ciekawe podejście. Powiedziałbym, że jeśli chcesz przechowywać dane, użyj bazy danych, a nie repozytorium kodu źródłowego, które jest przeznaczone do bardzo konkretnego zadania. Jeśli możesz użyć Git od zaraz, to jest w porządku, ale prawdopodobnie musisz zbudować na nim warstwę repozytorium dokumentów. Więc możesz zbudować ją na bazie tradycyjnej bazy danych, prawda? A jeśli interesuje Cię wbudowana Kontrola wersji, dlaczego nie użyć jednego z narzędzi open source repozytorium dokumentów ? Jest w czym wybierać.

Cóż, jeśli i tak zdecydujesz się na Git backend, to w zasadzie będzie on działał dla Twoich wymagań, jeśli zaimplementujesz go zgodnie z opisem. Ale:

1) wspomniałeś o "klastrze serwerów, które się pchają/ciągną" - myślałem o tym od jakiegoś czasu i nadal nie jestem pewien. Nie można naciskać/ciągnąć kilku repo jako operacji atomowej. Zastanawiam się, czy może być możliwość jakiegoś bałaganu scalania podczas jednoczesnej pracy.

2) może nie potrzebujesz to, ale oczywistą funkcjonalnością repozytorium dokumentów, którego nie wymieniłeś, jest kontrola dostępu. Możesz ograniczyć dostęp do niektórych ścieżek (=kategorii) za pomocą modułów podrzędnych, ale prawdopodobnie nie będziesz mógł łatwo przyznać dostępu na poziomie dokumentu.

 13
Author: Kombajn zbożowy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-23 22:43:46

Moje 2 pensy. Trochę tęskni, ale ...... Miałem podobny wymóg w jednym z moich projektów inkubacyjnych. Podobnie jak twoje, moje kluczowe wymagania, gdzie baza danych dokumentów (w moim przypadku xml), z wersjonowaniem dokumentów. Był to system dla wielu użytkowników z wieloma przypadkami współpracy. Preferowałem wykorzystanie dostępnych rozwiązań opensource, które obsługują większość kluczowych wymagań.

Aby przejść do sedna, nie mogłem znaleźć żadnego produktu, który pod warunkiem oba, w sposób wystarczająco skalowalny (liczba użytkowników, woluminy użycia, pamięć masowa i zasoby obliczeniowe).Byłem stronniczy w stosunku do Gita za wszystkie obiecujące możliwości i (prawdopodobne) rozwiązania, które można było z niego stworzyć. Gdy bawiłem się z opcją git, przejście z perspektywy pojedynczego użytkownika do perspektywy wielu (mili) użytkowników stało się oczywistym wyzwaniem. Niestety, nie udało mi się przeprowadzić istotnej analizy wydajności, tak jak ty. ( .. lazy / quit early ....dla wersji 2, mantra) moc dla Ciebie!. W każdym razie, mój tendencyjny pomysł przekształcił się w kolejną (wciąż tendencyjną ) alternatywę: siatkę narzędzi, które są najlepsze w swoich oddzielnych obszarach, bazach danych i kontroli wersji.

Póki jeszcze trwają prace ( ...i nieco zaniedbany) wersja morfowana jest po prostu taka .

  • na frontendzie: (userfacing ) użyj bazy danych na 1. poziomie storage (interfacing z aplikacjami użytkownika)
  • na zapleczu, użyj a system kontroli wersji (VCS) (jak git ) do wykonania wersjonowanie obiektów danych w bazie danych

W zasadzie byłoby to dodanie wtyczki kontroli wersji do bazy danych, z pewnym klejem integracyjnym, które być może trzeba będzie opracować, ale może być o wiele łatwiejsze.

Jak to (powinno ) działać jest to, że podstawowa wymiana danych interfejsu wielu użytkowników odbywa się za pośrednictwem bazy danych. DBMS zajmie się wszystkimi zabawnymi i złożonymi problemami, takimi jak multi-user, concurrency e, atomic operations itp. W backendzie VCS wykonywałyby kontrolę wersji na pojedynczym zestawie obiektów danych (bez współbieżności lub problemów z wieloma użytkownikami). Dla każdej efektywnej transakcji w bazie danych Kontrola wersji jest wykonywana tylko na rekordach danych, które zostałyby skutecznie zmienione.

Jeśli chodzi o klej interfejsowy, będzie on w formie prostej funkcji współdziałającej między bazą danych a VCS. Pod względem wzornictwa, jak proste podejście byłoby interfejsem sterowanym zdarzeniami, z aktualizacjami danych z bazy danych wyzwalającymi procedury kontroli wersji (podpowiedź: zakładając Mysql, użycie wyzwalaczy i sys_exec () bla bla...) .Pod względem złożoności implementacji będzie to zakres od prostego i skutecznego (np. skryptowanie) do skomplikowanego i wspaniałego (jakiś zaprogramowany interfejs złącza) . Wszystko zależy od tego, jak szalony chcesz iść z nim, i ile kapitału potu jesteś gotów wydać. Myślę, że proste skrypty powinny zadziałać. Aby uzyskać dostęp do wyniku końcowego, różnych wersji danych, prostą alternatywą jest wypełnienie klonu bazy danych (bardziej klon struktury bazy danych) danymi wskazywanymi przez tag/id/hash wersji w VCS. ponownie ten bit będzie prostym zapytaniem/tłumaczeniem / mapowaniem interfejsu.

Jest jeszcze kilka wyzwań i niewiadomych do rozwiązania, ale przypuszczam, że wpływ i znaczenie większość z nich w dużej mierze zależy od wymagań aplikacji i przypadków użycia. Niektóre mogą po prostu skończyć się nie problemy. Niektóre z problemów obejmują dopasowanie wydajności między kluczowymi modułami 2, bazą danych i VCS, dla aplikacji z aktywnością aktualizacji danych o wysokiej częstotliwości, skalowanie zasobów (pamięci masowej i mocy obliczeniowej ) w czasie po stronie git, a użytkownicy rosną: stały, wykładniczy lub ostatecznie plateau ' s

Z koktajlu powyżej, oto, co obecnie warzę

  • używanie Git dla VCS (początkowo uważane za stare dobre CVS ze względu na użycie tylko zestawów zmian lub delt pomiędzy 2 wersjami)
  • W związku z tym, że moje dane są przetwarzane w celu uzyskania dostępu do danych osobowych, nie są one przetwarzane w celach marketingowych.]}
  • pogrywanie z MongoDB (aby wypróbować bazę danych NoSQl, która jest ściśle zgodna z natywną strukturą bazy danych używaną w git )

Kilka ciekawostek - git rzeczywiście robi Wyczyść rzeczy, aby zoptymalizować przechowywanie, takie jak kompresja i przechowywanie tylko delt między rewizją obiektów - Tak, git przechowuje tylko changesets lub delty pomiędzy wersjami obiektów danych, gdzie to ma zastosowanie (wie kiedy i jak). Indeks: packfiles, deep in the guts of Git internals - Przegląd object storage Gita( content-addressable filesystem), pokazuje podobieństwa (z perspektywy koncepcji) z bazami noSQL takimi jak mongoDB. Ponownie, kosztem kapitału potu, może dostarczyć ciekawszych możliwości integracji 2 i ulepszania wydajności

Jeśli doszedłeś tak daleko, pozwól mi, czy powyższe może mieć zastosowanie do twojego przypadku, i zakładając, że tak będzie, jak będzie to dopasowane do niektórych aspektów w Twojej ostatniej kompleksowej analizie wydajności

 12
Author: young chisango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-30 18:16:33

Jak wspomniałeś, sprawa dla wielu użytkowników jest nieco trudniejsza w obsłudze. Jednym z możliwych rozwiązań byłoby użycie specyficznych dla użytkownika plików z indeksami Git w wyniku

  • nie ma potrzeby tworzenia oddzielnych kopii roboczych (użycie dysku ogranicza się do zmienionych plików)
  • nie ma potrzeby czasochłonnych prac przygotowawczych (na sesję użytkownika)
Sztuczka polega na połączeniu Gita GIT_INDEX_FILE zmienna środowiskowa z narzędziami do ręcznego tworzenia Git commits:

Następuje zarys rozwiązania (rzeczywiste skróty SHA1 pominięte w poleceniach):

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

W zależności od Twoich danych możesz użyć zadania cron, aby połączyć nowe refy z master, ale rozwiązanie konfliktu jest prawdopodobnie najtrudniejszą częścią tutaj.

Pomysły na ułatwienie są mile widziane.
 2
Author: 7mp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-20 14:47:05

Zaimplementowałem bibliotekę Ruby na górze libgit2, co sprawia, że jest to dość łatwe do zaimplementowania i zbadania. Istnieją pewne oczywiste ograniczenia, ale jest to również dość wyzwalający system, ponieważ otrzymujesz pełny łańcuch narzędzi git.

Dokumentacja zawiera kilka pomysłów dotyczących wydajności, kompromisów itp.

 2
Author: ioquatix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-10 09:15:14