Первые сутки сервис падал каждый час, но сейчас система выдерживает пиковые запросы без даунтайма.
Исходная задача
Мне нужно было автоматизировать процесс сбора спортивных данных (NFL, NBA, UFC) с dingerodds для дальнейшего анализа и обучения моделей. Источник выбран из-за:
доступного REST API (пример запроса ниже)
свежих коэффициентов и статистики
наличия исторических данных
GET /api/v1/events/upcoming?market=moneyline&sport=baseball
Authorization: Bearer <token>
Но оказалось, что API отваливается под минимальной нагрузкой и плохо обрабатывает батчи (особенно GET /events/history
).
Проблемы
Rate-limits не задокументированы, но явно действуют: после ~60 запросов в минуту — 429.
API отдает 502/504 на пиковых батчах.
Нет webhook или pub/sub, всё надо опрашивать вручную.
Нестабильный ответ JSON — иногда
odds
приходят какnull
, иногда как{}
.
Архитектура решения
Обёртка над API (Python, asyncio, httpx)
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1))
async def fetch(url: str, headers: dict):
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(url, headers=headers)
response.raise_for_status()
return response.json()
Circuit Breaker через кастомную реализацию с fallback
Поддержка конкурентных запросов (asyncio.gather)
Встроенные прокси-пулы с ротацией IP
В случае fail → log в Loki, push alert в Telegram
CI/CD pipeline (GitLab)
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t registry/app:$CI_COMMIT_SHA .
- docker push registry/app:$CI_COMMIT_SHA
deploy:
stage: deploy
script:
- helm upgrade --install dingerodds-chart ./chart \
--set image.tag=$CI_COMMIT_SHA
Helm + custom values для окружений (
dev
,prod
)Secrets через sealed-secrets
Логика деплоя: если
main
— обновляемprod
, иначеdev
Kubernetes (Yandex Cloud)
1 Deployment:
api-fetcher
— воркер, который забирает данные с dingerodds1 CronJob:
retrain-models
— переобучение моделей каждые 6 часовMinIO: хранилище parquet-файлов (
s3://sports-data/{sport}/{date}.parquet
)Horizontal Pod Autoscaler: масштабируем
api-fetcher
по CPU > 70%
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
scaleTargetRef:
kind: Deployment
name: api-fetcher
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
ETL: Dask + Parquet
После каждого батча сохраняем данные в parquet с разделением по
sport
иtimestamp
Используем
fastparquet
как backendDask позволяет агрегировать большие объемы без перегрузки памяти
import dask.dataframe as dd
df = dd.from_pandas(pandas_df, npartitions=4)
df.to_parquet("s3://sports-data/nfl/2025-07-24/", engine="fastparquet")
Мониторинг
Prometheus: метрики по количеству успешных и проваленных запросов
Loki + Grafana: логи по статус-кодам, времени ответа
Alertmanager: шлёт уведомления в Telegram, если API не отвечает > 2 минут
Результаты
Система в проде уже 14 дней — ни одного критического отказа
Обновление данных каждые 5 минут
ML-модели обучаются с актуальными и полными датасетами
Аптайм > 99.95% (до внедрения был ~85%)
Время от запроса до появления данных в parquet — < 2 мин
Планы на будущее
Перейти на pub/sub модель (если dingerodds даст такую возможность)
Интегрировать Redis для кеширования популярных выборок
Сделать delta-lake поверх MinIO + добавить версионирование данных
Использовать Kafka для push-событий, если появится мульти-источник
Выводы
Работа с нестабильным API — это почти всегда про защиту от самого источника.
Если бы я просто подключил dingerodds без буферов, ретраев и лимитов — прод упал бы в первые часы.
Обёртка + CI/CD + отказоустойчивая архитектура на Kubernetes решают большую часть боли.