← все проекты
Jun 2019 — Dec 2022

Bequant

Fullstack-разработчик / Lead

Институциональная криптобиржа с собственным matching-движком. Торговый терминал на 10K событий в минуту.

Метрики

10K/min
Событий рынка в минуту в UI
−45%
Нагрузки на backend
50K+
Активных пользователей в пике
45→8 min
MTTR production-инцидентов

Контекст

Bequant — институциональная криптобиржа. Не клон Robinhood, а площадка для маркет-мейкеров и prop-десков с собственной quant-инфраструктурой. Matching-движок — свой. Торговый терминал должен ощущаться как Bloomberg, не как Binance.

Когда я пришёл во фронт, у продукта было три неудобных проблемы: стакан рендерил 4-5 FPS под нагрузкой, экран портфеля делал по 14 REST-запросов на загрузку, а загрузка KYC-документов была переписана 3 раза в разных частях UI.

Что я сделал

  • Переписал стакан и feed трейдов на WebSocket-диффы + виртуализацию. Держим 10 000 событий рынка в минуту без потерь кадров. Throttling на React-редьюсере, не на сокете — данные не теряются, просто батчатся UI-апдейты.
  • Спроектировал GraphQL-агрегацию поверх существующих REST-сервисов. Портфельный экран: с 14 round-trip → 1 GraphQL-запрос. Нагрузка на backend на этом маршруте упала на 45% за 30 дней замеров.
  • Внедрил многоуровневое Redis-кэширование: rate-limits на edge, market data на API-gateway, сессии — в service-layer. Держали 50K активных пользователей в пике без деградации backend.
  • Перевёл CI с самописного shell-пайплайна на GitLab CI с параллельным запуском тестов. 800+ юнит-тестов и Cypress E2E теперь идут 9 минут вместо 3 часов.
  • Поднял Prometheus + Grafana и PagerDuty-алерты на SLO. MTTR production-инцидентов снизился с 45 до 8 минут — большая часть выигрыша в том, чтобы за первые 2 минуты понимать, какой сервис сломался.
  • Безопасность: OAuth 2.0, 2FA, защита от JWT-replay, CSRF на state-changing эндпоинтах, клиентское AES-GCM шифрование KYC-документов до загрузки в S3.

Технические решения

Диффы стакана: движок шлёт дельты, не снапшоты. UI держит канонический стакан в нормализованном Redux-слайсе и применяет диффы одним dispatch на анимационный кадр. Re-render — через React.memo с кастомным equality по парам price/size.

GraphQL — прагматичный выбор: REST-сервисы остались, GraphQL — тонкий BFF на Apollo. Без миграционного налога, выигрыш сразу.

Cypress для E2E, хотя команда тогда смотрела в сторону Playwright — time-travel debugger и встроенный network stubbing сэкономили часы на flaky-тестах против matching-движка.

Клиентское шифрование KYC: ключи через PBKDF2 из пароля юзера, на сервер не уходят. Если S3 утечёт — документы нечитаемы. Trade-off: при reset пароля документы не восстановить — это прямо в UI прописано.

Из кода

graphql
GraphQL-агрегация портфеля
query Portfolio($userId: ID!) {
  user(id: $userId) {
    id
    balances {
      currency
      free
      locked
      btcEquivalent
    }
    openOrders {
      id
      symbol
      side
      price
      size
      filled
      createdAt
    }
    recentTrades(limit: 20) {
      id
      symbol
      side
      price
      size
      fee
      executedAt
    }
    pnl(timeframe: D1) {
      realized
      unrealized
      percentChange
    }
  }
}
# 1 round-trip replaces 14 REST calls.
typescript
Редьюсер диффов стакана
type OrderBookDiff = {
  bids: [price: string, size: string][];  // size="0" means delete
  asks: [price: string, size: string][];
  sequence: number;
};

const applyDiff = (state: OrderBookState, diff: OrderBookDiff) => {
  if (diff.sequence !== state.sequence + 1) {
    // Out-of-order or gap: resync via REST snapshot.
    return { ...state, needsResync: true };
  }
  for (const [price, size] of diff.bids) {
    if (size === "0") delete state.bids[price];
    else state.bids[price] = size;
  }
  for (const [price, size] of diff.asks) {
    if (size === "0") delete state.asks[price];
    else state.asks[price] = size;
  }
  state.sequence = diff.sequence;
};

Результат

Терминал держит институциональную нагрузку. Backend-косты ощутимо просели после GraphQL-консолидации. Релиз-цикл — с 3-часового ритуала до 25-минутной фоновой рутины.

Менторил 3 джунов; двое выросли до middle за год.

Стек

Node.jsTypeScriptReactReduxGraphQL (Apollo)RESTWebSocketMongoDBRedisKubernetesAWSELK StackCypress