# Broker API Performance Optimization ## Проблема - `GET /api/v1/broker/accounts/:id/portfolio` — **~5s** - `GET /api/v1/broker/accounts/:id/positions` — **1-3s** ## Диагностика ### 1. Мёртвый код в `getPortfolio` `buildInstrumentMap` делает N gRPC вызовов `GetInstrumentBy` (по одному на каждый `instrumentUid` в портфеле), но результат **не используется** в `mapBrokerPortfolio`. Это чистое WASTE. ### 2. Блокирующий instrument enrichment в `getPositions` `buildInstrumentMap` вызывает `findByInstrumentUid` для каждого инструмента через gRPC. Даже при `Promise.allSettled`, все вызовы проходят через единый `p-queue` с 5 req/s. Для 10-20 позиций = 2-4 секунды ожидания в очереди. Из всех полей `GetInstrumentBy` `mapBrokerPosition` использует только `name` — `ticker`, `classCode`, `instrumentType` уже есть в ответе `PortfolioPosition` (proto fields 32, 33, 2). ### 3. Единый rate limiter Один `p-queue` на 5 req/s для всех gRPC вызовов. Instrument lookups конкурируют за очередь с portfolio/operations запросами. ## Изменения ### Change 1: Убрать `buildInstrumentMap` из `getPortfolio` **Файлы:** `broker-portfolio.service.ts` Удалить вызов `buildInstrumentMap` и передачу `instruments` в `mapBrokerPortfolio`. Исключить `BrokerInstrumentsService` из зависимостей (если не используется больше нигде в сервисе). ### Change 2: Instrument enrichment из кэша без блокировки **Файлы:** `broker-portfolio.service.ts`, `cache.service.ts` - `buildInstrumentMap` пытается достать данные из кэша без триггера gRPC - Если данных нет — возвращаем `null` для имени (не блокируем ответ) - Новый метод `CacheService.getIfPresent(key)` — проверяет кэш без вызова fetchFn ### Change 3: Разделить rate limiter на 3 очереди **Файлы:** `tbank-client.service.ts` Заменить единый `p-queue` на: | Очередь | Rate | Сервисы | |---|---|---| | `operationsQueue` | 5 req/s | OperationsService | | `instrumentsQueue` | 20 req/s | InstrumentsService | | `usersQueue` | 5 req/s | UsersService | Метод `callUnary` принимает параметр `queueName`. Клиентские методы выбирают очередь по типу сервиса. ### Change 4: Увеличить rate limit по умолчанию **Файлы:** `configuration.ts` `rateLimitPerSecond` по умолчанию: 5 → 20. ### Change 5: Shared cache сырого `GetPortfolio` **Файлы:** `broker-portfolio.service.ts` Оба эндпоинта вызывают `GetPortfolio` с одинаковым `accountId`. Кэшировать сырой ответ отдельно (TTL 60s, ключ `tbank:raw-portfolio:{accountId}`), чтобы второй запрос в том же окне не дублировал вызов. ## Ожидаемый эффект | Endpoint | До | После | |---|---|---| | Portfolio | ~5s | ~0.3-0.5s (2 параллельных gRPC, без instrument enrichment) | | Positions | 1-3s | ~0.2-0.3s (1 gRPC GetPortfolio, name из кэша / null) | ## Этапы реализации (по порядку) 1. Убрать `buildInstrumentMap` из `getPortfolio` 2. `CacheService.getIfPresent()` для instrument enrichment в positions 3. Разделить rate limiter на очереди 4. Увеличить rate limit по умолчанию 5. Shared cache сырого GetPortfolio Каждый этап отдельным коммитом. ## Acceptance Criteria 1. Portfolio endpoint < 1s при тёплом кэше account/instrument, < 1.5s при холодном 2. Positions endpoint < 0.5s при тёплом кэше, < 1s при холодном 3. Все существующие тесты проходят 4. Instrument name показывается если есть в кэше, иначе `null`