docs(snapshot): sync workspace documentation

This commit is contained in:
u1
2026-03-29 13:14:30 +02:00
parent 93a6e4aa2f
commit 0d3d110026
37 changed files with 8500 additions and 123 deletions

243
dlob-services.md Normal file
View File

@@ -0,0 +1,243 @@
# Serwisy DLOB na VPS (k3s / `trade-staging`)
Ten dokument opisuje aktualny, live runtime DLOB na `mevnode_bot` w namespace `trade-staging`.
## Czy legacy read-path pracuje na VPS?
Nie. W aktywnym stacku nie ma już starej ścieżki REST + backend workers.
Obecny read-path jest oparty o:
- `dlob-publisher-hot`
- `dlob-publisher-all`
- `dlob-redis`
- `dlob-hot-redis-to-postgres-raw-writer`
- `dlob-hot-postgres-to-postgres-derived-writer`
- `dlob-all-redis-to-postgres-derived-writer`
- `postgres`
- `hasura`
## Czy na VPS jest GraphQL/WS dla stats i orderbook?
Tak. GraphQL i websocket subscriptions wystawia **Hasura**, a źródłem danych są tabele pochodne w PostgreSQL.
Z zewnątrz korzystamy przez frontend (proxy) pod:
- HTTP: `https://trade.rv32i.pl/graphql`
- WS: `wss://trade.rv32i.pl/graphql`
Najważniejsze tabele live dla UI:
- `dlob_hot_derived_latest`
- `dlob_hot_derived_ts`
- `dlob_all_derived_latest`
- `dlob_all_derived_ts`
- `drift_ticks` dla wykresów
## TL;DR: kto co robi
### `dlob-publisher-hot`
- Rola: utrzymuje “żywy” DLOB dla hot-setu marketów i publikuje raw snapshoty do Redis pod `dlob-hot:*`.
- Wejście: Solana RPC/WS/gRPC z secreta `trade-dlob-rpc`, Drift SDK, `PERP_MARKETS_TO_LOAD`.
- Wyjście: bieżący stan orderbooka do `dlob-redis`.
### `dlob-hot-redis-to-postgres-raw-writer`
- Rola: zapisuje kanoniczny raw payload `hot` z Redis do PostgreSQL.
- Wejście: `dlob-hot:*`.
- Wyjście:
- `dlob_hot_snapshot_latest`
- `dlob_hot_snapshot_ts`
### `dlob-hot-postgres-to-postgres-derived-writer`
- Rola: normalizuje warstwę `hot` na derived tables dla UI i downstream processing.
- Wejście: raw hot z PostgreSQL.
- Wyjście:
- `dlob_hot_derived_latest`
- `dlob_hot_derived_ts`
### `dlob-publisher-all`
- Rola: utrzymuje szeroki, pełny feed rynku i publikuje go do Redis pod `dlob-all:*`.
- Wejście: ten sam upstream Solana/Yellowstone co `hot`, ale bez ograniczenia do shortlisty.
- Wyjście: pełny stan rynku do `dlob-redis`.
### `dlob-all-redis-to-postgres-derived-writer`
- Rola: buduje tylko warstwę pochodną dla `all`, bez przechowywania raw append-only.
- Wejście: `dlob-all:*`.
- Wyjście:
- `dlob_all_derived_latest`
- `dlob_all_derived_ts`
### `dlob-redis`
- Rola: szybki cache i punkt wymiany danych między publisherami i writerami.
- Uwagi: to nie jest trwałe źródło prawdy; trwałość daje PostgreSQL.
### `postgres` + `hasura`
- `postgres`: trwałe składowanie raw `hot`, derived `hot/all`, `drift_ticks`, tokenów i danych aplikacyjnych.
- `hasura`: query + subscription plane dla UI.
### `trade-ingestor`
- Rola: zasila `drift_ticks` i ścieżkę wykresów.
- Uwaga: to osobny app-ingest path, nie zamiennik dla DLOB publisherów.
## Jak to się spina (przepływ danych)
1. `dlob-publisher-hot` i `dlob-publisher-all` budują live state z prywatnego RPC/WS/gRPC i publikują snapshoty do `dlob-redis`.
2. `dlob-hot-redis-to-postgres-raw-writer` zapisuje raw `hot` do PostgreSQL.
3. `dlob-hot-postgres-to-postgres-derived-writer` tworzy znormalizowaną warstwę `hot`.
4. `dlob-all-redis-to-postgres-derived-writer` tworzy znormalizowaną warstwę `all`.
5. `hasura` wystawia derived tables jako GraphQL/WS dla UI.
6. `trade-api` i `trade-ingestor` obsługują osobno chart/ticks path.
## Co UI i API biorą z tej warstwy
- DLOB / L2 / stats: z `dlob_hot_derived_*` i `dlob_all_derived_*` przez Hasurę.
- Chart / ticks: z `drift_ticks` przez `trade-api`.
- Metryki depth/slippage nie są już osobnymi workerami w k3s; są liczone z danych L2/derived w aktualnej ścieżce aplikacji.
## Co to jest L1 / L2 / L3 (orderbook)
- `L1`: najlepszy bid i ask.
- `L2`: zagregowane poziomy cenowe `{ price, size }`.
- `L3`: pojedyncze zlecenia.
W obecnym stacku live warstwa aplikacyjna korzysta przede wszystkim z pochodnej reprezentacji L2 zapisanej w PostgreSQL (`bids_norm`, `asks_norm` i metryki top-of-book), a nie z osobnego REST serwera DLOB.
## Szybka diagnostyka na VPS
```bash
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get deploy | grep -E 'trade-|dlob-|hasura'
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/dlob-publisher-hot --tail=80
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/dlob-publisher-all --tail=80
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/dlob-hot-redis-to-postgres-raw-writer --tail=80
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/dlob-hot-postgres-to-postgres-derived-writer --tail=80
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/dlob-all-redis-to-postgres-derived-writer --tail=80
```
## Słownik pojęć (bid/ask/spread i metryki)
### Podstawy orderbooka
- **Bid**: zlecenia kupna (chęć kupna). W orderbooku “bid side”.
- **Ask**: zlecenia sprzedaży (chęć sprzedaży). W orderbooku “ask side”.
- **Best bid / best ask**: najlepsza (najwyższa) cena kupna i najlepsza (najniższa) cena sprzedaży na topie księgi (L1).
- **Spread**: różnica pomiędzy `best_ask` a `best_bid`. Im mniejszy spread, tym “taniej” wejść/wyjść (mniej kosztów natychmiastowej realizacji).
- **Mid price**: cena “po środku”: `(best_bid + best_ask) / 2`. Używana jako punkt odniesienia do bps i slippage.
- **Level**: pojedynczy poziom cenowy w L2 (np. `price=100.00`, `size=12.3`).
- **Size**: ilość/płynność na poziomie (zwykle w jednostkach “base asset”).
- **Base / Quote**:
- `base` = instrument bazowy (np. SOL),
- `quote` = waluta wyceny (często USD).
## Kolory w UI (visualizer)
- `bid` / “buy side” = zielony (`.pos`, `#22c55e`)
- `ask` / “sell side” = czerwony (`.neg`, `#ef4444`)
- “flat” / brak zmiany = niebieski (`#60a5fa`) — używany m.in. w “brick stack” pod świecami
### Jednostki i skróty
- **bps (basis points)**: 1 bps = 0.01% = `0.0001`. Np. 25 bps = 0.25%.
- **USD**: u nas wiele wartości jest przeliczanych do USD (np. `size_base * price`).
### Metryki “stats” (np. `dlob_stats_latest`)
- `spread_abs` (USD): `best_ask - best_bid`.
- `spread_bps` (bps): `(spread_abs / mid_price) * 10_000`.
- `depth_levels`: ile leveli (topN) z każdej strony braliśmy do liczenia “depth”.
- `depth_bid_base` / `depth_ask_base`: suma `size` po topN levelach bid/ask (w base).
- `depth_bid_usd` / `depth_ask_usd`: suma `size_base * price` po topN levelach (w USD).
- `imbalance` ([-1..1]): miara asymetrii płynności:
- `(depth_bid_usd - depth_ask_usd) / (depth_bid_usd + depth_ask_usd)`
- >0 = relatywnie więcej płynności po bid, <0 = po ask.
- `oracle_price`: cena z oracla (np. Pyth) jako punkt odniesienia.
- `mark_price`: mark z rynku/perp (cena referencyjna dla rozliczeń); różni się od oracle/top-of-book.
### Metryki “depth bands” (np. `dlob_depth_bps_latest`)
- `band_bps`: szerokość pasma wokół `mid_price` (np. 5/10/20/50/100/200 bps).
- `bid_usd` / `ask_usd`: płynność po danej stronie, ale **tylko z poziomów mieszczących się w oknie ±`band_bps`** wokół mid.
- `imbalance`: jak wyżej, ale liczony per band.
### Metryki “slippage” (np. `dlob_slippage_latest`)
To jest symulacja gdybym teraz zrobił market order o rozmiarze X na podstawie L2.
- `size_usd`: docelowy rozmiar zlecenia w USD.
- `vwap_price`: średnia cena realizacji (Volume Weighted Average Price) dla symulowanego fill.
- `impact_bps`: koszt/odchylenie względem `mid_price` wyrażone w bps (zwykle na bazie `vwap` vs `mid`).
- `worst_price`: najgorsza cena dotknięta podczas zjadania kolejnych leveli.
- `filled_usd` / `filled_base`: ile realnie udało się wypełnić (może być < docelowego, jeśli brakuje płynności).
- `fill_pct`: procent wypełnienia (100% = pełny fill).
- `levels_consumed`: ile leveli zostało zjedzonych podczas fill.
### Metadane czasu (“świeżość”)
- `ts`: timestamp źródła (czas snapshotu).
- `slot`: slot Solany, z którego pochodzi snapshot (monotoniczny numer czasu chaina).
- `updated_at`: kiedy nasz pipeline zapisał/odświeżył rekord w DB (do oceny, czy dane świeże).
## Redis retention na poziomie k3s i Redisa
Dla `mevnode-bot` retencja Redis jest ustawiana na poziomie `k3s` przez cleaner `CronJob`, a nie przez natywny `TTL` kluczy.
Powod:
- publishery DLOB zapisują do Redis w modelu `latest-only`
- kolejne `SET` nadpisuje poprzedni snapshot tego samego klucza
- zwykly Redis `TTL` bylby kasowany przez kolejne zapisy, wiec nie daje poprawnej retencji semantycznej
Ustawienie operacyjne:
- `hot` (`dlob-hot:`): retencja `15m`
- `all` (`dlob-all:`): retencja `1h`
- cleaner uruchamiany z `CronJob` co `5m`
Sposob dzialania cleanera:
- skanuje klucze `dlob-hot:*` oraz `dlob-all:*`
- odczytuje `ts`, a jesli go nie ma to `updatedAtTs`, z payloadu JSON
- usuwa tylko klucze starsze niz zadany prog wieku
To oznacza:
- aktywne markety pozostaja stale obecne w Redisie
- osierocone klucze po zmianie konfiguracji znikaja automatycznie
- Redis pozostaje warstwa `raw latest snapshot`, bez normalizacji payloadu
Aktualny stan Redis, istotny dla tej decyzji:
- klucze nie maja automatycznej expiracji (`TTL=-1`)
- `maxmemory-policy=noeviction`
- `aof_enabled=0`
Aktualny stan publisherow na poziomie k3s:
- `hot` i `all` sa obecnie rozrozniane przez markety, `REDIS_CLIENT`, `DLOB_SOURCE` i persistent store
- maja osobne `priorityClassName`
- `resources.requests/limits` sa ustawione dla writerow `hot`; publishery maja rozroznienie priorytetow na poziomie klastra
## Priorytety na poziomie k3s
Na poziomie `k3s` rozroznienie `hot` i `all` jest ustawione przez osobne `PriorityClass`.
Parametry:
- `hot`:
- deployment: `dlob-publisher-hot`
- `priorityClassName: dlob-hot`
- wartosc `PriorityClass`: `100000`
- `all`:
- deployment: `dlob-publisher-all`
- `priorityClassName: dlob-all`
- wartosc `PriorityClass`: `50000`
Znaczenie operacyjne:
- `hot` ma wyzszy priorytet schedulera i preemption niz `all`
- przy presji na node `hot` powinien byc traktowany jako sciezka krytyczna
- `all` pozostaje feedem nizszego priorytetu dla pelnego coverage rynku
Zakres tej parametryzacji:
- to jest tylko priorytet na poziomie schedulera `k3s`
- nie ustawia jeszcze `resources.requests` ani `resources.limits`
- nie zmienia logiki publishera, tylko kolejnosc traktowania workloadu przez klaster