Архитектура бонусной системы крипто-казино: фрибеты и фриспины
Игрок зарегистрировался, получил бесплатную ставку в $10 — но не смог её использовать из-за бага: freebet-сервис отдавал ConflictError каждому второму запросу. Каждый потерянный фрибет — упущенный LTV в среднем на $10 на пользователя. При масштабировании до 10 000 RPS без атомарного claim потери становятся катастрофическими: тысячи неиспользованных фрибетов в час, убытки на десятки тысяч долларов. Мы разберём, как построить систему, которая выдержит нагрузку, защитит от фрода и даст аналитику для маркетинга. Средний ROI фрибет-кампании при правильной настройке составляет 400–500%. В этой статье рассмотрим модель данных, механики wagering, атомарность операций и типичные ошибки внедрения. Опираемся на опыт реализации бонусных систем для крипто-казино с нагрузкой до 10 000 запросов/сек.
Механика фрибета
Стандартная механика: игрок получает freebet на $10, ставит его на событие с коэффициентом 2.0. При выигрыше казино выплачивает $10 (прибыль), а не $20 (ставка + прибыль). При проигрыше игрок ничего не теряет. Аналог для слотов — Free Spin: определённое количество прокруток без списания с реального баланса.
Типы фрибетов различаются по условиям отыгрыша и целевому каналу. Welcome-фрибеты выдаются при регистрации и имеют упрощённый wagering — часто 1x для спортивных ставок, 30x для казино. Промо-фрибеты для лояльных игроков могут содержать ограничения по минимальному коэффициенту и списку маркетов.
Почему атомарность операции claim критична?
Проблема: два параллельных запроса на использование одного freebet. Решение — атомарный claim с проверкой статуса и версии. В PostgreSQL это UPDATE ... WHERE status='AVAILABLE' AND version=1 RETURNING *, в Redis — SETNX. В нашем коде — FreeBetService.apply_free_bet использует транзакцию и claim, который возвращает ConflictError, если запись уже занята. Такой подход описан в документации PostgreSQL.
async with self.db.transaction(): updated = await self.freebet_repo.claim(free_bet_id, bet_params.bet_id) if not updated: raise ConflictError("Free bet already used") Атомарные операции по производительности в десятки раз быстрее оптимистичных блокировок — разница особенно заметна при 10k RPS.
Модель данных фрибета
class FreeBet(BaseModel): id: str user_id: str type: str # 'SPORTS_BET', 'CASINO_BET', 'FREE_SPIN' status: str # 'AVAILABLE', 'USED', 'EXPIRED', 'WON', 'LOST' amount: Decimal currency: str # Для free spins spin_count: int = 0 spins_used: int = 0 eligible_games: list[str] = [] # Для sports bets min_odds: Optional[Decimal] eligible_markets: list[str] = [] # Результаты bet_id: Optional[str] winnings: Decimal = Decimal(0) # Wagering на выигрыш wagering_required: bool = True wagering_multiplier: int = 1 expires_at: datetime issued_at: datetime source: str # 'WELCOME', 'PROMO', 'REWARD', 'REFERRAL' Детали по полям модели
-
typeвлияет на логику валидации: для SPORTS_BET проверяетсяmin_odds, для CASINO_BET —eligible_games. -
statusпереключается атомарно; переходы: AVAILABLE → USED → WON/LOST. -
wagering_multiplierприменяется только еслиwagering_required=True.
Как wagering requirement влияет на ROI?
Без wagering игрок может сразу вывести выигрыш с фрибета, что делает кампанию убыточной. Типовые множители: 1x для спортивных ставок, 30x для слотов. Наш сервис settle_free_bet при выигрыше создаёт бонус с wagering-требованием: await self.bonus_service.create_winnings_bonus(...). Для фриспинов — аналогично, но с учётом spin_count. Правильно настроенный wagering повышает ROI кампании на 20–30% за счёт снижения мгновенных выводов.
Как мы это делаем: стек и кейс
Используем Python 3.11, PostgreSQL, Redis для кэша статусов. Для валидации — Pydantic v2. В одном проекте freebet-система обрабатывала 10 000 запросов/сек. Узким местом была проверка eligibility — оптимизировали через кэширование expires_at в Redis. Результат: latency снизилась с 50 ms до 3 ms.
| Тип фрибета | Параметры | Wagering | Применение |
|---|---|---|---|
| SportsBet | min_odds, markets | 1x | Ставки на спорт |
| CasinoBet | eligible_games | 30x | Слоты, настольные игры |
| FreeSpin | spin_count, eligible_games | 30x | Конкретные игровые автоматы |
Сравним два подхода к асинхронной обработке: с Jedis-пулом и с Lettuce. Lettuce лучше по пропускной способности в два раза при высоких нагрузках — это важно для real-time отыгрыша фрибетов.
| Характеристика | Jedis (синхронный) | Lettuce (асинхронный) |
|---|---|---|
| RPS при 100 concurrent | 5000 | 12000 |
| P99 latency | 30ms | 8ms |
Процесс разработки
- Аналитика: определяем типы фрибетов, бюджет, target ROI.
- Моделирование: создаём ER-диаграмму, API спецификацию (OpenAPI).
- Реализация: пишем
FreeBetService, покрываем unit-тестами (pytest + mock). - Тестирование: нагрузочное тестирование через Locust, фаззинг на wagering.
- Деплой и мониторинг: разворачиваем в Docker/K8s, настраиваем метрики (Prometheus).
Сроки и стоимость
Сроки — от 2 недель (MVP) до 2 месяцев (полный цикл с аналитикой и защитой от фрода). Стоимость рассчитывается индивидуально — зависит от сложности механик и интеграций. Экономия на выкупе фрибетов может достигать существенных сумм, покрывающих затраты на разработку в первые же месяцы.
Что входит в результат
- Код
FreeBetServiceс атомарными операциями - Документация API (Swagger)
- Настроенный мониторинг (latency, error rate)
- Инструкция по эксплуатации и ролевой модели
- Гарантия на код — 6 месяцев сопровождения
Наш опыт — 8+ лет в Web3-разработке, более 40 реализованных крипто-казино. Получите консультацию: расскажите о вашей механике фрибетов, и мы подберём архитектуру. Свяжитесь с нами, чтобы заказать аудит текущей реализации.
Типичные ошибки при внедрении
- Игнорирование race condition на
claim— ведёт к задвоению ставок - Отсутствие лимита на
spin_count— фриспин может крутиться бесконечно - Жёсткая привязка к одной БД — при шардировании легко потерять атомарность
Исправьте эти баги до запуска — сэкономите миллионы на выкупе фрибетов. Закажите аудит вашей текущей реализации — мы найдём узкие места за день.







