345 lines
7.8 KiB
Markdown
345 lines
7.8 KiB
Markdown
# 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`
|