Czy powinniśmy zastąpić Kafkę Temporal Standalone Activities? Nasz ADR

TL;DR

Prowadzimy Kafkę na produkcji do operacji masowych i zastanawiamy się, czy zastąpić ją Temporal Standalone Activities. Tekst napisany w formie ADR-u zestawia trzech kandydatów (Kafka + KIP-932, zwykła kolejka, Temporal) względem dziewięciu ważonych kryteriów i pokazuje, dlaczego skłaniamy się ku Temporal - oraz ile dokładnie nas to kosztuje. To nie manifest w stylu “Kafka jest zła”; to zapis decyzji, która nie będzie ostateczna, dopóki PoC nie rozstrzygnie jedynego otwartego ryzyka: obciążenia warstwy trwałości przy milionie zadań.

Część 1 - Problem (dla każdego, kto podejmuje decyzje systemowe)

“Batch” znaczy co innego każdego dnia

Każda duża organizacja ma klasę operacji uruchamianych masowo na tysiącach lub milionach obiektów.

  • Przyznanie promocji segmentowi klientów.
  • Naprawa źle skonfigurowanej oferty.
  • Wypchnięcie kampanii.
  • Wysyłka fali e-maili lub SMS-ów.

W telekomunikacji to codzienność, ten sam wzorzec pojawia się w bankowości, ubezpieczeniach, retailu. Oto scena, która definiuje całą architekturę. Wpada zgłoszenie oznaczone jako pilne: wadliwa oferta naliczyła zły rabat kilkudziesięciu tysiącom klientów i trzeba to naprawić przed nocnym billingiem. W tym samym czasie system przetwarza zaplanowaną kampanię na całej bazie - miliony rekordów. Pytanie, które rozstrzyga wszystko dalej, jest proste. Czy pilna poprawka przeskakuje kolejkę, czy czeka kilka godzin za kampanią? Jeśli czeka, klient dostaje błędną fakturę.

Trzy wymagania nigdy nie znikają:

Audytowalność. Dla każdego pojedynczego rekordu musimy wiedzieć, czy akcja się wykonała, kiedy, z jakim wynikiem, a jeśli się nie powiodła - dlaczego. Na poziomie rekordu, nie zbiorczo.

Kontrola nad stanem i priorytetem. Operator musi widzieć postęp, móc wstrzymać, wznowić i pozwolić pilnemu zadaniu wyprzedzić zaplanowane.

Bezpieczne ponawianie. Rekordy, które się nie powiodły, muszą dać się ponowić bez podwójnego wykonania akcji. Jednym słowem: idempotencja.

Brzmi jak batch. Dokładnie na tym założeniu zbudowaliśmy pierwszą generację i okazało się, że to batch tylko z nazwy.

Dwie generacje za nami

Generacja 1 - Spring Batch. Klasyka, sprawdzona, bez dodatkowej infrastruktury. Problem: ta sama funkcjonalność jednego dnia przetwarza 10 rekordów, a następnego milion, a Spring Batch nie skaluje się w chmurze bez doczepienia kolejnego brokera i sporej gimnastyki konfiguracyjnej. Do tego płaska kolejka zadań, więc brak realnej priorytetyzacji.

Spring Batch Architecture

Generacja 2 - Kafka. To działa dziś na produkcji. Czytnik pobiera rekordy z bazy danych i publikuje je do topiców; konsumenci przetwarzają je niezależnie. Zyskaliśmy rozdzielenie ingestu od przetwarzania i skalowanie konsumentów. Ale po kilku latach utrzymywania tego rozwiązania uczciwa ocena jest taka, że wiele z tego, co daje Kafka, jest nam niepotrzebne, a wiele z tego, czego potrzebujemy, musieliśmy dobudować sami na wierzchu. Obserwacja, która wszystko dźwiga: zakładaliśmy, że zdarzenia będą płynąć z wielu źródeł, a w praktyce jest dokładnie jedno. Płacimy pełną cenę architektury pub/sub za coś, co pod spodem jest sprioretyzowaną kolejką zadań.

Kafka Batch Architecture

I tu zaczyna się właściwy temat tego artykułu. Jeśli problemem jest kolejka zadań, a nie strumień, co pasuje najlepiej? Zestawiliśmy kandydatów z tym samym zestawem kryteriów.


Część 2 - ADR: wybór architektury dla operacji masowych (analiza techniczna)

Uwaga o skali. To wszystko ma znaczenie dopiero powyżej mniej więcej kilkuset tysięcy rekordów na operację, przy kilkuset odrębnych typach zadań. Poniżej tego progu wybór to głównie kwestia gustu - każde z rozwiązań się sprawdzi. Różnice zaczynają boleć, kiedy albo wolumen pojedynczej operacji, albo kardynalność (ile niezależnych typów zadań uruchamiasz) zaczyna dokuczać. Dwie różne osie, do obu jeszcze wrócę.

Kontekst decyzji

Status: Proponowany. Generacja 2 jest na produkcji.

Kod biznesowy to Java + Spring Boot. Logika akcji - remediacja, promocja, dyspozycja - jest prosta i przenośna między architekturami, co ma znaczenie, bo koszt przeniesienia samej logiki jest niski w każdej opcji. Różnica leży w infrastrukturze wokół niej. A źródłem danych jest jedna baza Oracle, co po cichu zabija główny argument za pub/sub.

Kryteria i ich waga

Każde kryterium ma wagę wynikającą z naszego kontekstu, nie z podręcznika: Krytyczne, Ważne albo Miło mieć. To waga przesądza o decyzji, nie suma punktów. Nie sumuję ocen i niżej wyjaśniam dlaczego.

#KryteriumWagaDlaczego taka waga
K1Audyt na poziomie rekordu (status, próby, ostatni błąd, przeszukiwalny)KrytyczneRegulacyjne i operacyjne. Nie do negocjacji
K2Priorytetyzacja (pilne wyprzedza zaplanowane)Miło miećRealny scenariusz, ale dziś radzimy sobie bez tego
K3Ponawianie z backoffem + obsługa błędów (DLQ)KrytyczneBez tego błędny rekord kręci się w pętli
K4Skalowanie poziome bez sufitu partycji / rebalansuKrytyczneKardynalność zadań rośnie; topologia nie może być hamulcem
K5Długo trwające zadania (bez walki z timeoutem brokera)KrytyczneNiektóre akcje trwają minuty, nie milisekundy
K6Koszt operacyjny / złożoność infrastrukturyWażneBól, od którego zaczęła się cała ta analiza
K7Ścieżka do wieloetapowego procesuMiło miećMamy dziś kilka połączonych zadań, ale to mniejszość
K8Dojrzałość i ryzykoWażneRegulowany klient oceni to we własnym przeglądzie
K9Lock-in / koszt wyjściaMiło miećLogika domenowa i tak pozostaje agnostyczna

Jedna rzecz do ustalenia przed kandydatami. Te kryteria oceniają to, co mechanizm kolejkowania daje “z pudełka”. Tam, gdzie już dobudowaliśmy coś na dzisiejszej Kafce, żeby spełnić kryterium, oznaczę to osobno - bo ten kod nie znika z bilansu. Działa i to my go utrzymujemy. Cały sens przyglądania się Temporal to skasowanie tego kodu, nie przeniesienie go gdzie indziej.

Kandydat A - zostajemy przy Kafce (ewentualnie + KIP-932 Share Groups)

Najtańsza decyzja to nic nie zmieniać. A od Kafki 4.2 pojawia się naprawdę mocny argument za pozostaniem: Share Groups (KIP-932) dają natywną semantykę kolejki i zrywają sprzężenie między równoległością a liczbą partycji. To adresuje nasz największy ból związany z partycjami.

Gdzie wciąż się nie domyka:

K1, audyt (Krytyczne). Sama Kafka nic tu nie daje. Licznik dostaw i stan w locie to wewnętrzne szczegóły brokera, nie przeszukiwalne API “jaki jest status rekordu X”. Co mamy dziś: audyt zbudowany na własnych tabelach stanu. Działa i spełnia wymaganie - ale to nasz kod, nasz schemat, nasze utrzymanie.

K3, retry/DLQ (Krytyczne). Kafka pozwala zwolnić rekord do ponownej dostawy, ale bez backoffu (opóźnione ponowienia wciąż są tylko w planach), a natywny DLQ to KIP-1191, celowany na Kafkę 4.4 - jeszcze go nie ma. Co mamy dziś: prosty, niezawodny mechanizm - licznik prób na rekordzie plus cron, który przełącza statusy i ponawia na podstawie maksymalnej liczby prób. Niezawodny i znowu - to my go utrzymujemy przy życiu.

K2, priorytety (Miło mieć). Brak natywnych priorytetów w share groups, więc wracamy do osobnych topiców albo własnej logiki. Dziś tego nie mamy i radzimy sobie.

K5, długie zadania. Rekord jest wydawany pod blokadą czasową (domyślnie około 30s); cokolwiek dłuższego oznacza odnawianie blokady. Ta sama walka z timeoutem, inna nazwa.

K6, koszt. Rośnie, nie maleje. Share groups dodają koordynatora share i wewnętrzny topic stanu do brokera; główny ból (utrzymanie klastra Kafki) zostaje nietknięty. A dojście do 4.2 to ścieżka aktualizacji - dla klastra na 3.x najpierw migracja z ZooKeepera na KRaft, co w enterprise jest osobnym projektem.

K8, dojrzałość. Sama Kafka jest sprawdzona, ale konkretnie share groups są nowe (GA w 4.2), więc ten konkretny wariant jest młodszy, niż się wydaje. Klasyczna Kafka bez share groups: solidna.

K9, lock-in. Niski. Kafka to standard z wieloma implementacjami.

Werdykt: KIP-932 rozwiązuje dokładnie jeden z naszych problemów (K4, sufit partycji). Wszystko po stronie stanu - audyt, priorytety, backoff, DLQ - zostaje czymś, co sami budujemy i utrzymujemy, a klaster robi się bardziej, nie mniej złożony. To wciąż dyspozycja bez zarządzania stanem zadania. A zarządzanie stanem zadania to właśnie kod, który próbujemy skasować.

Kandydat B - zwykła kolejka (RabbitMQ / Oracle AQ / SELECT … FOR UPDATE SKIP LOCKED)

RabbitMQ, Oracle AQ Architecture

Jeśli problemem jest kolejka zadań, zwykła kolejka to oczywisty ruch. Trzy realistyczne warianty.

RabbitMQ daje konkurujących konsumentów bez pojęcia partycji, a skalowanie konsumentów na Kubernetesie jest tak czyste, jak to możliwe - KEDA na głębokości kolejki, od 0 do N bez rebalansu. Świetnie radzi sobie z K4. Oracle AQ kusi, bo źródło już jest w Oracle, więc to zero nowej infrastruktury. A SKIP LOCKED to kolejka zbudowana wprost na bazie danych, którą już mamy, bez żadnego dodatkowego systemu - najprostsza rzecz, jaka mogłaby zadziałać.

Gdzie się nie domyka, we wszystkich wariantach:

K1, audyt (Krytyczne). Kolejka wydaje zadanie i zapomina o nim po ack. Status na poziomie rekordu, liczba prób, ostatni błąd - znowu własny magazyn stanu. Jedno warto tu powiedzieć: nasze obecne tabele stanu i retry+cron przeniosłyby się do tego wariantu niemal bez zmian, zwłaszcza do SKIP LOCKED, gdzie stan już żyje w tabeli, więc audyt i retry to naturalne rozszerzenie, a nie doklejka. To sprawia, że migracja do B jest tania. Nie kasuje naszego kodu - utrwala go.

K3, retry/backoff. W RabbitMQ backoff to taniec z dead-letter-exchange i TTL; da się, ale to znowu wasz kod infrastruktury. AQ ma natywny opóźniony retry, co jest realnym plusem.

K5, długie zadania. consumer_timeout w RabbitMQ (domyślnie 30 min) zabija kanał, jeśli ack nie przyjdzie, i nie ma odpowiednika heartbeatu, więc wielogodzinne akcje wymagają obejść.

K2, priorytety. RabbitMQ - mirrored queues zniknęły w 4.0, a quorum queues dają dwa poziomy priorytetu. Wystarczy na “pilne kontra zaplanowane”, nie na nic bogatszego. AQ i SKIP LOCKED obsługują priorytet zapytaniem albo kolumną.

K7, ścieżka procesu. Żadne z nich nic tu nie oferuje.

Jest też zmarszczka związana ze skalowaniem brokera RabbitMQ: pojedyncza kolejka żyje na pojedynczym węźle, więc przepustowość jednej kolejki jest ograniczona jednym brokerem. Przy naszej kardynalności (kilkaset kolejek) akurat rozkłada się to na klaster, więc dla nas konkretnie to mniejszy problem, ale warto o tym wiedzieć.

K8, dojrzałość jest najwyższa z całego pola - SKIP LOCKED to zwykły SQL sprawdzony od dekad, Rabbit i AQ mają długą historię w enterprise. Najłatwiej przejść przez przegląd architektury w banku. K9, lock-in: SKIP LOCKED to zero (to SQL na bazie, którą i tak prowadzimy), Rabbit/AQ - niski.

Werdykt: zwykła kolejka rozwiązuje dyspozycję (K4) czysto i tanio. Nie zdejmuje z nas stanu zadania (K1, K3, K5, K7). Wszystko, od czego chcieliśmy odejść - własny audyt, retry, obsługę błędów - musielibyśmy odbudować gdzie indziej. SKIP LOCKED jest tu najciekawszy jako minimum, które działa, gdyby liczyła się tylko prostota.

Kandydat C - Temporal Standalone Activities

Temporal Standalone Activities

Już dziś prowadzimy Temporal do orkiestracji długo żyjących procesów (workflow). Standalone Activities, zapowiedziane w 2026, pozwalają uruchomić pojedynczą Activity jako samodzielną rzecz, bez opakowywania jej w workflow. Klient zgłasza zadanie, serwer trwale je zapisuje przed potwierdzeniem, worker je wykonuje, a wynik, liczba prób i ostatni błąd są widoczne w API i Web UI.

Potraktuję tego kandydata mniej pobłażliwie niż pozostałych, nie bardziej - to najsilniejszy kandydat, dlatego zasługuje na najtwardsze spojrzenie.

K1, audyt (Krytyczne). Natywne, z gwiazdką. Status, liczba prób, ostatni błąd są przeszukiwalne, bez żadnego naszego magazynu stanu. Ale w naszej skali “przeszukiwalne” oznacza postawienie Elasticsearch albo OpenSearch jako backendu widoczności - kolejny klaster do utrzymania - a ukończone activities podlegają retencji, więc wypadają z historii. Audyt, który musi przetrwać lata, i tak oznacza eksport do własnego magazynu. Więc to tak, warunkowo, pod ES plus politykę archiwizacji. Nie za darmo.

K2, priorytety (Miło mieć). Ważone poziomy priorytetów na jednej kolejce zadań, z ochroną przed głodzeniem - i właśnie z powodu tej ochrony to nie jest ścisła kolejka priorytetowa: pod obciążeniem część niższego poziomu i tak przechodzi. Jeśli biznes kiedyś zażąda “pilne jest absolutne, kropka”, trzeba rozdzielić na osobne kolejki zadań i pule workerów, co z powrotem wciąga zarządzanie topologią. A wstrzymywanie pojedynczego zadania trafia dopiero do GA. Realistycznie: częściowo.

K3, retry/backoff (Krytyczne). Deklaratywna polityka ponawiania z backoffem, obsługa błędów w pudełku. Czyste tak.

K4, skalowanie (Krytyczne). Tu leży ukryty koszt. Po stronie konsumpcji limit równoległości to liczba workerów, bez sufitu partycji. Ale wąskie gardło przenosi się na warstwę trwałości serwera Temporal: każde zgłoszenie, każda zmiana stanu, każdy heartbeat to zapis. Przy prostym przemiale przez milion rekordów Temporal jest cięższy niż dopisanie do logu Kafki - płacisz I/O za każdą gwarancję trwałości. To nie jest “przeskaluj workerów i gotowe”. To “wymiaruj, a być może i shardinguj, bazę danych Temporal”. Częściowo, i to jedyne “częściowo” na kryterium krytycznym.

K5, długie zadania (Krytyczne). Heartbeaty plus checkpointing, zaprojektowane dokładnie do tego. Naturalny żywioł Temporal. Tak.

K7, ścieżka procesu (Miło mieć). Ta sama Activity działa samodzielnie i jako krok workflow bez zmiany kodu. Mamy już garść połączonych zadań, gdzie jedno uruchamia następne - mniejszość, ale realna, i będzie rosła. Kiedy operacja masowa zamienia się w proces (promocja, potem powiadomienie, potem rozliczenie), opakowujemy istniejące Activities zamiast przepisywać. Żaden inny kandydat tego nie oferuje.

K8, dojrzałość (Ważne). To nowa funkcja (Public Preview) i mówię to wprost. Ale bez teatru: sam Temporal jest u nas solidny i nigdy nas nie zawiódł, a Standalone Activity to mechanicznie ten sam wzorzec co workflow z jednym krokiem. To węższe API na dojrzałym rdzeniu, nie nowy silnik. Ryzyko jest realne, ale ograniczone do świeżości powierzchni API, nie fundamentu. Częściowo, nie porażka.

K9, lock-in (Miło mieć). Niski, i to dzięki dyscyplinie, a nie czemuś, co daje sam Temporal. Logika akcji żyje jako zwykła Java/Spring wewnątrz Activity (applyPromotion(input)), a Temporal jest tylko warstwą kolejkowania i orkiestracji wokół niej. Wyjście oznacza wymianę tej warstwy, tak samo jak wymianę konsumenta Kafki albo pętli SKIP LOCKED; logika domenowa pozostaje nietknięta. Zastrzeżenie: to działa tylko dopóki trzymamy się Standalone Activities. W momencie, gdy wchodzimy w K7 i piszemy prawdziwe workflow, kod workflow przyjmuje kształt Temporal - determinizm, sygnały, wersjonowanie - i to już tak łatwo nie przenosi się dalej. Ta sama ścieżka procesu, która jest argumentem za, to też miejsce, gdzie ujawnia się lock-in. Oczy otwarte.

Uczciwe zastrzeżenia, bo bez nich to marketing, nie ADR: status Public Preview i brak natywnego wstrzymania pojedynczego zadania aż do GA (obejście: wstrzymanie na czytniku przez niezgłaszanie nowej pracy - operacyjnie w porządku, ale to nie to samo). Wymiarowanie pod milion zadań - milion samodzielnych activities to realne obciążenie trwałości serwera, a zgłaszanie musi być ograniczone tempem, żebyśmy nie zrobili DDoS-u własnej platformie. A semantyka at-least-once oznacza, że Activities muszą być idempotentne, dokładnie jak konsumenci Kafki, więc tu dla nas nic się nie zmienia.

Werdykt: wygrywa tam, gdzie waga jest najwyższa - stan zadania na kryteriach krytycznych (K3 retry, K5 długie zadania, K1 audyt z platformy zamiast z naszego kodu). Przegrywa niżej: K8 (Ważne, ale złagodzone dojrzałością rdzenia) i K2/K9 (Miło mieć). Jedyne zmartwienie na kryterium krytycznym to K4, skalowanie i trwałość - i dokładnie to musi rozstrzygnąć PoC, zanim zapadnie wiążąca decyzja. To nie kandydat, który wygrywa wszystko. To kandydat, który wygrywa tam, gdzie nam najbardziej zależy, i płaci tam, gdzie mamy więcej luzu.

Zestawienie

Legenda: ● natywne / ◐ częściowe albo przez własny kod / ○ brak. Kolumna Waga niesie kontekst - to ona decyduje, nie liczba kropek.

KryteriumWagaA. Kafka (+KIP-932)B. Zwykła kolejkaC. Temporal Std. Activities
K1 Audyt na poziomie rekorduKrytyczne◐¹◐¹●⁶
K2 Priorytetymiło mieć◐⁷
K3 Retry + backoff / DLQKrytyczne◐¹◐²
K4 Skalowanie, bez sufitu partycjiKrytyczne◐⁸
K5 Długo trwające zadaniaKrytyczne
K6 Koszt operacyjnyważne○³●⁴◐⁹
K7 Ścieżka do procesumiło mieć
K8 Dojrzałość / ryzykoważne◐¹⁰
K9 Lock-inmiło mieć●¹¹

Jak to czytać: decyzja nie leży w liczbie ptaszków, tylko w tym, jak układają się względem wagi. Na czterech kryteriach krytycznych - audyt (K1) i retry (K3) mamy dziś, ale jako własny kod, którego chcemy się pozbyć; długie zadania (K5) natywnie robi tylko C; skalowanie (K4) to miejsce, gdzie C jest faktycznie słabszy, i właśnie to ma zweryfikować PoC. Straty C leżą wszystkie na kryteriach o niższej wadze. Wygrywa tam, gdzie waga jest najcięższa, a przegrywa tam, gdzie jest najlżejsza. To jest uzasadnienie, nie arytmetyka.

O metodzie. To ADR w duchu MADR, z sekcją rozważanych opcji i oceną dla każdej z nich. Świadomie używam oceny jakościowej z jawnymi wagami zamiast ważonego wyniku z sumą punktów. Kiedy wybiera się architekturę, wagi i oceny i tak są osądem autora, więc końcowa liczba (7,2 kontra 5,8) sugeruje precyzję, której nie ma, i zaprasza do sporu o drugie miejsce po przecinku zamiast o samą rzecz. Rozrzut ocen względem wagi niesie tę samą informację, bez udawania obiektywności.

Decyzja

Skłaniamy się ku kandydatowi C, Temporal Standalone Activities, z pełną świadomością rachunku.

Nie chodzi o to, że Temporal wygrywa wszędzie. Nie wygrywa - jest cięższy na K4 i droższy na K6. Chodzi o to, jak jego mocne strony układają się na kryteriach krytycznych. Audyt (K1) i retry (K3): dziś spełniamy je własnymi tabelami stanu i retry+cronem, co działa, ale to nasz kod do utrzymania, a Temporal daje oba z platformy i pozwala ten kod skasować. Długie zadania (K5): tylko Temporal robi to natywnie, bez walki z timeoutem. Do tego dochodzi bonus na kryterium “miło mieć”, które będzie zyskiwać na wadze z czasem - ścieżka procesu (K7), gdzie mamy już kilka połączonych zadań, a ta sama Activity działa samodzielnie albo jako krok workflow bez przepisywania. Kontekst przypieczętowuje sprawę: mamy już kompetencje i serwer Temporal, a rdzeń jest u nas sprawdzony. Standalone Activity to węższe API na tym samym silniku, nie nowy silnik.

Koszty, na które świadomie się piszemy: Public Preview (K8, złagodzone dojrzałością rdzenia, ale realne), cięższa trwałość pod przemiałem (K4), zmiana wymiarowania serwera i pewnie ES pod widoczność (K6). Gdyby jedynym celem było odtworzenie dzisiejszego stanu za najmniej pieniędzy, SKIP LOCKED byłby uczciwie lepszym wyborem - nasze obecne tabele stanu i retry przeniosłyby się niemal dosłownie, zero nowej infrastruktury, zero lock-inu. Różnica jest taka, że SKIP LOCKED utrwala nasz kod zarządzania stanem, a Temporal go usuwa, dając przy tym K5 i K7, których baza-jako-kolejka nigdy nie da. A gdyby kiedykolwiek faktycznie zmaterializowało się wiele źródeł zdarzeń i niezależnych konsumentów tych samych danych, Kafka wraca do gry. To nie jest ADR, który mówi, że Temporal jest najlepszy. To taki, który mówi, że dla naszego zestawu wag Temporal wychodzi na prowadzenie, i pokazuje dokładnie, ile za to płacimy.

Konsekwencje

Po stronie plusów kasujemy własny kod audytu/retry/magazynu stanu, dostajemy obsługę długich zadań z platformy, dostajemy ścieżkę do workflow bez przepisywania logiki, a logika domenowa zostaje agnostyczna (niski lock-in dla dzisiejszego zakresu).

Co akceptujemy: zależność od funkcji w statusie Public Preview, w tym brak natywnego wstrzymania pojedynczego zadania aż do GA (złagodzone przez wstrzymanie na czytniku). Trwałość Temporal jako nowe wąskie gardło, wymagające wymiarowania i ograniczania tempa zgłoszeń - backpressure musimy napisać sami. Prawdopodobną potrzebę ES/OpenSearch za widocznością, plus osobną politykę archiwizacji pod wieloletni audyt regulacyjny. Priorytety, które są miękkie (ważone), a nie ścisłe - jeśli biznes kiedyś zechce ścisłych, to osobne kolejki zadań i pule workerów. I lock-in, który rośnie w momencie wejścia w workflow (K7): to, co jest korzyścią, jest też pułapką przy wyjściu.

Kolejny krok to PoC przy reprezentatywnym wolumenie - milion rekordów jednego typu, plus kilkaset typów po kilkadziesiąt każdy - mierzący przede wszystkim obciążenie trwałości Temporal i realną przepustowość przy jednoczesnym zapisywaniu audytu. Tam żyje ryzyko tej architektury, nie w workerach. Żadna wiążąca decyzja przed PoC.

Notatka o tym, jak pracujemy

Nic z tego nie jest jednorazowe. Spisywanie kluczowych decyzji technologicznych w ten sposób - kandydaci, ważone kryteria, koszty, na które się piszemy - to sposób, w jaki prowadzimy je jako coś standardowego. Dzięki temu rozumowanie zostaje na stole, a nie w czyjejś głowie, kolejny krok można podjąć jawnie zamiast bronić go po fakcie, a kiedy za rok wrócimy do tej decyzji, zobaczymy, co wtedy naprawdę wiedzieliśmy. Efektem nie jest sama decyzja. Jest nim decyzja, którą można zaudytować - dokładnie tak, jak oczekujemy audytu każdego rekordu przechodzącego przez system, o którym tu dyskutujemy.


¹ platforma tego nie daje, ale już to mamy zbudowane we własnym kodzie (tabele stanu + licznik prób/cron) - działa, ale to nasze brzemię utrzymania. ² Oracle AQ ma natywny opóźniony retry; nasz obecny mechanizm przenosi się wprost. ³ klaster rośnie o koordynatora share + aktualizację do 4.2. ⁴ zwłaszcza AQ/SKIP LOCKED - zero nowej infrastruktury. ⁶ przeszukiwalne z platformy (kasuje nasz magazyn stanu), ale wymaga ES pod widoczność + politykę archiwizacji pod wieloletni audyt. ⁷ ważone poziomy, nie ścisła kolejka; wstrzymanie pojedynczego zadania dopiero w GA. ⁸ wąskie gardło przenosi się na trwałość Temporal; cięższe niż dopisanie do logu. ⁹ niżej niż ręczne budowanie stanu, ale wymaga zmiany wymiarowania serwera i ES. ¹⁰ Public Preview, ale na dojrzałym rdzeniu Temporal (ten sam wzorzec co workflow, jeden krok). ¹¹ logika w agnostycznej Javie; lock-in pojawia się dopiero po wejściu w workflow (K7).