# Data ingest strategy ## Cel Zachowac niski latency dla logiki tradingowej i read-side, ale rownoczesnie miec trwala persystencje danych do UI, replayu, debugowania i analityki. Nie robimy modelu `DB-first` jako jedynej sciezki przetwarzania. ## Rekomendowany model Stosujemy `dual-path`: 1. `hot path` `Yellowstone gRPC / Agave RPC -> consumer -> Redis / internal state -> strategy / read-side` 2. `durable path` `ten sam consumer -> Postgres -> UI / Hasura / replay / debug` Opcjonalnie pozniej: 3. `history / analytics path` `consumer lub batch exporter -> ClickHouse` ## Dlaczego nie DB-first Model: `gRPC / RPC -> DB -> dopiero potem przetwarzanie` nie jest dobry jako glowna sciezka, bo: - dodaje latency do kazdego eventu - uzaleznia ingest od kondycji bazy - pogarsza odpornosc calego pipeline - zwieksza write amplification przy duzym wolumenie zdarzen - utrudnia low-latency strategie i read-side DB ma byc miejscem persystencji i read modelu, nie bramka przed logika runtime. ## Rola Yellowstone gRPC Yellowstone ma byc glownym zrodlem `live feed`: - account updates - tx updates - slot updates - program updates Sciezka: `Yellowstone -> consumer -> hot state` oraz rownolegle: `Yellowstone -> normalization / persistence -> Postgres` Yellowstone nie powinien byc uzalezniony od tego, czy Postgres aktualnie pisze szybko. ## Rola Agave RPC RPC nie jest naturalnym feedem eventowym. RPC powinno sluzyc glownie do: - point reads - bootstrapu klienta - recovery - getAccountInfo / getMultipleAccounts - getHealth / getSlot - write path dla tx, jesli nie idziemy przez TPU/Jito RPC nie powinno byc traktowane jak glowny event stream do zrzucania wszystkiego do DB. ## Rola Redis Redis powinien byc `hot state layer`: - szybki stan DLOB - cache - fanout wewnetrzny - ewentualnie stream / queue dla workerow To jest warstwa pod: - strategy engine - orderbook read-side - szybkie projekcje Nie trzymamy tam historii jako source of truth. ## Rola Postgres Postgres powinien byc `durable read model`: - dane dla UI - dane dla Hasury - znormalizowane ticki - pochodne tabele DLOB - stan aplikacji - konfiguracja - historia operacyjna Postgres jest dobry do: - frontend - dashboard - replay lekkiego zakresu - audyt aplikacyjny ## Kiedy ClickHouse ClickHouse dodajemy dopiero, gdy: - wolumen raw feedu bedzie duzy - potrzebna bedzie ciezsza analityka historyczna - bedziemy chcieli trzymac dluzsza historie eventow i tx Na obecnym etapie nie jest konieczny do uruchomienia runtime. ## Docelowy przeplyw ```text mevnode_sol Agave RPC Yellowstone gRPC | v mevnode_bot consumer |\ | +--> Redis -> DLOB/read-side -> strategy/workers | +----> Postgres -> Hasura/API -> frontend ``` ## Zasada architektoniczna - `gRPC / RPC` dostarcza dane do runtime - `Redis` utrzymuje szybki stan operacyjny - `Postgres` utrzymuje trwaly stan i read model - `frontend` i `Hasura` czytaja z Postgresa - `strategy` i `workers` nie czekaja na zapis do DB, aby przetwarzac dane ## Rekomendacja dla projektu Na teraz: 1. `Yellowstone -> consumer -> Redis` 2. `consumer -> Postgres` 3. `Hasura / API / UI <- Postgres` 4. `strategy / read-side <- Redis` 5. `RPC` zostawic do bootstrapu, point reads i write path To daje najlepszy kompromis: - niski latency - odpornosc runtime - sensowna persystencja - dobra baza pod UI i replay ## Ocena obecnego ukladu na k3s Obecny runtime na `mevnode_bot / trade-staging` jest tylko czesciowo zgodny z docelowym modelem. ### Co jest zgodne - `postgres` jest postawiony jako `StatefulSet`, wiec nadaje sie na trwały storage - `dlob-redis` jest osobna lekka usluga i pasuje do roli cache / hot state - `hasura` i `trade-api` siedza nad `Postgresem`, co jest poprawne dla durable read modelu - `frontend` korzysta z `API/Hasury`, a nie bezposrednio z `Redis` ### Co nie jest zgodne - `dlob-publisher` i `dlob-server` nie sa zdrowe, wiec hot path nie jest operacyjnie domkniety - `dlob-publisher` jest podpiety do `gRPC`, ale nie jest glownym i pewnym writerem do `Postgresa` - `trade-ingestor` nie ingestuje danych bezposrednio z chain source, tylko polluje dane pochodne z `Hasury` - persistent path jest wiec dzisiaj czesciowo odwrocony: - `derived view -> ingest -> Postgres` - zamiast: - `chain source -> normalize -> Postgres` ## Plan zmian ### Cel koncowy Chcemy dojsc do ukladu: ```text Yellowstone gRPC / Agave RPC | v consumer |\ | +--> Redis -> hot state / workers / strategy | +----> Postgres -> Hasura / API / frontend / replay ``` ### Etap 1. Naprawa hot path Najpierw trzeba przywrocic dzialanie: - `dlob-publisher` - `dlob-server` Bez zdrowego publishera nie ma sensu budowac dalszej warstwy `Redis = hot state`. Zakres: - ustabilizowac `Agave RPC` i `Yellowstone` - doprowadzic `dlob-publisher` do `Ready` - potwierdzic, ze `Redis` dostaje aktualny stan - doprowadzic `dlob-server` do `Ready` ### Etap 2. Uczynic publisher glownym consumerem danych chain Obecnie `trade-ingestor` korzysta z danych pochodnych z `Hasury`. To trzeba zmienic tak, aby glownym miejscem wejscia danych byl komponent konsumujacy: - `Yellowstone gRPC` - `Agave RPC` Rekomendacja: - rozszerzyc `dlob-publisher` - albo wydzielic nowy `market-consumer` ale w obu przypadkach komponent ma robic: - odczyt live z `Yellowstone` - wymagane point reads z `RPC` - normalizacje danych - jednoczesny zapis do `Redis` i `Postgresa` ### Etap 3. Ustalic twardy podzial rol Redis / Postgres #### Redis Do `Redis` trafia tylko to, co potrzebne jako szybki stan runtime: - latest DLOB state - latest L2 - quick cache dla workerow - fanout do read-side i strategii - dane z TTL lub dane odtwarzalne `Redis` nie jest source of truth. #### Postgres Do `Postgresa` trafia trwaly model danych: - znormalizowane ticki - znormalizowane snapshoty rynku - pochodne tabele pod `Hasure` - historia operacyjna - stan aplikacji `Postgres` jest source of truth dla aplikacji i UI. ### Etap 4. Przebudowac worker path Docelowy model dla workerow: - `Redis in` - `Postgres out` To znaczy: - workery czytaja szybki stan z `Redis` - licza projekcje - zapisują wynik do `Postgresa` Dotyczy to szczegolnie: - `dlob-worker` - `dlob-depth-worker` - `dlob-slippage-worker` ### Etap 5. Wygasic obecny odwrocony ingest Obecny `trade-ingestor` zapisuje dane do `Postgresa` na podstawie danych juz pochodnych z `Hasury`. To nie powinno byc glowna sciezka ingestu. Mozliwe role po zmianie: - backfill - sanity check - fallback - testowy importer Ale nie: - glowny chain ingest ### Etap 6. Utrwalic read model pod UI Po zmianach: - `Hasura` czyta tylko z `Postgresa` - `trade-api` czyta tylko z `Postgresa` - `frontend` czyta przez `API/Hasure` Frontend nie powinien zalezec od `Redis`. ### Etap 7. Zostawic RPC w waskiej roli `Agave RPC` zostawiamy do: - bootstrapu - point reads - recovery - write path dla tx Nie robimy z `RPC` glownej magistrali trwałego ingestu. ## Plan wdrozenia krok po kroku 1. Naprawic `dlob-publisher` i `dlob-server`. 2. Potwierdzic, ze `Redis` jest zasilany poprawnym hot state. 3. Rozszerzyc publisher albo dodac nowy consumer tak, aby pisal rownolegle do: - `Redis` - `Postgresa` 4. Zdefiniowac schemat tabel normalized data w `Postgresie`. 5. Przepiac workery na model: - read z `Redis` - write do `Postgresa` 6. Zostawic `Hasure` i `trade-api` jako warstwe odczytowa nad `Postgresem`. 7. Zdegradowac `trade-ingestor` z roli glownego ingestu do roli pomocniczej albo go usunac z glownej sciezki. ## Rekomendacja praktyczna Najmniejsza sensowna zmiana to nie budowac od razu nowego systemu od zera, tylko: 1. naprawic obecny `dlob-publisher` 2. dodac mu zapis do `Postgresa` 3. stopniowo wyprowadzac `trade-ingestor` z glownego path To jest najmniej ryzykowna droga do modelu: - `Redis = cache / hot state` - `Postgres = persistent normalized store`