feat(visualizer): add runtime architecture diagram
This commit is contained in:
344
doc/data-ingest-strategy.md
Normal file
344
doc/data-ingest-strategy.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user