🧭 Prolog: Piętnaście lat w okopach systemów rozproszonych
Jeżeli spędzisz w inżynierii oprogramowania ponad piętnaście lat, zaczynasz dostrzegać coś, co umyka w codziennym szumie technologicznym: cykle mody przychodzą i odchodzą, ale prawa fizyki obliczeniowej i sieciowej pozostają niezmienne.
Widziałem narodziny i upadki architektur, które miały „zmienić wszystko”. Przeżyłem zachwyt monolitycznymi frameworkami enterprise, eksplozję mikroserwisów doprowadzoną do absurdu (gdzie proste zapytanie HTTP generowało kaskadę 40 wywołań RPC wewnątrz klastra), modę na bazy NoSQL wciskane tam, gdzie wymagana była bezwzględna spójność ACID, aż po współczesny renesans monolitycznych struktur modułowych i systemów sterowanych zdarzeniami.
W tym czasie budowałem, optymalizowałem i ratowałem systemy o charakterze krytycznym — od platform transakcyjnych obsługujących miliony operacji, przez silniki komunikacji sieciowej, po węzły rejestracji domen i orkiestracji stref DNS, w których pojedynczy błąd współbieżności lub nieobsłużony timeout oznacza katastrofę biznesową dla tysięcy przedsiębiorstw.
Kiedy w 2026 roku patrzę na krajobraz współczesnej inżynierii oprogramowania, widzę jedno dominujące zagrożenie: epidemię nieuzasadnionej złożoności (accidental complexity).
Wielu inżynierów uwierzyło, że miarą dojrzałości systemu jest liczba kontenerów, warstw abstrakcji i modnych bibliotek w pliku konfiguracyjnym. Tymczasem najtrwalsze, najszybsze i najbardziej niezawodne systemy na świecie cechuje coś dokładnie odwrotnego: bezwzględna prostota, matematyczny determinizm i głęboki szacunek do protokołów bazowych.
Ten esej jest zapisem refleksji, lekcji i zasad projektowych, które ukształtowały moją filozofię inżynierską — od kodu źródłowego silnika ElasticName, przez architekturę platformy Adcelero, aż po strukturę operacyjną w grupie McKinley.
- ⚠️ 10 warstw abstrakcji i wrapperów
- ⚠️ Ukryty, współdzielony stan w pamięci
- ⚠️ Niekontrolowane blokowanie I/O na socketach
- ⚠️ Kaskadowe awarie i nieprzewidywalne cascading timeouts
- ⚠️ Brak determinizmu i strach przed refaktoryzacją
- 💎 Ścisła wierność protokołom IETF RFC
- 💎 Izolacja stanu poprzez Model Aktorowy
- 💎 Asynchroniczne, w pełni non-blocking I/O
- 💎 Determinizm, zero długu, sub-millisecond latency
- 💎 Otwarty kod i statyczna kontrola typów
🏛 1. Anatomia Architektury Krytycznej: Bezstanowość, Stan i Model Aktorowy
1.1. Grzech Pierworodny Systemów Rozproszonych: Ukryty Stan
W inżynierii oprogramowania istnieje tylko jeden prawdziwy problem fundamentalny: zarządzanie stanem w czasie.
Gdyby wszystkie operacje były czystymi funkcjami matematycznymi ($f(x) = y$), programowanie systemów rozproszonych byłoby trywialne. Prawdziwe trudności zaczynają się w momencie, gdy system musi utrzymać stan:
- Czy sesja mTLS z centralnym rejestrem NASK jest aktywna?
- Czy transakcja rejestracji domeny została zacommitowana po stronie rejestru, podczas gdy nasze połączenie HTTP zerwało się w trakcie transmisji nagłówka odpowiedzi?
- Kto jest właścicielem blokady w momencie równoczesnego uderzenia 500 zapytań o tę samą domenę podczas uwolnienia z droplisty?
Przez lata dominującym podejściem w branży było stosowanie współdzielonej pamięci z prymitywami blokującymi (mutexy, semafory, transakcje bazodanowe na poziomie SERIALIZABLE). W systemach o wysokiej współbieżności jest to prosta droga do zakleszczeń (deadlocks), zjawiska thread starvation i nieprzewidywalnych skoków opóźnień (latency jitter).
Moja pierwsza żelazna zasada brzmi: Uczyń wszystko, co możliwe, bezstanowym. A to, co ze swej natury musi posiadać stan, zamknij w szczelnie izolowanej, jednowątkowej komórce wykonawczej.
| WARSTWA BEZSTANOWA (Stateless Ingestion) | WARSTWA STANOWA (Isolated Actor Core) |
|---|---|
| • Kontrolery HTTP / REST (Play Framework) | • Dedykowany Aktor Rejestru (registry.pl) |
| • Serializacja / Deserializacja JSON & XML | • Kolejka komunikatów FIFO per węzeł |
| • Walidacja schematów i uprawnień RBAC | • Brak współdzielonej pamięci (Share Nothing) |
| • Skalowalność horyzontalna bez koordynacji | • Deterministyczna maszyna stanów sesji mTLS |
| • Pomiary telemetryczne i OpenTelemetry spans | • Atomowe transakcje w pamięci bez locków |
1.2. Dlaczego Scala i Model Aktorowy Akka?
Kiedy projektowaliśmy rdzeń silnika rejestracji domen ElasticName, stanęliśmy przed wyzwaniem: jak obsłużyć protokół EPP (Extensible Provisioning Protocol) w wariancie wymaganym przez NASK — gdzie transport opiera się na stateful HTTP session over mTLS, a pojedyncza sesja rejestratorska musi synchronicznie przetwarzać komendy, podczas gdy warstwa API dla klientów i agentów AI musi być w 100% asynchroniczna i reaktywna?
Większość tradycyjnych rejestratorów rozwiązała ten problem najgorszym możliwym sposobem: pulą procesów PHP/Perl, z których każdy blokuje dedykowane gniazdo TCP, albo ociężałymi wrapperami w Javie z tysiącami zablokowanych wątków czekających na I/O.
My wybraliśmy Scalę i model aktorowy Akka.
Dlaczego? Ponieważ model aktorowy eliminuje współdzieloną pamięć. Aktor to autonomiczny byt, który:
- Posiada swój prywatny stan, niedostępny dla żadnego innego wątku w procesie JVM.
- Komunikuje się ze światem zewnętrznym wyłącznie poprzez niemutowalne komunikaty (immutable messages) wysyłane do jego skrzynki odbiorczej (mailbox).
- Przetwarza komunikaty sekwencyjnie, jeden po drugim, eliminując konieczność stosowania jakichkolwiek muteksów wewnątrz logiki biznesowej.
// Fragment koncepcyjny z rdzenia ElasticName / DialectPL
sealed trait RegistryCommand
case class CheckDomain(name: DomainName, replyTo: ActorRef[CheckResult]) extends RegistryCommand
case class RegisterDomain(name: DomainName, auth: AuthInfo, period: Years, replyTo: ActorRef[RegisterResult]) extends RegistryCommand
case class SessionKeepAlive(ticket: Long) extends RegistryCommand
class NaskRegistryActor(config: RegistryConfig, transport: HttpMtlsTransport) extends Actor with Stash {
// Wewnętrzny, ściśle izolowany stan sesji
private var sessionCookie: Option[SessionCookie] = None
private var inFlightCommands: Int = 0
override def receive: Receive = disconnected
private def disconnected: Receive = {
case cmd: RegistryCommand =>
stash() // Buforujemy zapytania na czas nawiązywania bezpiecznego połączenia
initiateLoginHandshake()
case LoginSuccess(cookie) =>
sessionCookie = Some(cookie)
unstashAll()
context.become(connected)
case LoginFailure(cause) =>
log.error(s"Błąd autoryzacji mTLS z rejestrem: ${cause.getMessage}")
// Backoff supervisor podejmuje próbę samonaprawy (Self-Healing)
}
private def connected: Receive = {
case CheckDomain(name, replyTo) =>
val currentCookie = sessionCookie.get
transport.sendEppXml(renderCheckXml(name), currentCookie).pipeTo(replyTo)
case SessionExpired =>
sessionCookie = None
context.become(disconnected)
}
}
Taka konstrukcja sprawia, że system jest deterministyczny z definicji. Nawet w przypadku awarii sieci, restartu load balancera NASK czy wygaśnięcia ciasteczka sesyjnego, żaden wątek nie ulega zawieszeniu. Aktor zmienia stan na disconnected, bezpiecznie odkłada przychodzące komunikaty na stos (stash()), przeprowadza cichą renegocjację sesji mTLS, po czym wznawia przetwarzanie z zerową utratą danych.
To nie jest „przypadkowy sukces”. To matematyczna elegancja modelu aktorowego.
📜 2. Zaufanie do Standardów IETF RFC vs Wymyślanie Koła na Nowo
2.1. Piękno Protokołów, Które Przetrwały Próbę Dekad
Jedną z najważniejszych lekcji, jakie wyniosłem z 15 lat pracy, jest pokora wobec inżynierów, którzy tworzyli fundamenty Internetu w IETF (Internet Engineering Task Force).
Młodzi programiści mają tendencję do traktowania starych protokołów z pobłażaniem. Słyszę często: „Dlaczego DNS (RFC 1034/1035) używa UDP i binarnego formatu nagłówków z 1987 roku? Zbudujmy to na JSON-ie i GraphQL-u!” albo „Dlaczego EPP (RFC 5730–5734) operuje na XML-u zamiast gRPC?”.
To fundamentalne niezrozumienie inżynierii sieciowej.
Protokoły takie jak DNS, BGP, TCP czy EPP zostały zaprojektowane pod kątem:
- Maksymalnej wydajności transmisji bitowej: Nagłówek DNS ma 12 bajtów. W 12 bajtach zakodowano ID transakcji, 8 flag bitowych, 4 liczniki rekordów. W erze, gdy pakiety przemierzają świat w światłowodach z prędkością bliską 200 000 km/s, każdy bajt narzutu i każdy niepotrzebny round-trip (RTT) ma znaczenie fizyczne.
- Odporności na asynchroniczne awarie węzłów: Wbudowane mechanizmy TTL, łańcuchy delegacji, strefy autorytatywne, semantyka kodów błędów.
- Niezmienności i interoperacyjności: Kod napisany zgodnie z RFC 1035 w 1990 roku poprawnie zinterpretuje pakiet odpowiedzi serwera BIND9 wygenerowany dzisiaj. Pokażcie mi bibliotekę JavaScript, która bez zmian działa przez 5 lat, nie mówiąc o 35.
| PODEJŚCIE PROPRIETARY / HYPE-DRIVEN | STANDARDY IETF RFC (Podejście Adcelero) |
|---|---|
| • Wymyślanie własnych dialektów i payloadów | • Ścisła zgodność z RFC 5730–5734 (EPP Core) |
| • Ukrywanie protokołu za 5 warstwami wrapperów | • RFC 1034/1035 (DNS), RFC 4033–4035 (DNSSEC) |
| • Niejednoznaczna semantyka błędów HTTP 500 | • RFC 5910 (EPP DNSSEC Extension) |
| • Niemożność audytu i vendor lock-in | • Czyste, deterministyczne drzewa stanów (AST) |
| • Podatność na regresje przy update bibliotek | • Przewidywalność na dekady |
2.2. Koszt Wynajdywania Koła na Nowo
Kiedy zespół inżynierski postanawia „uprościć sobie życie” i zamiast zgłębić specyfikację RFC tworzy własną warstwę abstrakcji, niemal zawsze kończy się to katastrofą w środowisku produkcyjnym:
- Ignorowanie specyfikacji DNSSEC (RFC 4033–4035) i próba traktowania rekordów
DSczyDNSKEYjak zwykłych stringów tekstowych kończy się rozerwaniem łańcucha zaufania i unieważnieniem całej domeny w walidujących resolverach (jak Cloudflare1.1.1.1czy Google8.8.8.8). - Błędna interpretacja semantyki komendy
<poll>w EPP prowadzi do zapychania kolejki wiadomości rejestru, co skutkuje przegapieniem krytycznych powiadomień o transferze domeny lub jej wygaszeniu.
W Adcelero i ElasticName przyjęliśmy zasadę Protocol Fidelity (Wierności Protokołowi). Nie ulepszamy tego, co zostało zdefiniowane w RFC. Zamiast tego budujemy wokół standardu najbardziej precyzyjne, typowane, reaktywne modele danych w Scali, jakie pozwala stworzyć współczesna kompilacja statyczna.
⚡ 3. Obsesja na Punkcie Eliminacji Tarcia (Velocity & Latency Reduction)
3.1. Dwa Wymiary Opóźnienia: Fizyka Kodu i Fizyka Organizacji
Większość ludzi myśli o opóźnieniu (Latency) wyłącznie w kategoriach milisekund w narzędziach takich jak Jaeger, Grafana czy wrk:
- Ile milisekund trwa wykonanie zapytania do bazy danych?
- Jaki jest p99 handshake'u TLS?
- Ile mikrosekund zajmuje serializacja JSON-a?
Dla mnie pojęcie Latency jest znacznie szersze. Opóźnienie to każde tarcie, które opóźnia przejście od intencji do rezultatu.
| SYSTEM LEVEL: Micro-Latency | BUSINESS LEVEL: Macro-Latency |
|---|---|
| • Netty EventLoop context switches | • Manualne formularze i wysyłanie wniosków |
| • Blokujące zapytania I/O na socketach | • Oczekiwanie 48h na kod AuthInfo z supportu |
| • Garbage Collection pauses (Stop-the-World) | • Ręczne wystawianie i parowanie faktur VAT |
| • Kaskadowe wywołania synchroniczne | • Wdrażanie DNS przez ticket zamiast API |
| CEL: P99 < 50ms w kodzie | CEL: Zero tarcia, 100% automatyzacji |
W inżynierii sieciowej prawo Amdahla jest bezwzględne: jeżeli zoptymalizujesz czas wykonania algorytmu z 10ms do 1ms, ale Twój proces biznesowy wymaga 2 dni roboczych na to, by człowiek kliknął przycisk w panelu administracyjnym — Twoja całkowita latencja wynosi 2 dni.
3.2. Likwidacja Tarcia w Kodzie (System Level)
W warstwie kodu źródłowego eliminacja tarcia oznacza:
- Zero-Blocking Architecture: Żaden wątek w puli wykonawczej aplikacji nie ma prawa czekać na operacje sieciowe, dyskowe czy bazodanowe. Zastosowanie asynchronicznego I/O (Netty, Akka IO, Slick Non-Blocking Streaming) pozwala obsłużyć dziesiątki tysięcy jednoczesnych połączeń przy zaledwie kilku wątkach roboczych procesora, dopasowanych do liczby fizycznych rdzeni.
- Zero-Copy i Minimalna Alokacja: Unikanie zbędnego kopiowania buforów w pamięci RAM. Parsowanie strumieniowe XML/JSON bez tworzenia tymczasowych milionów obiektów zaśmiecających pamięć sterty JVM (co eliminuje pauzy GC).
- Deterministyczny Routing: Wyeliminowanie niepotrzebnych skoków sieciowych. Jeśli zapytanie o dostępność domeny może zostać zweryfikowane w lokalnej pamięci podręcznej strefy lub zoptymalizowanym indeksem droplisty, odpowiedź wraca do klienta w ułamku milisekundy.
3.3. Likwidacja Tarcia w Strukturze Grupy McKinley (Business Level)
Tę samą obsesję eliminacji tarcia przenieśliśmy na poziom operacyjny wszystkich spółek i projektów w ramach grupy McKinley.
Tradycyjny rynek rejestracji domen w Polsce zbudował swoje marże na sztucznie wytworzonym tarciu:
- Chcesz pobrać kod AuthInfo, by przenieść domenę? Złóż wniosek papierowy, podpisz u notariusza, wyślij pocztą tradycyjną, poczekaj 14 dni roboczych.
- Chcesz zarejestrować 100 domen pod dynamiczne środowiska testowe? Klikaj pojedynczo w archaicznym panelu z 2004 roku.
- Chcesz rozliczyć faktury za 500 domen swoich klientów agencji? Ściągaj 500 pojedynczych plików PDF co miesiąc.
To jest patologia inżynieryjna i biznesowa.
W Adcelero zlikwidowaliśmy każdy z tych punktów oporu:
- Kod AuthInfo on-demand: Dostępny natychmiast w panelu i przez REST API w czasie rzeczywistym.
- DNS-as-Code & REST API: Pełna automatyzacja stref DNS, webhooki, natychmiastowe provisioningi stref.
- Automatyzacja Bilingowa & KSeF: Zbiorcze fakturowanie, integracja z Krajowym Systemem e-Faktur, subkonta dla agencji z automatycznym rozliczaniem Stripe.
Kiedy eliminujesz tarcie, uwalniasz niesamowity wektor wzrostu. Inżynierowie i agencje wybierają Adcelero nie dlatego, że przeczytali slogan reklamowy, ale dlatego, że nasz system nie marnuje ich najcenniejszego zasobu — czasu.
🚀 4. Dlaczego ElasticName i Adcelero Powstały Jako Otwarty Ekosystem Bez Długu Technologicznego
4.1. Pułapka Długu Technologicznego w Sektorze Rejestratorów
Większość działających w Polsce rejestratorów domen to systemy uwięzione w latach 2000–2010. Powstawały w epoce monolitycznego PHP 5.2, archaicznych bibliotek cURL i baz danych MySQL z tabelami MyISAM.
Przez 15–20 lat te systemy obrosły setkami warstw łat (monkey patches), prowizorycznych skryptów crona i nieudokumentowanych wyjątków biznesowych. Nikt w tych organizacjach nie rozumie już całego przepływu danych. Każda próba wdrożenia nowej funkcji — jak obsługa rekordów HTTPS (RFC 9460), automatyzacja DNSSEC z algorytmem ECDSA, czy integracja z API nowoczesnych chmur — trwa miesiące i niesie ze sobą ryzyko regresji.
Kiedy podjąłem decyzję o budowie Adcelero i silnika ElasticName, zasada była jedna: Żadnych kompromisów, żadnego kopiowania archaicznych rozwiązań, zero długu technologicznego na starcie.
| LEGACY STACKS (2005–2020) | ADCELERO 2.0 / ELASTICNAME ARCHITECTURE |
|---|---|
| • 20 lat nieudokumentowanych łat i monkey-patches | • Czysta, statycznie typowana Scala 2.12 / 3 |
| • Blokujące skrypty PHP/cURL i gniazda TCP | • Niemutowalne modele AST (Protocol) |
| • Brak testów integracyjnych i brak mocków | • 100% test coverage z EPPSimulator Mock |
| • Podatności architektury monolitycznej | • Reaktywne strumienie i asynchroniczne I/O |
| • Strach przed jakimkolwiek refaktoringiem | • Continuous Refactoring & Strict Verification |
4.2. Decyzja o Open Source (Apache 2.0): Odwaga i Radykalna Transparentność
Wielu doradców biznesowych pytało mnie: „Dlaczego otwieracie kod źródłowy rdzenia ElasticName? Dlaczego publikujecie całą implementację protokołu NASK EPP Schema 2.1 w otwartym repozytorium GitHub na licencji Apache 2.0? Przecież konkurencja może to skopiować!”.
Moja odpowiedź jest prosta: Jeżeli Twoja jedyna przewaga konkurencyjna polega na ukrywaniu kodu, który realizuje publiczny standard IETF, to nie masz żadnej przewagi.
Otwarcie kodu silnika ElasticName było decyzją strategiczną i doktrynalną:
- Dowód Dojrzałości Inżynieryjnej: Nie prosimy klientów, programistów i partnerów B2B o „ślepe zaufanie”. Dajemy im kod źródłowy do wglądu. Każdy architekt może wejść do repozytorium, sprawdzić, jak zarządzamy sesjami mTLS, jak parsujemy XML, jak wygląda obsługa błędów.
- Koniec z Vendor Lock-In: Pokazujemy rynkowi, że Adcelero to platforma budowana przez inżynierów dla inżynierów.
- Podniesienie Standardów w Całej Branży: Polski rynek domenowy przez zbyt długi czas tkwił w cieniu zamkniętych, monopolitycznych rogatek podatkowych. Otwarcie nowoczesnego silnika EPP w Scali daje społeczności narzędzie klasy Enterprise i wymusza na całym rynku transparentność.
🛠 5. Dekalog Architekta: Zasady Budowania Odpornych Systemów na Kolejne 15 Lat
Dla inżynierów, tech leadów i przyszłych architektów, którzy budują systemy mające przetrwać próbę skali i czasu, zebrałem 10 fundamentalnych reguł warsztatowych:
🔮 Epilog: Internet w Erze Autonomicznych Agentów AI
Wchodzimy w fascynującą epokę. W 2026 roku po raz pierwszy w historii architektury sieciowej głównymi użytkownikami infrastruktury internetowej przestają być wyłącznie ludzie klikający w przeglądarkach — stają się nimi autonomiczni agenci AI.
Agent AI nie potrzebuje kolorowych banerów, skomplikowanych formularzy z captchą ani infolinii z konsultantem. Agent AI potrzebuje:
- Precyzyjnego, deterministycznego REST API.
- Czasów odpowiedzi mierzonych w milisekundach.
- Zrozumiałych, standaryzowanych kodów błędów zgodnych z protokołem.
- Stuprocentowej niezawodności i bezpieczeństwa kryptograficznego (DNSSEC).
Systemy zbudowane na kruchych, przekombinowanych podstawach rozpadną się pod naporem tej nowej fali automatyzacji. Przetrwają te, które postawiono na fundamencie prostoty, standardów i czystego rzemiosła inżynierskiego.
Właśnie dlatego zbudowaliśmy Adcelero. I dlatego każdy dzień naszej pracy to hołd dla bezkompromisowej inżynierii.