fix(ods): изменен батчевый резолвер для транзакционных таблиц

- Зачем:
  - текущая реализация ODS batch_id теряла данные транзакционных таблиц, если между запусками ODS STG успевал отработать дважды (брался только последний батч).
- Что:
  - изменены скрипты загрузки транзакционных таблиц (bookings, tickets, flights, segments, boarding_passes) для использования паттерна HWM по _load_ts вместо фильтрации по конкретному батчу.
  - обновлен комментарий в DAG bookings_to_gp_ods, объясняющий разное поведение для справочников и транзакционных данных.
  - сохранено использование оригинального batch_id из STG для поля _load_id в слое ODS для сквозного трассирования.
- Проверка:
  - запуск пайплайнов и проверка, что все батчи загружаются из STG в ODS без потерь.
This commit is contained in:
2026-03-01 18:15:04 +03:00
parent faf410b4e8
commit cfd20328d6
6 changed files with 29 additions and 18 deletions
+9
View File
@@ -34,6 +34,15 @@ def _resolve_stg_batch_id(**context) -> str:
""" """
Возвращает stg_batch_id из dag_run.conf или вычисляет последний согласованный батч. Возвращает stg_batch_id из dag_run.conf или вычисляет последний согласованный батч.
ВНИМАНИЕ: Это значение используется ТОЛЬКО для загрузки snapshot-справочников
(airports, airplanes, routes, seats).
Для инкрементальных транзакционных таблиц (bookings, tickets, flights, segments,
boarding_passes) этот батч НЕ используется. Вместо этого они грузят все новые
записи по HWM: WHERE load_dttm > (SELECT MAX(_load_ts) FROM ods.table).
Это сделано для того, чтобы не потерять инкременты, если STG-DAG запускался
несколько раз до запуска ODS-DAG'а.
Зачем нужна согласованность (INTERSECT по всем справочникам)? Зачем нужна согласованность (INTERSECT по всем справочникам)?
Чтобы ODS загружал только те данные, для которых уже приехали ВСЕ связанные Чтобы ODS загружал только те данные, для которых уже приехали ВСЕ связанные
справочники. Это защищает от рассинхрона данных, когда часть измерений в текущем справочники. Это защищает от рассинхрона данных, когда часть измерений в текущем
+3 -3
View File
@@ -14,7 +14,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.boarding_passes AS s FROM stg.boarding_passes AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.boarding_passes)
) )
UPDATE ods.boarding_passes AS o UPDATE ods.boarding_passes AS o
SET seat_no = s.seat_no, SET seat_no = s.seat_no,
@@ -48,7 +48,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.boarding_passes AS s FROM stg.boarding_passes AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.boarding_passes)
) )
INSERT INTO ods.boarding_passes ( INSERT INTO ods.boarding_passes (
ticket_no, ticket_no,
@@ -67,7 +67,7 @@ SELECT
s.boarding_no, s.boarding_no,
s.boarding_time, s.boarding_time,
s.event_ts, s.event_ts,
'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, s.batch_id,
now() now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1
+6 -4
View File
@@ -7,18 +7,19 @@ WITH src AS (
NULLIF(s.book_date, '')::TIMESTAMP WITH TIME ZONE AS book_date, NULLIF(s.book_date, '')::TIMESTAMP WITH TIME ZONE AS book_date,
NULLIF(s.total_amount, '')::NUMERIC(10,2) AS total_amount, NULLIF(s.total_amount, '')::NUMERIC(10,2) AS total_amount,
s.src_created_at_ts AS event_ts, s.src_created_at_ts AS event_ts,
s.batch_id,
ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY s.book_ref PARTITION BY s.book_ref
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.bookings AS s FROM stg.bookings AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.bookings)
) )
UPDATE ods.bookings AS o UPDATE ods.bookings AS o
SET book_date = s.book_date, SET book_date = s.book_date,
total_amount = s.total_amount, total_amount = s.total_amount,
event_ts = s.event_ts, event_ts = s.event_ts,
_load_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, _load_id = s.batch_id,
_load_ts = now() _load_ts = now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1
@@ -36,12 +37,13 @@ WITH src AS (
NULLIF(s.book_date, '')::TIMESTAMP WITH TIME ZONE AS book_date, NULLIF(s.book_date, '')::TIMESTAMP WITH TIME ZONE AS book_date,
NULLIF(s.total_amount, '')::NUMERIC(10,2) AS total_amount, NULLIF(s.total_amount, '')::NUMERIC(10,2) AS total_amount,
s.src_created_at_ts AS event_ts, s.src_created_at_ts AS event_ts,
s.batch_id,
ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY s.book_ref PARTITION BY s.book_ref
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.bookings AS s FROM stg.bookings AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.bookings)
) )
INSERT INTO ods.bookings ( INSERT INTO ods.bookings (
book_ref, book_ref,
@@ -56,7 +58,7 @@ SELECT
s.book_date, s.book_date,
s.total_amount, s.total_amount,
s.event_ts, s.event_ts,
'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, s.batch_id,
now() now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1
+5 -5
View File
@@ -7,7 +7,7 @@ WITH segment_flights AS (
SELECT DISTINCT SELECT DISTINCT
NULLIF(s.flight_id, '')::INTEGER AS flight_id NULLIF(s.flight_id, '')::INTEGER AS flight_id
FROM stg.segments AS s FROM stg.segments AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.segments)
AND s.flight_id IS NOT NULL AND s.flight_id IS NOT NULL
AND s.flight_id <> '' AND s.flight_id <> ''
), ),
@@ -40,7 +40,7 @@ src_union AS (
f.event_ts, f.event_ts,
f.load_dttm f.load_dttm
FROM stg_flights_typed AS f FROM stg_flights_typed AS f
WHERE f.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE f.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.flights)
UNION ALL UNION ALL
@@ -103,7 +103,7 @@ WITH segment_flights AS (
SELECT DISTINCT SELECT DISTINCT
NULLIF(s.flight_id, '')::INTEGER AS flight_id NULLIF(s.flight_id, '')::INTEGER AS flight_id
FROM stg.segments AS s FROM stg.segments AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.segments)
AND s.flight_id IS NOT NULL AND s.flight_id IS NOT NULL
AND s.flight_id <> '' AND s.flight_id <> ''
), ),
@@ -135,7 +135,7 @@ src_union AS (
f.event_ts, f.event_ts,
f.load_dttm f.load_dttm
FROM stg_flights_typed AS f FROM stg_flights_typed AS f
WHERE f.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE f.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.flights)
UNION ALL UNION ALL
@@ -190,7 +190,7 @@ SELECT
s.actual_departure, s.actual_departure,
s.actual_arrival, s.actual_arrival,
s.event_ts, s.event_ts,
'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, s.batch_id,
now() now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1
+3 -3
View File
@@ -13,7 +13,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.segments AS s FROM stg.segments AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.segments)
) )
UPDATE ods.segments AS o UPDATE ods.segments AS o
SET fare_conditions = s.fare_conditions, SET fare_conditions = s.fare_conditions,
@@ -44,7 +44,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.segments AS s FROM stg.segments AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.segments)
) )
INSERT INTO ods.segments ( INSERT INTO ods.segments (
ticket_no, ticket_no,
@@ -61,7 +61,7 @@ SELECT
s.fare_conditions, s.fare_conditions,
s.segment_amount, s.segment_amount,
s.event_ts, s.event_ts,
'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, s.batch_id,
now() now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1
+3 -3
View File
@@ -14,7 +14,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.tickets AS s FROM stg.tickets AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.tickets)
) )
UPDATE ods.tickets AS o UPDATE ods.tickets AS o
SET book_ref = s.book_ref, SET book_ref = s.book_ref,
@@ -49,7 +49,7 @@ WITH src AS (
ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC ORDER BY s.src_created_at_ts DESC NULLS LAST, s.load_dttm DESC
) AS rn ) AS rn
FROM stg.tickets AS s FROM stg.tickets AS s
WHERE s.batch_id = '{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text WHERE s.load_dttm > (SELECT COALESCE(MAX(_load_ts), '1900-01-01 00:00:00'::TIMESTAMP) FROM ods.tickets)
) )
INSERT INTO ods.tickets ( INSERT INTO ods.tickets (
ticket_no, ticket_no,
@@ -68,7 +68,7 @@ SELECT
s.passenger_name, s.passenger_name,
s.is_outbound, s.is_outbound,
s.event_ts, s.event_ts,
'{{ ti.xcom_pull(task_ids="resolve_stg_batch_id") }}'::text, s.batch_id,
now() now()
FROM src AS s FROM src AS s
WHERE s.rn = 1 WHERE s.rn = 1