Высоконагруженная платформа для многораундовых аукционов цифровых товаров с реал‑тайм UI.
Этот README включает:
- понимание механики Telegram Gift Auctions;
- явные допущения там, где поведение неочевидно;
- архитектурные решения и почему они безопасны;
- описание ключевых алгоритмов (см. docs/ALGORITHMS.md);
- инструкции по запуску.
Telegram использует многораундовый формат:
- аукцион состоит из нескольких раундов;
- в каждом раунде часть участников получает товар;
- остальные продолжают участие в следующих раундах;
- есть защита от «снайперских» ставок в последнюю секунду.
- Раунды: у аукциона фиксированы
roundsCountиitemsPerRound. - Победители: победители — топ N по сумме ставки в текущем раунде.
- Перенос ставок: проигравшие переносят ставку в следующий раунд; если раунд последний — средства возвращаются.
- Anti‑snipe: если ставка попадает в топ в последние
antiSnipeWindowSeconds, раунд продлевается наantiSnipeExtensionSeconds. - Баланс: ставка блокирует средства (
available -> held), окончательное списание происходит при закрытии раунда.
- Все денежные операции идут через TX‑воркеры (Redis Streams): единый вход для команд
hold_bid,close_round,deposit,set_balance. - Идемпотентность: все мутирующие запросы требуют
Idempotency-Key— защита от повторной доставки и сетевых ретраев. - Redis как быстрый слой состояния: лидерборд и балансы обновляются атомарно.
- Леджер в БД:
LedgerTransactionиLedgerEntryфиксируют каждую операцию и позволяют проверять целостность.
Почему так: при одновременных ставках нельзя допускать двойных списаний или потери баланса; выбранный путь гарантирует воспроизводимость и проверяемость финансовых операций.
Порядок закрытия:
- блокировка раунда (distributed lock),
- формирование победителей по лидерборду,
- списание у победителей и возвраты/перенос у проигравших,
- фиксация
GiftAllocation, - обновление статусов в БД и Redis.
Если ставка попадает в топ в последние секунды, раунд продлевается — это снижает эффект «последнего клика» и повышает честность участия.
Встроен режим нагрузки:
- админ может запускать/останавливать стресс‑ботов,
- метрики (latency, error rate, rps) отображаются в админ‑панели,
- есть кнопка очистки ботов и их ставок.
Это позволяет проверить систему под конкурентной нагрузкой и в конце раунда.
- повторные запросы одной операции (идемпотентность);
- одновременные ставки в последние секунды (anti‑snipe);
- закрытие раунда при конкурирующих запросах;
- недостаточный баланс и частичное удержание средств;
- повторное закрытие раунда (идемпотентные ответы).
UI предназначен для демонстрации логики:
- создание аукциона (без картинок),
- участие в аукционе (ставки),
- ставки от ботов,
- просмотр состояния и результатов,
- просмотр баланса.
Дизайн не оценивается, важна корректность логики.
flowchart LR;
web["Web (Qwik)"];
app["App (Elysia)"];
redis[(Redis)];
mongo[(MongoDB)];
sozu["Sōzu reverse-proxy"];
web <--> app;
app --> redis;
app --> mongo;
sozu --> web;
sozu --> app;
| Сервис | Порт | Описание |
|---|---|---|
| web | 4173 | Qwik UI (static) |
| app | 3000 | HTTP API + WebSocket + воркеры |
| sozu | 8080 | reverse‑proxy (единая точка входа) |
Вход в UI: http://localhost:8080
- Runtime: Node.js 20+
- Frontend: Qwik + TypeScript
- Backend: Elysia (HTTP + WebSocket + воркеры)
- Database: MongoDB + Prisma
- Cache/Streams: Redis (вспомогательный слой)
- Monorepo: Yarn Workspaces + TurboRepo
Выбрал Elysia по практическим причинам:
- высокая производительность и низкий overhead на запросах;
- удобная типизация end‑to‑end (контракты, валидация, ответы);
- простой и предсказуемый plugin‑подход без тяжёлой инфраструктуры;
- встроенная поддержка WebSocket и middleware;
- одинаковая модель для Node.js и Bun, без форков кода.
Это снижает стоимость поддержки и упрощает тестирование логики аукциона.
corepack enable
yarn install
cp .env.example .env
docker compose up -ddocker compose build
docker compose up -d| Переменная | Описание | Пример |
|---|---|---|
API_PORT |
Порт HTTP сервера | 3000 |
DATABASE_URL |
MongoDB connection string | mongodb://localhost:27017/goboli?replicaSet=rs0 |
REDIS_URL |
Redis connection string | redis://localhost:6379 |
TX_COMMANDS_STREAM |
Redis stream для команд | tx:commands |
REPLY_PREFIX |
Префикс reply streams | reply: |
CORS_ORIGIN |
Разрешённые origin | * |
| Переменная | Описание | Пример |
|---|---|---|
TX_PERSIST_STREAM |
Stream для персистенции | tx:persist |
TX_GROUP |
Consumer group | tx-workers |
CONSUMER_NAME |
Имя консьюмера | tx-1 |
TX_PERSIST_GROUP |
Consumer group для persist | tx-persist |
TX_PERSIST_CONSUMER_NAME |
Имя консьюмера persist | tx-persist-1 |
ANTI_SNIPE_WINDOW_SECONDS |
Окно anti‑snipe | 15 |
ANTI_SNIPE_EXTENSION_SECONDS |
Продление при anti‑snipe | 10 |
ROUND_SWEEP_INTERVAL_MS |
Интервал проверки раундов | 1000 |
ROUND_SWEEP_BATCH_SIZE |
Размер batch | 25 |
Полная спецификация: services/app/openapi.yaml
Markdown версия: docs/OPENAPI.md