12 — Elastyczność i skalowalność
Złota zasada altytudy zastosowana do architektury: wolno tam, gdzie błąd jest drogi; szybko tam, gdzie tani. Skaluj, gdy metryka tego wymaga — nie wcześniej, nie „na zapas”.
Elastyczność to zdolność zmiany bez przepisywania; skalowalność to zdolność wzrostu bez przebudowy. Oba bierze się z tych samych nawyków: rozdziel warstwy, zmieniaj additive, schowaj ryzyko za flagą, nie sprzęgaj stanu tam, gdzie nie musisz. I — równie ważne — nie over-engineeruj: prosta architektura, którą umiesz rozwinąć, bije rozproszoną, której nie potrzebujesz.
Rozdziel warstwy
- DB ↔ ingest ↔ web to trzy oddzielne światy. W projekcie referencyjnym: SQLite — Najprostsza baza danych — cała mieszka w jednym pliku, bez osobnego serwera. Świetny default na start: zero konfiguracji, łatwy backup (kopiujesz plik). Przy wzroście migrujesz wyżej. (dane) ↔ scrapery Python — Popularny, czytelny język programowania — domyślny wybór The Craft do skryptów, danych i backendu. Sprawdzony default: dużo gotowych narzędzi i bibliotek, łatwy do prowadzenia z AI. (ingest) ↔ Node/Express (web) — łączy je kontrakt (schemat, → 11), nie wspólny kod. Wymienisz Scraping — Automatyczne zbieranie danych ze stron internetowych przez program zamiast ręcznego kopiowania. Potężne do pozyskiwania danych, ale wymaga kultury: szanuj cudze zasady (robots.txt), nie przeciążaj serwera. bez dotykania weba.
- Kontrakt API — Umówiony sposób, w jaki dwa programy gadają ze sobą — jeden prosi, drugi odpowiada w ustalonym formacie. Przez API Twoja aplikacja łączy się z cudzymi usługami (płatności, mapy, AI). Klucz API traktuj jak hasło. jako granica — np. front Next.js ↔ backend Python przez jawny kontrakt (OpenAPI). Granica, której się trzymasz, pozwala wymienić każdą stronę osobno. → 08
- Wspólny backend pod web + przyszły mobile. ADR z wyprzedzeniem: SQLite vs PostgreSQL — Solidna, „dorosła” baza danych do większych aplikacji — działa jako osobny serwer. Domyślny krok po SQLite, gdy potrzebujesz wielu równoczesnych użytkowników i zaawansowanych funkcji., sesja vs JWT (np. sesja dla weba; mobile w przyszłości → rozważ JWT na wspólnym backendzie). Decyzję zapisz, nie trzymaj w głowie. → 01
Zmieniaj tak, by się dało cofnąć i rampować
- Additive / backward-compatible — nowa kolumna, nowy Endpoint — Pojedynczy „adres” w API, pod który wysyłasz zapytanie po konkretną rzecz (np. listę zamówień). Aplikacje rozmawiają przez endpointy — jeden endpoint = jedna funkcja, którą udostępniasz., nowy język; nie łam tego, co działa (→ 11).
- Feature flag — Wewnętrzny włącznik, który uruchamia nową funkcję dla części ludzi albo wyłącza ją bez przerabiania kodu. Wpuszczasz nowość stopniowo i wycofujesz w sekundę — najtańsza polisa przy każdej zmianie. —
feature_flagsw bazie + przełączniki typuBETA_ALL_PREMIUM. Ship dark, potem ramp: kod jedzie wyłączony, włączasz dla części, potem dla wszystkich. Bez flag każda zmiana jest all-or-nothing. - Stateless, gdzie się da — im mniej stanu w procesie, tym łatwiej skalować poziomo.
Platforma: scale-to-zero vs always-on
Realna decyzja z dwóch projektów, świadomy tradeoff koszt/latencja:
- Cloud Run (scale-to-zero) — płacisz za użycie, zero ruchu = zero kosztu, ale cold start dodaje latencję pierwszego żądania. Dobre przy nierównym, globalnym ruchu.
- VPS — Wynajęty kawałek serwera w chmurze „tylko dla Ciebie”, na którym stawiasz aplikację „na żywo”. Przewidywalny koszt i pełna kontrola. Jeden VPS spokojnie uniesie kilka małych projektów. always-on (Hetzner — Tani, solidny dostawca serwerów (hosting), na którym stawia się aplikację „na żywo”. Domyślny wybór serwera w kodeksie — przewidywalny koszt i wydajność bez przepłacania. + pm2 — Menedżer, który pilnuje, żeby aplikacja (Node) ciągle działała — restartuje ją po awarii. Bez tego po crashu albo restarcie serwera aplikacja po prostu stoi. pm2 trzyma ją „przy życiu”.) — np. stały koszt, zero cold startu, pełna kontrola. Dobre przy przewidywalnym ruchu i SQLite na dysku. Wybór = profil ruchu i budżet, nie moda. Zapisz jako ADR.
Cache i pipeline’y
- Warstwy Cache — Tymczasowo zapamiętany wynik, żeby nie liczyć tego samego od nowa przy każdym pytaniu. Przyspiesza aplikację, ale bywa pułapką: nieświeży cache pokazuje stare dane. z jawną inwalidacją: np. inwalidacja cache w pipeline
full, rankingi cache’owane 10 min, statyki zmax-age(godzina dev / 7 dni immutable prod). - Composable, Idempotencja — Cecha operacji, którą można uruchomić wiele razy, a wynik jest zawsze taki sam — bez dublowania. Kluczowa przy skryptach i zdarzeniach: ponowne uruchomienie nie psuje danych. Jak włącznik „ON”. pipeline’y —
normalize → metadata → enrich → validate → stats; każdy etap odpalalny osobno, ponowne uruchomienie bezpieczne (→ 04). - i18n / multi-market od początku, jeśli globalnie (np. 16 języków od startu, nie doklejone później — → 10).
Nie over-engineeruj
- Zacznij prosto. SQLite + statyczny build (np. build statyczny w Pythonie) obsługują zadziwiająco duży ruch. Projekt referencyjny na SQLite/WAL — Write-Ahead Log: tryb bazy, w którym zmiany najpierw trafiają do dziennika, a potem do danych. Daje bezpieczeństwo (odtwarzalność po awarii) i lepszą równoczesność odczytu/zapisu. serwuje ~kilka tysięcy pozycji bez Postgresa.
- Skaluj, gdy metryka tego wymaga — nie „bo kiedyś urośnie”. Right-size do realnego ruchu.
- Migracja (bazy danych) — Kontrolowana zmiana układu bazy — dodanie kolumny, tabeli czy przeniesienie danych — krok po kroku. Jak remont według planu: przebudowa danych w ustalonej kolejności, żeby nic się nie „zawaliło”. SQLite→Postgres, Monolit — Aplikacja zbudowana jako jedna całość (jeden deploy, jedna baza), a nie zlepek osobnych usług. Prosty, tani default na start — łatwy do ogarnięcia. Rozbijasz go dopiero, gdy skala naprawdę tego wymaga.→serwisy: gdy liczby tego żądają, z ADR, nie prewencyjnie.
Anty-wzorce
- 🚫 Przedwczesna złożoność rozproszona (mikroserwisy/Kafka/k8s na 100 userów).
- 🚫 Stanowe sprzężenie blokujące skalowanie (stan sesji w pamięci procesu bez storu).
- 🚫 Big-bang rewrite zamiast zmian additive (→ 04).
- 🚫 Brak feature flags → każda zmiana wymuszona all-or-nothing, brak rampu/dark-shipu.
- 🚫 Ignorowanie kosztu always-on (płacisz za idle, gdy scale-to-zero by pasował) — i odwrotnie.
- 🚫 Przepisanie na Postgres „na zapas”, gdy SQLite jeszcze się nie zadyszał.
Dla nowych projektów
Na Dzień 0 (→ 07) ustal trzy granice (DB / ingest / web) i
zapisz trzy ADR: baza (SQLite vs Postgres), sesja vs JWT, platforma (VPS vs Cloud Run).
Wprowadź feature_flags od początku — to najtańsza polisa na elastyczność. Zacznij od
najprostszego stacku, który dowozi (statyk/SQLite), a skalowanie odłóż do chwili, gdy konkretna
metryka (latencja, koszt, rozmiar bazy) tego zażąda — i wtedy decyduj liczbami, nie przeczuciem
(→ 13).