Files
trade-doc/dlob-services.md

10 KiB
Raw Permalink Blame History

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

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 są ś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