Files
wiki/tasks/flightradar24/reports/TZ-fr24-worker-pagination-fix.md
2026-04-22 09:00:01 +03:00

5.0 KiB
Raw Blame History

ТЗ: Фикс пагинации fr24_worker — commit per page + дедупликация

Дата: 2026-04-22
Статус: READY FOR DEV
Приоритет: Критический (из-за бага слиты 44K кредитов за ночь)


Суть проблемы

fetch_flight_summaries() накапливает все страницы в памяти, потом run() вставляет всё разом.
При ошибке на любой странице — ничего не коммитится, все данные теряются.

Дополнительно: both:SVO,both:DME,both:VKO,both:ZIA возвращает дубли —
рейс SVO→DME попадает в выборку и как рейс SVO, и как рейс DME.
Итого: ~3 500 уникальных рейсов → 14 660 записей (×4 дублирование).


Изменения в fetch_flight_summaries() → убрать, заменить на генератор

Файл: ingest/tracks_fr24/fr24_worker.py

Было

def fetch_flight_summaries(target_date: date) -> List[Dict]:
    all_items = []
    offset = 0
    while True:
        data = _get(...)
        items = data.get("data", ...) 
        all_items.extend(items)
        if len(items) < PAGE: break
        offset += PAGE
    return all_items

Стало — генератор страниц

def iter_flight_summary_pages(target_date: date):
    """Yield one page (list of flights) at a time. Stops on 402/empty."""
    PAGE = 20
    airports_param = _build_airports_param()
    offset = 0
    seen_fr24_ids = set()   # дедупликация между страницами

    while True:
        try:
            data = _get("/api/flight-summary/full", params={
                "flight_datetime_from": f"{target_date}T00:00:00",
                "flight_datetime_to":   f"{target_date}T23:59:59",
                "airports":             airports_param,
                "limit":                PAGE,
                "offset":               offset,
            })
        except Exception as e:
            log.error("fetch page offset=%d failed: %s", offset, e)
            break

        items = data.get("data", data) if isinstance(data, dict) else data
        if not items or not isinstance(items, list):
            break

        # Дедупликация по fr24_id
        unique = [x for x in items if x.get("fr24_id") not in seen_fr24_ids]
        seen_fr24_ids.update(x["fr24_id"] for x in items if x.get("fr24_id"))

        yield unique

        if len(items) < PAGE:
            break
        offset += PAGE

Изменения в run() — commit после каждой страницы

Было

summaries = fetch_flight_summaries(target_date)
stats["flights_found"] = len(summaries)
for item in summaries:
    ...upsert...
    conn.commit()

Стало

for page in iter_flight_summary_pages(target_date):
    stats["flights_found"] += len(page)
    for item in page:
        fr24_id = item.get("fr24_id")
        if not fr24_id:
            continue
        try:
            actual_id = upsert_flight_actual(conn, item, target_date)
            if actual_id:
                stats["flights_upserted"] += 1
            ...FETCH_TRACKS логика...
        except Exception as e:
            conn.rollback()
            stats["errors"] += 1
            log.error("FR24: error processing %s: %s", fr24_id, e)
    
    # Коммит после каждой страницы
    try:
        conn.commit()
        log.debug("Committed page, total so far: %d", stats["flights_upserted"])
    except Exception as e:
        conn.rollback()
        log.error("Commit failed: %s", e)
        stats["errors"] += 1

Дополнительно — лимит страниц

Добавить защитный лимит:

MAX_PAGES = int(os.getenv("FR24_MAX_PAGES", "200"))  # 200 × 20 = 4000 рейсов max

В генераторе добавить счётчик:

page_num = 0
while True:
    ...
    page_num += 1
    if page_num >= MAX_PAGES:
        log.warning("Reached MAX_PAGES=%d, stopping pagination", MAX_PAGES)
        break

В config.py добавить:

MAX_PAGES: int = int(os.getenv("FR24_MAX_PAGES", "200"))

Проверка

После деплоя запустить:

POST http://fr24-vm:8001/run?date=2026-04-19

(дата когда кредиты были) — проверить что flight_actual заполняется постепенно по страницам.

В БД сразу должны появляться строки, не ждать конца всей загрузки.


Файлы для изменения

  • ingest/tracks_fr24/fr24_worker.py — главные изменения
  • ingest/tracks_fr24/config.py — добавить MAX_PAGES

Деплой: только docker cp (не rebuild), потом проверить синтаксис.