Robię projekt, który zajmuje się ustrukturyzowaną bazą danych dokumentów. Mam drzewo kategorii (~ 1000 kategorii, do ~ 50 kategorii na każdym poziomie), każda kategoria zawiera kilka tysięcy (do, powiedzmy, ~ 10000) ustrukturyzowanych dokumentów. Każdy dokument to kilka kilobajtów danych w jakiejś uporządkowanej formie (wolałbym YAML, ale równie dobrze może to być JSON lub XML).
Użytkownicy tego systemu wykonują kilka rodzajów operacji:
- pobranie tych dokumentów za pomocą dowodu osobistego
- wyszukiwanie dokumentów według niektórych strukturalnych atrybutów w nich
- edycja dokumentów (tj. dodawanie / usuwanie / zmiana nazwy / łączenie); każda operacja edycji powinna być zarejestrowana jako transakcja z pewnym komentarzem
- przeglądanie historii zarejestrowanych zmian dla konkretnego dokumentu (w tym przeglądanie kto, kiedy i dlaczego zmienił dokument, pobieranie wcześniejszej wersji - i prawdopodobnie powrót do tej na żądanie)
Oczywiście tradycyjnym rozwiązaniem byłoby użycie jakiejś bazy danych dokumentów (takiej jak CouchDB lub Mongo) dla tego problemu - jednak ta kontrola wersji (historia) skusiła mnie do szalonego pomysłu - dlaczego nie miałbym używać git
repozytorium jako zaplecze bazy danych dla tej aplikacji?
Na pierwszy rzut oka można to rozwiązać w następujący sposób:
- kategoria = katalog, dokument = plik
- pobieranie dokumentu przez ID => zmiana katalogów + odczyt pliku w kopii roboczej
- edycja dokumentów z komentarzami do edycji => dokonywanie zatwierdzeń przez różnych użytkowników + przechowywanie wiadomości o zatwierdzeniach
- historia => normalny dziennik git i pobieranie starszych transakcji
- search => to trochę trudniejsza część, myślę, że wymagałoby to 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ś próbował już zaimplementować taki backend (np. Dla jakichkolwiek popularnych frameworków - RoR, node.js, Django, CakePHP)? Czy to rozwiązanie ma jakikolwiek możliwy wpływ na wydajność lub niezawodność - tj. Czy zostało udowodnione, że git będzie znacznie wolniejszy niż tradycyjne rozwiązania bazodanowe, czy też wystąpią jakiekolwiek pułapki związane ze skalowalnością / niezawodnością? Zakładam, że klaster takich serwerów, które wypychają / ściągają swoje repozytorium, powinien być dość solidny i niezawodny.
Zasadniczo powiedz mi, czy to rozwiązanie zadziała i dlaczego zadziała lub nie?
Odpowiedzi:
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 mieć zastosowania we wszystkich przypadkach, więc decyzja należy do architekta.
Ogólnie rzecz biorąc, pierwszą główną kwestią, której pomijam, jest to, że mam do czynienia z systemem wielu użytkowników, 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 muszę zachować stan dla nich wszystkich. Istnieje kilka podejść do tego, ale wszystkie z nich są albo zbyt trudne pod względem zasobów, albo zbyt skomplikowane, aby je zaimplementować (a tym samym w pewnym sensie zabijają pierwotny cel, jakim jest odciążenie wszystkich trudnych rzeczy implementacyjnych do gita):
Podejście „tępe”: 1 użytkownik = 1 stan = 1 pełna kopia robocza repozytorium, które serwer utrzymuje dla użytkownika. Nawet jeśli mówimy o dość małej bazie danych dokumentów (na przykład 100s MiBs) z ~ 100K użytkowników, utrzymanie pełnego klonu repozytorium dla wszystkich sprawia, że użycie dysku przebiega przez dach (tj. 100K użytkowników razy 100MiB ~ 10 TiB) . Co gorsza, klonowanie repozytorium 100 MB za każdym razem zajmuje kilka sekund, nawet jeśli odbywa się to w dość skuteczny sposób (tj. Nie jest używane przez git i rozpakowywanie-przepakowywanie), co jest nie do przyjęcia, IMO. A co gorsza - każda edycja, którą zastosujemy do głównego drzewa, powinna zostać ściągnięta do repozytorium każdego użytkownika, którym jest (1) zasób zasobów, (2) może prowadzić do nierozwiązanych konfliktów edycji w ogólnym przypadku.
Zasadniczo może być tak zły, jak O (liczba edycji × dane × liczba użytkowników) pod względem wykorzystania dysku, a takie użycie dysku automatycznie oznacza dość wysokie użycie procesora.
Podejście „Tylko aktywni użytkownicy”: zachowaj kopię roboczą tylko dla aktywnych użytkowników. W ten sposób generalnie nie przechowujesz pełnego repozytorium klonu na użytkownika, ale:
Zatem wykorzystanie dysku w tym przypadku osiąga wartość O (liczba edycji × dane × liczba aktywnych użytkowników), które jest zwykle ~ 100..1000 razy mniejsze niż liczba wszystkich użytkowników, ale powoduje to, że logowanie / wylogowanie jest bardziej skomplikowane i wolniejsze , ponieważ obejmuje klonowanie gałęzi na użytkownika przy każdym logowaniu i wycofywanie tych zmian przy wylogowaniu lub wygaśnięciu sesji (co powinno być wykonane transakcyjnie => dodaje kolejną warstwę złożoności). W liczbach bezwzględnych zmniejsza to 10 TiB wykorzystania dysku do 10-100 GiBs w moim przypadku, co może być akceptowalne, ale znowu mówimy teraz o dość małej bazie danych 100 MiB.
Podejście „rzadkie kasy”: robienie „rzadkich zakupów” zamiast pełnoprawnego klonowania repozytorium na jednego aktywnego użytkownika nie pomaga zbytnio. Może to zaoszczędzić ~ 10x wykorzystania miejsca na dysku, ale kosztem znacznie większego obciążenia procesora / dysku w przypadku operacji związanych z historią, co w pewnym sensie zabija cel.
Podejście „pula pracowników”: zamiast robić pełne klony za każdym razem dla osoby aktywnej, możemy przechowywać pulę klonów „pracujących”, gotowych do użycia. W ten sposób za każdym razem, gdy użytkownik się loguje, zajmuje jednego „pracownika”, wyciągając tam swoją gałąź z głównego repozytorium, a gdy się wylogowuje, uwalnia „pracownika”, który wykonuje sprytny twardy reset, aby ponownie stać się główny klon repozytorium, gotowy do użycia przez innego logującego się użytkownika. Nie pomaga zbytnio w korzystaniu z dysku (nadal jest dość wysoki - tylko pełny klon na aktywnego użytkownika), ale przynajmniej przyspiesza logowanie / wylogowywanie, ponieważ kosztuje jeszcze bardziej złożona.
To powiedziawszy, zwróć uwagę, że celowo obliczyłem liczby dość małej bazy danych i bazy użytkowników: 100 000 użytkowników, 1 000 aktywnych użytkowników, całkowita baza danych 100 MiB + historia zmian, 10 MiB kopii roboczej. Jeśli spojrzysz na bardziej znaczące projekty crowdsourcingowe, są tam znacznie wyższe liczby:
Oczywiście przy tak dużej ilości danych / działań takie podejście byłoby całkowicie nie do przyjęcia.
Generalnie by zadziałało, gdyby można było używać przeglądarki internetowej jako „grubego” klienta, tj. Wydawać operacje git i przechowywać prawie cały checkout po stronie klienta, a nie po stronie serwera.
Są też inne punkty, które przegapiłem, ale nie są one takie złe w porównaniu z pierwszym:
Tak, dolna linia : to jest możliwe, ale dla większości obecnych usecases nie będzie nigdzie w pobliżu rozwiązania optymalnego. Prawdopodobnie lepszą alternatywą byłoby wprowadzenie własnej implementacji historii edycji dokumentów do SQL lub próba użycia jakiejkolwiek istniejącej bazy danych dokumentów.
źródło
Rzeczywiście ciekawe podejście. Powiedziałbym, że jeśli potrzebujesz przechowywać dane, korzystaj z bazy danych, a nie z repozytorium kodu źródłowego, które jest przeznaczone do bardzo konkretnego zadania. Jeśli mógłbyś używać Gita od razu po wyjęciu z pudełka, to jest w porządku, ale prawdopodobnie musisz zbudować na nim warstwę repozytorium dokumentów. Możesz więc zbudować go również na tradycyjnej bazie danych, prawda? A jeśli interesuje Cię wbudowana kontrola wersji, dlaczego nie skorzystać po prostu z jednego z narzędzi do repozytorium dokumentów typu open source ? Jest w czym wybierać.
Cóż, jeśli mimo wszystko zdecydujesz się na backend Git, to w zasadzie działałoby to dla twoich wymagań, gdybyś zaimplementował go zgodnie z opisem. Ale:
1) Wspomniałeś o „klastrze serwerów, które wzajemnie się przepychają / ciągną” - myślałem o tym przez chwilę i nadal nie jestem pewien. Nie możesz push / pull kilku repozytoriów jako operacji atomowej. Zastanawiam się, czy podczas pracy równoległej może zaistnieć jakiś bałagan przy scalaniu.
2) Może tego nie potrzebujesz, ale oczywistą funkcją repozytorium dokumentów, którego nie wymieniłeś, jest kontrola dostępu. Możesz prawdopodobnie ograniczyć dostęp do niektórych ścieżek (= kategorii) za pośrednictwem podmodułów, ale prawdopodobnie nie będziesz w stanie łatwo przyznać dostępu na poziomie dokumentu.
źródło
moje 2 pensy warte. Trochę tęskniłem, ale ...... miałem podobne wymaganie w jednym z moich projektów inkubacyjnych. Podobnie jak twoje, moje kluczowe wymagania to baza danych dokumentów (w moim przypadku xml) z wersjonowaniem dokumentów. Było to dla systemu wielu użytkowników z wieloma przypadkami użycia współpracy. Wolałem korzystać z dostępnych rozwiązań open source, które obsługują większość kluczowych wymagań.
Aby przejść do sedna sprawy, nie mogłem znaleźć żadnego produktu, który zapewniałby oba, w sposób wystarczająco skalowalny (liczba użytkowników, wolumeny użytkowania, zasoby pamięci masowej i obliczeniowej). Byłem nastawiony na git dla wszystkich obiecujących możliwości, (prawdopodobne) rozwiązania, które można by z tego wymyślić. W miarę jak bawiłem się bardziej opcją git, przejście z perspektywy pojedynczego użytkownika do perspektywy wielu (miliardów) użytkowników stało się oczywistym wyzwaniem. Niestety, nie udało mi się przeprowadzić szczegółowej analizy wydajności, tak jak ty. (.. leniwy / zakończ wcześnie .... dla wersji 2, mantra) Moc dla ciebie !. W każdym razie, od tego czasu mój tendencyjny pomysł przekształcił się w następną (wciąż stronniczą) alternatywę: połączenie narzędzi, które są najlepsze w swoich oddzielnych sferach, bazach danych i kontroli wersji.
Podczas gdy wciąż trwają prace (... i trochę zaniedbane), wersja przekształcona jest po prostu taka.
Zasadniczo sprowadzałoby się to do dodania wtyczki kontroli wersji do bazy danych, z pewnym klejem integracyjnym, który być może będziesz musiał opracować, ale może być znacznie łatwiejszy.
Jak to (powinno) działać, polega na tym, że główna wymiana danych w interfejsie wielu użytkowników odbywa się za pośrednictwem bazy danych. DBMS poradzi sobie ze wszystkimi zabawnymi i złożonymi problemami, takimi jak wielu użytkowników, współbieżność, operacje atomowe itp. Na zapleczu VCS wykonywałby kontrolę wersji na jednym 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 faktycznie zmienione.
Jeśli chodzi o klej do łączenia, będzie on miał postać prostej funkcji współdziałania między bazą danych a VCS. Jeśli chodzi o projekt, prostym podejściem byłby interfejs sterowany zdarzeniami, z aktualizacjami danych z bazy danych wyzwalającymi procedury kontroli wersji (wskazówka: 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 efektywnego (np. skrypty) do złożonego i wspaniałego (jakiś programowany interfejs złącza). Wszystko zależy od tego, jak szalony chcesz z tym iść i ile potu z kapitału jesteś w stanie wydać. Myślę, że proste skrypty powinny wystarczyć. Aby uzyskać dostęp do wyników końcowych, różnych wersji danych, prostą alternatywą jest wypełnienie klonu bazy danych (bardziej klonu struktury bazy danych) danymi, do których odwołuje się znacznik wersji / identyfikator / hash w VCS. znowu ten bit będzie prostym zadaniem zapytania / tłumaczenia / mapowania interfejsu.
Nadal istnieje kilka wyzwań i niewiadomych, z którymi trzeba się zmierzyć, ale przypuszczam, że wpływ i znaczenie większości z nich będą w dużej mierze zależeć od wymagań aplikacji i przypadków użycia. Niektóre mogą po prostu nie być problemem. Niektóre z problemów obejmują dopasowanie wydajności między 2 kluczowymi modułami, bazą danych i VCS, dla aplikacji z aktywnością aktualizacji danych o wysokiej częstotliwości, skalowanie zasobów (pamięci i mocy obliczeniowej) w czasie po stronie git jako dane i użytkownicy wzrost: stały, wykładniczy lub ostatecznie plateau
Z powyższego koktajlu, oto, co obecnie warzę
Kilka zabawnych faktów - git faktycznie robi jasne rzeczy w celu optymalizacji pamięci, takie jak kompresja i przechowywanie tylko różnic między wersjami obiektów - TAK, git przechowuje tylko zestawy zmian lub delty między wersjami obiektów danych, gdzie ma to zastosowanie (wie kiedy i jak) . Odniesienie: pliki packfiles, głęboko w wnętrznościach Gita - Przegląd obiektowej pamięci masowej gita (system plików z adresowaniem treści) pokazuje uderzające podobieństwa (z punktu widzenia koncepcji) z bazami danych noSQL, takimi jak mongoDB. Ponownie, kosztem dużego wysiłku, może zapewnić bardziej interesujące możliwości integracji 2 i poprawienia wydajności
Jeśli dotarłeś tak daleko, pozwól mi, czy powyższe może mieć zastosowanie w twoim przypadku i zakładając, że tak będzie, jak to wyrównałoby się z niektórymi aspektami w twojej ostatniej kompleksowej analizie wydajności
źródło
I wdrożone bibliotekę Ruby na górze
libgit2
, która sprawia, że to dość łatwe do wdrożenia i zbadać. Istnieją pewne oczywiste ograniczenia, ale jest to również dość wyzwalający system, ponieważ masz pełny zestaw narzędzi git.Dokumentacja zawiera kilka pomysłów dotyczących wydajności, kompromisów itp.
źródło
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 plików indeksu Git specyficznych dla użytkownika, w wyniku czego
Sztuczka polega na połączeniu
GIT_INDEX_FILE
zmiennej środowiskowej Git z narzędziami do ręcznego tworzenia zatwierdzeń Git:Poniżej przedstawiono zarys rozwiązania (faktyczne skróty SHA1 pominięte w poleceniach):
W zależności od danych możesz użyć zadania crona do scalenia nowych referencji,
master
ale rozwiązanie konfliktu jest prawdopodobnie najtrudniejszą częścią tutaj.Mile widziane są pomysły na ułatwienie.
źródło