Dokument zorientowany
W tradycyjnej relacyjnej bazie danych użytkownik rozpoczyna od określenia serii typów kolumn i nazw dla tabeli. Informacje są następnie dodawane jako wiersze wartości, a każda z tych nazwanych kolumn jest komórką każdego wiersza. Nie można podać dodatkowych wartości, które nie zostały określone podczas tworzenia tabeli, a każda wartość musi być obecna, nawet jeśli jest to wartość NULL. Przechowywanie dokumentów zamiast tego umożliwia wpisanie każdego rekordu jako serii nazw wraz z powiązanymi wartościami, które można zobrazować jako obiekt JavaScript, słownik Python lub mieszanie Ruby. Nie określasz z wyprzedzeniem, jakie nazwy będą występować w każdej tabeli przy użyciu schematu. Teoretycznie każdy rekord może zawierać zupełnie inny zestaw nazwanych wartości, chociaż w praktyce warstwa aplikacji często opiera się na nieformalnym schemacie, a kod klienta oczekuje obecności określonych nazwanych wartości. Główną zaletą tego zorientowanego na dokumenty podejścia jest jego elastyczność. Możesz dodawać lub usuwać równoważniki kolumn bez żadnych kar, o ile warstwa aplikacji nie opiera się na usuniętych wartościach. Dobrą analogią jest różnica między językami, w których deklarowane są typy zmiennych z wyprzedzeniem, a tymi, w których typ jest wywnioskowany przez kompilator lub interpreter. Tracisz informacje, które można wykorzystać do automatycznego sprawdzania poprawności i optymalizacji pod względem wydajności, ale prototypowanie i eksperymentowanie staje się o wiele łatwiejsze.
Przechowywanie kluczy / wartości
System memcached wprowadził wielu programistów internetowych do mocy przetwarzania magazynu danych, takiego jak gigantyczna tablica asocjacyjna, odczytywania i zapisywania wartości opartych wyłącznie na unikalnym kluczu. Prowadzi to do bardzo prostego interfejsu, z trzema prymitywnymi operacjami, aby uzyskać dane powiązane z określonym kluczem, do przechowywania niektórych danych na kluczu oraz do usunięcia klucza i jego danych. W przeciwieństwie do relacyjnych baz danych, z czystym magazynem kluczy / wartości, nie można uruchamiać zapytań, choć niektóre mogą oferować rozszerzenia, takie jak możliwość znalezienia wszystkich kluczy pasujących do wyrażenia z dziką kartą. Oznacza to, że kod aplikacji musi obsługiwać wszelkie skomplikowane operacje poza prymitywnymi wywołaniami, które może wykonać w sklepie. Dlaczego każdy programista chce wykonać tę dodatkową pracę? W przypadku bardziej złożonych baz danych często płacisz karę za złożoność lub wydajność w przypadku funkcji, na które możesz nie zwrócić uwagi, takich jak pełna zgodność z ACID. Dzięki magazynom kluczy / wartości otrzymujesz bardzo podstawowe elementy składowe, które mają bardzo przewidywalne właściwości wydajności i możesz tworzyć bardziej złożone operacje przy użyciu tego samego języka, co reszta aplikacji. Wiele wymienionych tu baz danych stara się zachować prostotę interfejsu czystych kluczy / wartości, ale z dodatkowymi funkcjami dodanymi w celu spełnienia typowych wymagań. Wydaje się prawdopodobne, że istnieje słodka cecha funkcjonalności, która zachowuje niektóre zalety minimalnych sklepów kluczowych / wartościowych, nie wymagając od twórcy aplikacji tyle samo powielonego wysiłku.
Skalowanie poziome lub pionowe
Tradycyjne architektury baz danych są zaprojektowane tak, aby działały dobrze na jednym komputerze, a najprostszym sposobem obsługi większych operacji jest uaktualnienie maszyny przy użyciu szybszego procesora lub większej ilości pamięci. Takie podejście do zwiększania prędkości jest znane jako skalowanie pionowe. Nowsze systemy przetwarzania danych, takie jak Hadoop i Cassandra, są zaprojektowane do pracy na klastrach serwerów o stosunkowo niskiej specyfikacji, a więc najprostszym sposobem na obsłużenie większej ilości danych jest dodanie większej liczby tych maszyn do klastra. Takie horyzontalne podejście do skalowania wydaje się być tańsze w miarę wzrostu liczby operacji i rozmiaru danych, a bardzo duże potoki przetwarzania danych są zbudowane na modelu poziomym. Koszt tego podejścia jest jednak kosztowny. Pisanie kodu obsługi rozproszonych danych jest trudne i wymaga kompromisów między szybkością, skalowalnością, odpornością na uszkodzenia i tradycyjnymi celami bazy danych, takimi jak atomowość i spójność.
MapReduce
MapReduce to wzorzec projektowy algorytmu, który powstał w funkcjonalnym świecie programowania. Składa się z trzech kroków. Najpierw piszesz funkcję mapowania lub skrypt, który przechodzi przez twoje dane wejściowe i wypisuje serię kluczy i wartości do użycia przy obliczaniu wyników. Klucze służą do grupowania razem bitów danych, które będą potrzebne do obliczenia pojedynczego wyniku wyjściowego. Nieuporządkowana lista kluczy i wartości jest następnie poddawana krokowi sortowania, który zapewnia, że wszystkie fragmenty mające ten sam klucz znajdują się obok siebie w pliku. Etap reduktora przechodzi następnie przez sortowane wyjście i odbiera wszystkie wartości, które mają ten sam klucz w sąsiednim bloku. To może brzmieć jak bardzo okrężny sposób budowania twoich algorytmów, ale jego główną zaletą jest to, że usuwa nieplanowane losowe dostępy, z całym rozproszeniem i zbieraniem przetwarzanym w fazie sortowania. Nawet na pojedynczych urządzeniach zwiększa to wydajność, dzięki zwiększonemu dostępowi do pamięci, ale także pozwala na łatwe dzielenie procesu na dużą liczbę maszyn, radząc sobie z danymi wejściowymi w wielu niezależnych porcjach i dzieląc dane na podstawie klucz. Hadoop jest najbardziej znanym publicznym systemem do uruchamiania algorytmów MapReduce, ale wiele nowoczesnych baz danych, takich jak MongoDB, również obsługuje je jako opcję. Warto nawet w dość tradycyjnym systemie, ponieważ jeśli umiesz napisać zapytanie w formularzu MapReduce, będziesz mógł go wydajnie uruchomić na tylu maszynach, jakie masz.
Sharding
Każda baza danych rozproszona na wielu komputerach wymaga pewnego schematu, aby zdecydować, na których maszynach należy przechowywać dane dane. System odłamywania decyduje o tym dla każdego wiersza w tabeli, używając jego klucza. W najprostszym przypadku programista aplikacji określi jawną regułę do wykorzystania w przypadku shardingu. Na przykład, jeśli dysponujesz 10 klastrem maszynowym i kluczem numerycznym, możesz użyć ostatniej cyfry dziesiętnej klucza, aby zdecydować, na którym komputerze chcesz przechowywać dane. Ponieważ zarówno kod przechowywania i pobierania wie o tej zasadzie, gdy trzeba uzyskać wiersz, można przejść bezpośrednio do maszyny, która go posiada. Największym problemem z shardingiem jest równomierne rozłożenie danych między maszynami i radzenie sobie ze zmianami rozmiaru klastra. Na tym samym przykładzie wyobraź sobie, że klawisze numeryczne często kończą się na zero; doprowadzi to do wyjątkowo niezrównoważonej dystrybucji, w której pojedyncza maszyna jest nadużywana i staje się wąskim gardłem. Jeśli rozmiar klastra zostanie rozszerzony z dziesięciu do piętnastu maszyn, możemy przełączyć się na schemat piętnastki modulo do przypisywania danych, ale będzie to wymagało hurtowego tasowania wszystkich danych w klastrze. Aby złagodzić ból tych problemów, do podziału danych stosuje się bardziej złożone schematy. Niektóre z nich polegają na centralnym katalogu, w którym znajdują się lokalizacje poszczególnych kluczy. Ten poziom pośredni umożliwia przenoszenie danych pomiędzy komputerami, gdy dany fragment staje się zbyt duży (aby przywrócić równowagę dystrybucji), kosztem konieczności dodatkowego wyszukiwania w katalogu dla każdej operacji. Informacje zawarte w katalogu są zwykle dość małe i dość statyczne, więc jest to dobry kandydat do lokalnego buforowania, o ile zauważane są rzadkie zmiany. Innym popularnym podejściem jest użycie spójnego hashu do shardowania. Ta technika używa małej tabeli dzielącej możliwy zakres wartości skrótu na zakresy, z których jeden jest przypisany do każdego fragmentu. Dane wyszukiwania wymagane przez klientów są wyjątkowo lekkie, z zaledwie kilkoma wartościami liczbowymi na węzeł, dzięki czemu można je udostępniać i buforować w sposób wydajny, ale ma on wystarczającą elastyczność, aby umożliwić szybkie ponowne równoważenie dystrybucji wartości po dodaniu i usunięciu węzłów, lub nawet gdy jeden węzeł jest przeciążony, w przeciwieństwie do ustalonych funkcji modulo.