Первые сутки сервис падал каждый час, но сейчас система выдерживает пиковые запросы без даунтайма.
Исходная задача
Мне нужно было автоматизировать процесс сбора спортивных данных (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— воркер, который забирает данные с 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
ETL: Dask + Parquet
-
После каждого батча сохраняем данные в parquet с разделением по
sportиtimestamp -
Используем
fastparquetкак backend -
Dask позволяет агрегировать большие объемы без перегрузки памяти
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 решают большую часть боли.
ссылка на оригинал статьи https://habr.com/ru/articles/931050/
Добавить комментарий