- BrainTools - https://www.braintools.ru -
Первые сутки сервис падал каждый час, но сейчас система выдерживает пиковые запросы без даунтайма.
Мне нужно было автоматизировать процесс сбора спортивных данных (NFL, NBA, UFC) с dingerodds для дальнейшего анализа и обучения [1] моделей. Источник выбран из-за:
доступного 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, иногда как {}.
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
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
Логика [2] деплоя: если main — обновляем prod, иначе dev
1 Deployment: api-fetcher — воркер, который забирает данные с dingerodds
1 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
После каждого батча сохраняем данные в parquet с разделением по sport и timestamp
Используем fastparquet как backend
Dask позволяет агрегировать большие объемы без перегрузки памяти [3]
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 решают большую часть боли [4].
Автор: grigorij_mikhajlovu
Источник [5]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/17642
URLs in this post:
[1] обучения: http://www.braintools.ru/article/5125
[2] Логика: http://www.braintools.ru/article/7640
[3] памяти: http://www.braintools.ru/article/4140
[4] боли: http://www.braintools.ru/article/9901
[5] Источник: https://habr.com/ru/articles/931050/?utm_campaign=931050&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.