Реалтайм данные о рейсах: архитектура для высоконагруженных сервисов
Если через ваш сервис проходят тысячи проверок статусов рейсов в день (турагрегатор, страховая, такси-сервис), наивный polling быстро упирается в лимиты API и создаёт лишнюю нагрузку. Разбираем архитектуру, которая выдерживает масштаб.
Проблема: лишние запросы
Tablo API ограничивает запросы скользящим окном — 100 запросов за 10 минут на один ключ. При превышении — 429 Too Many Requests с заголовком Retry-After. Если опрашивать API за каждым рейсом отдельно, лимит выбьет вас на первом же пиковом дне.
Слой 1: кэш по data_freshness
Каждый ответ содержит data_freshness — время последнего обновления данных по аэропорту:
{
"data_freshness": { "SVO": "2026-07-24T10:25:00.000Z" }
}
Данные обновляются раз в 15 минут. Нет смысла запрашивать чаще — кэшируйте ответ до следующего обновления:
import time
import requests
CACHE: dict[str, tuple[float, dict]] = {}
TTL_SECONDS = 15 * 60
def get_flights(airport: str, flight_type: str, date: str) -> dict:
key = f"{airport}:{flight_type}:{date}"
cached = CACHE.get(key)
if cached and time.time() - cached[0] < TTL_SECONDS:
return cached[1]
response = requests.get(
"https://api.tabloapi.ru/v1/flights",
headers={"X-API-Key": "YOUR_KEY"},
params={"airport": airport, "type": flight_type, "date": date},
)
response.raise_for_status()
data = response.json()
CACHE[key] = (time.time(), data)
return data
Слой 2: вебхуки вместо циклического опроса
Для конкретных рейсов, которые нужно отслеживать точечно (а не весь поток по аэропорту), замените polling на вебхук — подробности в статье «Как подключить вебхуки для мониторинга задержек рейсов». Это снимает нагрузку и с вашей стороны, и с лимита API.
Слой 3: идемпотентная обработка событий
При высоком трафике вебхуков и ретраях на вашей стороне важно не обработать одно и то же событие дважды. Используйте комбинацию flight_number + field + changed_at как ключ идемпотентности:
const processedEvents = new Set(); // в проде — Redis SET с TTL
function handleWebhookEvent(event) {
const eventKey = `${event.flight_number}:${event.field}:${event.changed_at}`;
if (processedEvents.has(eventKey)) {
return; // уже обработано, пропускаем
}
processedEvents.add(eventKey);
// бизнес-логика: уведомление пассажира, пересчёт компенсации и т.д.
}
Слой 4: backoff при 429
Даже с кэшем и вебхуками возможны всплески. Реализуйте экспоненциальный backoff:
import time
def request_with_backoff(fn, max_retries=5):
delay = 1
for attempt in range(max_retries):
response = fn()
if response.status_code != 429:
return response
retry_after = int(response.headers.get("Retry-After", delay))
time.sleep(retry_after)
delay *= 2
raise RuntimeError("Rate limit exceeded after max retries")
Итоговая схема
- Кэш по
data_freshness— не более одного запроса на аэропорт за 15 минут - Вебхуки — для точечного отслеживания конкретных рейсов
- Идемпотентность — по
flight_number + field + changed_at - Backoff — на случай пиковой нагрузки
Такая архитектура выдерживает нагрузку без превышения лимитов и лишних затрат на инфраструктуру.