← Blog
Architektura16 min czytania

System design — zestaw materiałów o projektowaniu systemów

Czterdzieści jeden tysięcy gwiazdek przy zestawieniu linków do nauki projektowania systemów — i dwadzieścia dwie referencyjne implementacje algorytmów, o których mało kto wie, że tam są. Sprawdzamy, co w tym repozytorium jest realnie wartościowe, co jest lejem sprzedażowym do płatnej platformy autora, i które z dziewięćdziesięciu opisanych pojęć zwracają się w pracy agencyjnej, a które są teatrem rekrutacyjnym.

Zestawienia typu „awesome" mają w naszej branży złą prasę i zwykle zasłużoną: lista linków, tysiąc gwiazdek, dwa lata bez aktualizacji, połowa adresów martwa. Kiedy jednak takie repozytorium zbiera ponad czterdzieści tysięcy gwiazdek, warto sprawdzić, co dokładnie ludzie gwiazdkują — i czy jest tam coś poza listą.

Awesome System Design Resources (github.com/ashishps1/awesome-system-design-resources) jest dobrym materiałem na taką sekcję zwłok, bo ma trzy warstwy o zupełnie różnej wartości: uporządkowaną mapę pojęć, kilkadziesiąt linków do treści zewnętrznych o dużej wartości — i dwadzieścia dwie referencyjne implementacje algorytmów, o których istnieniu nie wie większość osób, które to repozytorium mają w zakładkach. Do tego jedną warstwę, o której trzeba powiedzieć wprost: lej sprzedażowy do płatnej platformy autora.

Przejdziemy je po kolei, a na końcu odpowiemy na pytanie, które w naszej pracy jest jedyne istotne: które z tych pojęć realnie zwracają się w projektach agencyjnych, a które są teatrem rozmów kwalifikacyjnych do wielkiego tech-u.

Stan projektu

Dane z API GitHuba na 29 września 2026:

  • 41 274 gwiazdki i 8710 forków — jedno z najpopularniejszych repozytoriów w całym naszym planie,
  • utworzone 25 października 2023, ostatni push 16 lutego 2026 — czyli od siedmiu miesięcy nie ruszone,
  • 16 otwartych zgłoszeń, licencja GPL-3.0,
  • 149 commitów autora na cztery osoby w zestawieniu kontrybutorów — projekt jednej osoby, Ashisha Prataba Singha,
  • strona domowa wskazana w repozytorium to algomaster.io, czyli komercyjna platforma kursowa tego samego autora.

Siedem miesięcy bez commita to przy narzędziu zwykle sygnał ostrzegawczy, ale przy zestawieniu linków znaczy coś innego i bardziej konkretnego: ryzyko gnicia odnośników. Nikt nie sprawdza, czy blog, do którego prowadzi link, jeszcze istnieje i czy artykuł nie został przeniesiony. Przy dwustu linkach po siedmiu miesiącach część na pewno wymaga wyszukania tytułu w wyszukiwarce, zamiast kliknięcia.

Warstwa pierwsza: mapa pojęć

Najbardziej użyteczną rzeczą w tym repozytorium jest sama struktura — dziewięć obszarów, w każdym od trzech do dziewięciu pojęć, ułożonych od podstaw do kompromisów:

  • Pojęcia podstawowe — skalowalność, dostępność, niezawodność, pojedynczy punkt awarii, opóźnienie kontra przepustowość kontra przepustowość pasma, spójne haszowanie, twierdzenie CAP, przełączanie awaryjne, odporność na awarie,
  • Podstawy sieci — model OSI, adresy IP, DNS, proxy kontra odwrotne proxy, HTTP/HTTPS, TCP kontra UDP, równoważenie obciążenia, sumy kontrolne,
  • Podstawy API — API, brama API, REST kontra GraphQL, WebSockets, webhooki, idempotentność, ograniczanie tempa żądań, projektowanie API,
  • Podstawy baz danych — transakcje ACID, SQL kontra NoSQL, indeksy, szardowanie, replikacja, skalowanie, typy baz, filtry Blooma,
  • Cache — czym jest, strategie, polityki wywłaszczania, cache rozproszony, CDN,
  • Komunikacja asynchroniczna — publikuj/subskrybuj, kolejki komunikatów, przechwytywanie zmian danych (CDC),
  • Systemy rozproszone i mikroserwisy — sygnały życia, odnajdywanie usług, algorytmy konsensusu, blokady rozproszone, protokół plotkowania, bezpiecznik, odtwarzanie po awarii, śledzenie rozproszone,
  • Wzorce architektoniczne — klient-serwer, mikroserwisy, bezserwerowe, zdarzeniowe, peer-to-peer,
  • Kompromisy — czternaście par typu skalowanie w pionie kontra w poziomie, współbieżność kontra równoległość, długie odpytywanie kontra WebSockets, przetwarzanie wsadowe kontra strumieniowe, stanowe kontra bezstanowe, spójność silna kontra ostateczna.

Ta lista ma wartość niezależną od linków, do których prowadzi. Jest przydatna jako lista kontrolna luk we własnej wiedzy — przejście po niej i szczere oznaczenie, których pojęć nie umiałbym wytłumaczyć kolegom, zajmuje kwadrans i daje konkretny plan czytania. Warto ją też przejrzeć przed rozmową o architekturze z klientem: nie po to, żeby użyć tych terminów, a po to, żeby wiedzieć, których pytań nie zadałeś.

Do tego dochodzi sekcja z frameworkiem odpowiadania na zadania projektowe na rozmowie oraz około pięćdziesięciu zadań podzielonych na trzy poziomy — od skracacza linków i równoważnika obciążenia, przez WhatsAppa, Instagrama i rozproszony planista zadań, do Ubera, Google Docs i rozproszonego crawlera.

Warstwa druga: linki, które są warte kliknięcia niezależnie od reszty

Dwie sekcje na końcu README są tą częścią, dla której warto to repozytorium zapisać, i obie są całkowicie wolne od promocji czegokolwiek.

Dziesięć klasycznych prac o systemach rozproszonych, z linkami do plików PDF bezpośrednio u źródeł: Paxos: The Part-Time Parliament Lamporta, MapReduce, The Google File System, Dynamo Amazona, praca o Kafce, Spanner, Bigtable, ZooKeeper, The Log-Structured Merge-Tree i Chubby. To jest kanon i nie zmieni się przez najbliższą dekadę — a fakt, że ktoś zebrał wszystkie dziesięć z aktualnymi adresami w jednym miejscu, oszczędza godzinę szukania.

Sześć artykułów inżynierskich z firm, które opisują własne wpadki i rozwiązania. Ta selekcja jest lepsza, niż sugeruje jej rozmiar — każdy z tych tekstów opisuje problem, który w mniejszej skali spotyka i nas:

  • jak Discord przechowuje biliony wiadomości,
  • jak Netflix zbudował wyszukiwanie wewnątrz materiałów wideo,
  • jak Canva wyskalowała wysyłanie plików od zera do pięćdziesięciu milionów dziennie,
  • jak Airbnb unika podwójnych płatności w rozproszonym systemie płatniczym,
  • pierwsze dziesięć lat API płatniczego Stripe'a,
  • komunikacja w czasie rzeczywistym w Slacku.

Ten czwarty przeczytałbym na miejscu każdego, kto kiedykolwiek integrował bramkę płatniczą — problem podwójnego obciążenia przy ponowionym żądaniu jest identyczny w sklepie na Laravelu i w Airbnb, różni się tylko liczbą zer w konsekwencjach.

Jest też lista siedmiu kanałów wideo (Gaurav Sen, ByteByteGo, codeKarle i inne) oraz jedna pozycja książkowa: Designing Data-Intensive Applications. Jedna, i słusznie — gdyby ta lista miała dwadzieścia pozycji, nikt by z niej nie skorzystał.

Warstwa trzecia: dwadzieścia dwie implementacje, o których nikt nie wie

To jest odkrycie tego researchu. README o tym nie wspomina ani słowem, ale w repozytorium jest katalog implementations/ z dwudziestoma dwoma plikami kodu w Javie i Pythonie, po jednej implementacji na algorytm w każdym języku:

  • Ograniczanie tempa żądań (pięć algorytmów): kubełek z żetonami, cieknący kubełek, licznik stałego okna, dziennik okna przesuwnego, licznik okna przesuwnego,
  • Równoważenie obciążenia (pięć algorytmów): karuzelowe, karuzelowe z wagami, najmniej połączeń, najkrótszy czas odpowiedzi, haszowanie adresu IP,
  • Spójne haszowanie — jedna implementacja, najdłuższy plik w zestawie.

Kod jest krótki i czytelny — pliki mają od pięciuset bajtów do trzech kilobajtów. Wartość jest dokładnie w tej zwięzłości: różnicę między dziennikiem okna przesuwnego a licznikiem okna przesuwnego łatwiej zrozumieć z dwudziestu linii niż z akapitu prozy. Licznik okna przesuwnego robi rzecz, którą warto zobaczyć na oczy — waży licznik z okna poprzedniego proporcjonalnie do tego, ile z aktualnego okna już minęło:

window_elapsed = (now % self.window_size) / self.window_size
threshold = self.previous_count * (1 - window_elapsed) + self.request_count

if threshold < self.max_requests:
    self.request_count += 1
    return True
return False

Trzy linie i cały pomysł jest jasny: przybliżamy prawdziwe okno przesuwne, nie trzymając znacznika czasu każdego żądania. To jest kompromis pamięć kontra dokładność, wyrażony kodem.

A teraz zastrzeżenie, bez którego ta sekcja byłaby szkodliwa. To są implementacje dydaktyczne i nie wolno ich przenieść do produkcji bez przemyślenia. Kubełek z żetonami trzyma stan w polach obiektu, w pamięci jednego procesu, bez żadnej synchronizacji:

self.tokens = min(self.capacity, self.tokens + time_passed * self.fill_rate)
self.last_time = now
if self.tokens >= tokens:
    self.tokens -= tokens
    return True

Odczyt, obliczenie i zapis są trzema krokami, więc dwa równoległe żądania mogą przeczytać ten sam stan i oba przejść. W naszym świecie znaczy to konkretnie tyle: taki limiter za pulą PHP-FPM z dwudziestoma procesami daje dwadzieścia niezależnych limitów, a przy Octane utrzymującym stan między żądaniami — limit, który wycieka między żądaniami w sposób zależny od tego, który worker obsłużył żądanie.

Produkcyjnie chcemy stanu wspólnego i operacji niepodzielnej: w Laravelu RateLimiter ze sterownikiem Redis, a przy naprawdę wysokim ruchu — skrypt Lua wykonujący cały algorytm po stronie Redisa albo limit_req w nginksie przed aplikacją. Implementacje z tego repozytorium są mapą algorytmu, nie gotowym komponentem.

Równoważenie obciążenia: co widać po uruchomieniu kodu

Pięć algorytmów równoważenia to ta część, którą warto nie tylko przeczytać, ale i odpalić — bo dopiero wtedy widać rzeczy, których żadna proza nie pokaże. Uruchomiłem wariant karuzelowy z wagami dla wag [5, 1, 1] i sekwencja wygląda tak:

Server1, Server1, Server1, Server1, Server1, Server2, Server3,
Server1, Server1, Server1, Server1, Server1, Server2, Server3, ...

Proporcje są prawidłowe — pięć do jednego do jednego w cyklu siedmiu żądań. Ale rozkład jest seryjny, nie wygładzony: pierwszy serwer dostaje pięć żądań pod rząd, potem po jednym trafia do dwóch pozostałych. Przy krótkich żądaniach nie ma to znaczenia, przy długich — pierwszy serwer ma w danym momencie pięć równoległych połączeń, a pozostałe zero. Dokładnie z tego powodu nginx implementuje smooth weighted round-robin, który te same proporcje rozkłada równomiernie w czasie. Zobaczenie różnicy na siedmiu linijkach wyjścia jest lepszym wytłumaczeniem, niż przeczytanie o niej w dokumentacji.

Drugi wariant, najkrótszy czas odpowiedzi, ma w tej implementacji cechę, którą trzeba nazwać, bo jest pouczająca sama w sobie: przechowuje ostatni zmierzony czas odpowiedzi każdego serwera, a nie średnią ani medianę. Konsekwencja jest nieintuicyjna i w produkcji groźna — serwer, który szybko zwraca błąd, staje się najbardziej atrakcyjnym celem, bo jego „czas odpowiedzi" jest najkrótszy w całej puli. Prawdziwy równoważnik musi więc albo liczyć okno czasowe, albo łączyć czas odpowiedzi z kontrolą stanu zdrowia. Ten plik pokazuje algorytm; pokazuje też, dlaczego sam algorytm nie wystarczy.

Spójne haszowanie z węzłami wirtualnymi

Najdłuższy plik w zestawie jest jednocześnie najlepszym. Implementacja pierścienia haszującego robi rzecz, którą większość opisów pomija albo wspomina jednym zdaniem: węzły wirtualne. Każdy fizyczny serwer jest haszowany kilka razy pod nazwą serwer-0, serwer-1 i tak dalej, więc trafia na pierścień w kilku miejscach:

for i in range(self.num_replicas):
    hash_val = self._hash(f"{server}-{i}")
    self.ring[hash_val] = server
    bisect.insort(self.sorted_keys, hash_val)

Bez tego zabiegu trzy serwery dzielą pierścień na trzy dowolnie nierówne łuki i jeden dostaje połowę ruchu. Wyszukiwanie serwera dla klucza to bisect po posortowanej liście pozycji z zawinięciem modulo na początek pierścienia — czyli ruch zgodnie z ruchem wskazówek zegara do najbliższego węzła, w czasie logarytmicznym.

Dwa zastrzeżenia do tego pliku, oba warte zapamiętania przy własnej implementacji. Domyślna liczba węzłów wirtualnych to trzy, czyli za mało na równy rozkład — produkcyjne implementacje używają zwykle stu do dwustu na serwer. I hasz to MD5, co do rozkładu kluczy jest zupełnie wystarczające, ale gdyby ktoś kiedyś chciał ten sam kod użyć do czegokolwiek związanego z bezpieczeństwem — nie wolno.

Przy okazji dwie drobne niechlujności, które mówią coś o skali dbałości: jeden z plików nazywa się round_robin.py.py, a w liście zadań rekrutacyjnych pozycja „Design Autocomplete for Search Engines" prowadzi pod adres design-instagram. Nic groźnego, ale przy czterdziestu tysiącach gwiazdek i siedmiu miesiącach bez commita — mówiące.

Warstwa czwarta: lej sprzedażowy

Tę część trzeba postawić otwarcie, bo bez niej opis byłby nieuczciwy. README otwiera się zachętą do zapisania się na newsletter autora w zamian za darmowy „System Design Interview Handbook", a przeważająca większość linków w sekcjach pojęciowych prowadzi na algomaster.io i blog.algomaster.io — czyli na komercyjną platformę kursową i płatny newsletter tej samej osoby. Osobna sekcja „Courses" wskazuje dwa kursy autora, sekcja „Newsletters" — jego newsletter.

Trzy rzeczy trzeba przy tym powiedzieć sprawiedliwie. Po pierwsze, nic tu nie jest ukryte: autor podpisuje się pod materiałami, a strona domowa repozytorium wskazuje wprost na jego platformę. Po drugie, część treści na tej platformie jest dostępna bez opłaty, więc opis „free resources" nie jest fałszywy — jest jednak niesprawdzalny bez klikania po kolei, bo repozytorium nie oznacza, co jest za rejestracją, co za opłatą, a co otwarte. Po trzecie, zadania rekrutacyjne prowadzą w większości na darmowe materiały wideo osób trzecich, a sekcje z pracami naukowymi i artykułami inżynierskimi są od promocji całkowicie wolne.

Praktyczny wniosek: traktuj to repozytorium jako spis treści i listę kontrolną, a nie jako źródło. Struktura jest darmowa i wartościowa. Treść pod linkami do platformy autora oceniaj tak, jak każdy materiał komercyjny — po tym, co dostajesz, a nie po tym, że przyszedłeś z zestawienia na GitHubie.

Które z tych pojęć zwracają się w pracy agencyjnej

Dziewięćdziesiąt pojęć to za dużo, żeby uczyć się wszystkich „na wszelki wypadek". W projektach, jakie realnie prowadzimy — sklepy, systemy rezerwacyjne, panele branżowe, integracje — zwracają się następujące, i to zazwyczaj w pierwszym roku:

  • Idempotentność. Numer jeden na tej liście. Webhook od bramki płatniczej przyjdzie dwa razy, klient kliknie „zapłać" dwukrotnie, a nasza kolejka ponowi zadanie po timeoucie. Bez klucza idempotencji każdy z tych trzech scenariuszy to podwójne obciążenie klienta,
  • Ograniczanie tempa żądań. Na naszym API, na formularzu kontaktowym, na endpointach logowania — i po naszej stronie, jako klienta cudzych API z limitami,
  • Strategie cache'owania i polityki wywłaszczania. Różnica między odczytem przez cache a zapisem przez cache decyduje o tym, czy po wdrożeniu klient widzi swoje zmiany. To pytanie zadaje się co drugi sprint,
  • Spójność silna kontra ostateczna. Nie jako teoria, a jako odpowiedź na pytanie „dlaczego licznik na dashboardzie pokazuje inną liczbę niż raport",
  • Indeksy i szardowanie. Pierwsze — zawsze. Drugie — żeby wiedzieć, że w naszej skali prawie nigdy nie jest odpowiedzią, a odpowiedzią są indeksy, replika do odczytu i porządne zapytania,
  • Kolejki komunikatów i publikuj/subskrybuj. Podstawa każdej integracji, która ma nie wywracać żądania HTTP,
  • Bezpiecznik i ponawianie. Kiedy integracja z cudzym API padnie, różnica między bezpiecznikiem a pętlą ponowień to różnica między degradacją a wzajemnym dobiciem dwóch systemów,
  • Przechwytywanie zmian danych. Nisza, ale przy synchronizacji z systemem klienta, którego nie wolno modyfikować, bywa jedynym sensownym wyjściem,
  • Odwrotne proxy i CDN. Bo to my ustawiamy nagłówki cache'owania i to nasz błąd, jeśli CDN serwuje spersonalizowaną stronę wszystkim.

A czego z tej listy w projekcie agencyjnym nie użyjesz i nie ma sensu udawać inaczej: algorytmów konsensusu, protokołu plotkowania, filtrów Blooma, spójnego haszowania i projektowania Ubera. To jest wiedza wartościowa poznawczo i konieczna na rozmowie do dużej firmy technologicznej, ale w systemie na jednej bazie z repliką nie ma czego rozwiązywać konsensusem. Warto wiedzieć, że istnieje. Nie warto się tego uczyć przed idempotentnością.

Pułapki

  • Siedem miesięcy bez commita przy dwustu linkach zewnętrznych. Część adresów wymaga już szukania tytułu w wyszukiwarce,
  • Większość linków pojęciowych prowadzi na komercyjną platformę autora, a repozytorium nie oznacza, co jest darmowe, co za rejestracją, a co płatne,
  • Implementacje są jednoprocesowe i bez synchronizacji — świetne do zrozumienia algorytmu, niebezpieczne po skopiowaniu do produkcji,
  • Zadania rekrutacyjne to nie plan nauki architektury. Projektowanie Google Maps uczy odpowiadania na rozmowach, nie prowadzenia projektu klienta,
  • Jeden autor, 149 ze 154 commitów — kierunek i selekcja są jego, wraz z jego interesem handlowym,
  • Drobne niechlujności: plik round_robin.py.py, błędny odnośnik przy zadaniu o autouzupełnianiu,
  • Licencja GPL-3.0 na zestawieniu linków — o czym niżej, bo to nie jest bez znaczenia, jeśli chcesz z tej listy zrobić własny materiał wewnętrzny.

Podsumowanie

To repozytorium jest wart uwagi z powodów innych niż te, dla których zebrało czterdzieści tysięcy gwiazdek. Co warto zapamiętać:

  • Największą wartością jest struktura — dziewięć obszarów i dziewięćdziesiąt pojęć jako lista kontrolna luk we własnej wiedzy, do przejścia w kwadrans,
  • Dziesięć klasycznych prac naukowych z aktualnymi adresami w jednym miejscu, bez żadnej promocji,
  • Sześć artykułów inżynierskich, z których tekst Airbnb o unikaniu podwójnych płatności jest lekturą obowiązkową dla każdego, kto integruje bramkę płatniczą,
  • Dwadzieścia dwie implementacje algorytmów w Javie i Pythonie, o których README nie wspomina — pięć limiterów, pięć algorytmów równoważenia, spójne haszowanie,
  • Implementacje czytaj, nie kopiuj. Produkcyjny limiter potrzebuje wspólnego stanu i niepodzielnej operacji — u nas RateLimiter na Redisie albo limit_req w nginksie,
  • Większość linków pojęciowych to lej do płatnej platformy autora. Nic ukrytego, ale warto o tym wiedzieć przed kliknięciem,
  • Ucz się w kolejności użyteczności: idempotentność, limity tempa, cache, spójność, indeksy, kolejki, bezpieczniki. Konsensus i filtry Blooma mogą poczekać,
  • Nie jest to plan nauki architektury, a mapa terenu plus zestaw wskazówek, gdzie szukać. Jako mapa — dobra.

Licencja: repozytorium jest na GPL-3.0 i to nietypowy wybór dla zestawienia odnośników, wart chwili uwagi właśnie dlatego, że łatwo go zignorować. Czytanie listy, klikanie linków i uczenie się z niej nie rodzi żadnych zobowiązań — licencja dotyczy rozpowszechniania, nie korzystania. Dwadzieścia dwie implementacje w Javie i Pythonie są jednak kodem objętym silnym copyleftem: skopiowanie ich do naszego repozytorium i dystrybucja produktu oznaczałaby, że całość trzeba udostępnić na GPL-3.0. Przy algorytmach opisanych w podręcznikach jest to problem raczej teoretyczny — kubełek z żetonami implementuje się samodzielnie w dziesięć minut i wtedy nie ma żadnej wątpliwości co do pochodzenia kodu — ale „wklejam, bo to tylko dwadzieścia linii z GitHuba" jest dokładnie tą decyzją, która wprowadza copyleft do zamkniętego projektu klienta. Osobna sprawa dotyczy samej listy: zrobienie z niej wewnętrznego materiału szkoleniowego i udostępnienie go poza firmą to rozpowszechnianie utworu pochodnego, więc również w zasięgu licencji. Bezpieczna droga jest prosta i nic nie kosztuje: korzystaj z listy jako drogowskazu, a treść pisz i koduj po swojemu. I pamiętaj, że treści pod linkami mają własne licencje oraz warunki — prace naukowe, artykuły firmowe i materiały platformy autora nie są objęte licencją tego repozytorium.