Nasza strona używa cookies. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Rozproszone generowanie identyfikatorów

Avik Das Software Engineer / LinkedIn
Poznaj kilka sposobów na skuteczniejsze generowanie identyfikatorów.
Rozproszone generowanie identyfikatorów

Praca przy skalowaniu nauczyła mnie kilku dobrych sztuczek. Podzielę się niektórymi z nich w tym artykule. Na początek porozmawiajmy o koncepcji, która pojawia się, za każdym razem, gdy Twoje serwery są rozproszone w wielu lokalizacjach: rozproszone generowanie identyfikatorów.


Automatycznie inkrementowane identyfikatory

Powszechną koncepcją w schematach baz danych jest automatycznie inkrementowany klucz główny. Załóżmy, że Twoi użytkownicy publikują gdzieś swoje wpisy, a każdy z nich ma swój własny wpis w bazie danych. Standardowym sposobem na modelowanie postów byłaby tabela postów z następującymi kolumnami:

  • ID — unikalny identyfikator dla każdego posta
  • Autor — klucz obcy do tabeli użytkowników
  • Inne pola, które będą służyć do modelowania treści


Ważne jest to, że identyfikator jest unikalny dla każdego — będziesz go używać do odwoływania się do danego posta (na przykład w adresie URL). Jak więc egzekwować unikalność klucza?

Jeśli masz tylko jeden serwer bazy danych i jeden wątek zapisujący w bazie danych, nie będzie to trudne. Liczby całkowite (zaczynając od 0) mogą być Twoimi identyfikatorami. Za każdym razem, gdy tworzysz nowy post, sprawdź identyfikator o najwyższym numerze i przypisz nowemu wpisowi identyfikator o jeden wyższy.

Problem pojawia się w momencie, gdy masz wiele wątków. Dwa wątki czytają ten sam identyfikator o najwyższym numerze, następnie inny wątek zapisuje następny identyfikator, a na koniec jeszcze inny wątek zapisuje ten sam identyfikator! Jest to klasyczny wyścig, a w naszym scenariuszu z pojedynczą maszyną można go rozwiązać za pomocą blokady.

Blokada zapewnia nam to, że operacja odczytu i zapisu ID jest atomowa, i żaden wątek nie wskoczy w środek tego procesu.

Bazy danych są w tym dobre i identyfikatorem może być automatycznie zwiększająca się liczba całkowita. Baza danych wewnętrznie zadba o blokowanie w potrzebnych momentach.


Skalowanie do wielu magazynów danych

Automatycznie inkrementowany identyfikator działa dobrze, o ile masz jedną bazę danych (nawet przy wielu serwerach aplikacji zapisujących do tej bazy). W końcu to baza danych generuje identyfikator, a mamy tylko jedną. Chciałbym jednak zauważyć, że architektura z jedną bazą danych pozwala na naprawdę wiele, więc nie próbuj przedwcześnie skalować!

Problem pojawia się w momencie, gdy baza danych stanie się na tyle duża, że chcesz ją rozdzielić na wiele komputerów. Każda baza danych będzie wtedy miała swój własny zestaw identyfikatorów, które się na siebie nałożą. Jeśli teraz weźmiesz pod uwagę wszystkie posty w każdej bazie danych jako jedną dużą kolekcję postów, nie będziesz wiedział, jaki post ma jaki identyfikator. Aby naprawdę oddzielić swoje identyfikatory, masz kilka opcji.


Scentralizowane zapisy

Trzy serwery aplikacji piszą do jednej podstawowej bazy danych. Dane są następnie powielane do replik do odczytu, dzięki czemu można je wykorzystać.

Nawet w przypadku wielu baz danych możesz mieć tylko jedną przeznaczoną do zapisu. Często jest to implementowane w parze z replikami do odczytu, które służą od rozłożenia ruchu, który wymaga tylko czytania.

Wadą jest to, że to pojedyncze wąskie gardło oraz potencjał wystąpienia opóźnień replikacji. Jednak jeżeli czytasz znacznie więcej, niż zapisujesz, to taka architektura może dobrze się sprawdzić.


Scentralizowane tworzenie identyfikatorów

Bez względu na to, do której bazy danych zapisujemy, używamy jednej usługi do generowania identyfikatorów. Piszesz do dowolnej bazy danych, ale zanim to zrobisz, korzystasz z oddzielnej usługi, aby dostać identyfikator nowego posta.

Po zaklepaniu identyfikatora żaden inny post nie będzie mógł go używać, nawet jeśli post, dla którego był przeznaczony identyfikator, będzie miał opóźnienie na zapisie lub pojawi się jakiś błąd po wygenerowaniu ID. Chociaż pozwala to na posiadanie wielu baz danych, do których można zapisywać, to nadal masz wąskie gardło i dodatkową usługę do zarządzania na backendzie.


Wybieraj losowo z szerszego zakresu

Zamiast używać ściśle rosnących identyfikatorów, wybierz losowy identyfikator z szerszego zakresu. Sztuczka polega na tym, żeby zasięg był bardzo duży. Najczęstszym przykładem jest uniwersalnie unikalny identyfikator (UUID), który jest 128-bitową liczbą. Oznacza to, że wybierasz spośród 2128 liczb.

Z tak dużym wachlarzem możliwości i odpowiednią techniką tworzenia UUID, unikalny identyfikator masz jak w banku. 

Minusem jest to, że identyfikatory są duże i nie następują w kolejności (chociaż brak sekwencji może być dobry dla bezpieczeństwa).


Zakoduj partycję w identyfikatorze

Identyfikator obsługujący partycje składa się z dwóch części: tej, która jest unikalna dla każdej partycji, i reszty, która musi być unikalna jedynie w obrębie tej jednej partycji.

Jeśli nie chcesz tak długich identyfikatorów (jednym przykładem jest generowanie identyfikatorów, które można łatwo zapamiętać), możesz zakodować partycję w identyfikatorze.

Nadaj każdej bazie danych unikalny prefiks, na przykład 00 i 01. Nasza baza zawsze będzie generować identyfikator z takim prefiksem. Każda baza danych może generować identyfikatory sekwencyjnie, dołączając następnie prefiks. Żadna inna baza danych nie uzyskałaby wtedy tego samego końcowego identyfikatora.

Minusem jest to, że jeśli masz tylko kilka baz, to możesz zmarnować kilka cyfr swojego identyfikatora. Możesz mieć wrażenie, że zapobiega to usuwaniu lub zmianie rozmiaru partycji, ale jeśli upewnisz się, że każda partycja może odczytać dowolne dane, prawdopodobnie nie będziesz mieć problemów.


Semantyczne identyfikatory

Ostatnią opcją jest użycie samych danych jako identyfikatora. Może się to wydawać bezsensowne dla posta, ale pomyśl o jego like’ach.

Można użyć kombinacji ID posta i like'ującego użytkownika (wygenerowanego przy użyciu jednej z powyższych metod) jako ID dla like'ów.

Ponieważ jeden użytkownik może polubić konkretny post tylko raz, ten złożony identyfikator jest z pewnością unikalny dla każdego like'a w systemie.


Skalowanie geograficzne

Skalowalność staje się prawdziwym problemem, gdy masz wiele centrów danych w różnych lokalizacjach. Wywołania między tymi centrami, które byłyby wymagane do centralizacji dowolnej części architektury, stają się naprawdę kosztowne.

Na szczęście z pomocą przychodzi tutaj zdecentralizowane podejście, które pomaga unikać kolizji między centrami danych, przy jednoczesnym scentralizowanym podejściu w ramach jednego centrum danych.

Zapewnienie unikalnych identyfikatorów jest proste w przypadku pojedynczej bazy danych, ale w miarę skalowania się Twojej aplikacji musisz o nich myśleć inaczej. Możesz albo znaleźć sposób generowania identyfikatorów w sposób scentralizowany, albo wygenerować je tak, aby identyfikatory każdej bazy danych nie kolidowały z innymi.



Oryginał tekstu w języku angielskim przeczytasz tutaj.

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 160 tysiącami naszych czytelników

Dowiedz się więcej