Первые сутки сервис падал каждый час, но сейчас система выдерживает пиковые запросы без даунтайма.
Исходная задача
Мне нужно было автоматизировать процесс сбора спортивных данных (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: 70ETL: 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 решают большую часть боли.
 
          