feat: добавил инструкции к SDD подходу
All checks were successful
CI / ci (push) Successful in 3m13s

This commit is contained in:
Sergey Krylov 2026-06-19 07:38:33 +03:00
parent d8b3886130
commit 1dc27a6e9b
3 changed files with 0 additions and 88 deletions

View File

@ -1,88 +0,0 @@
# 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`