Короткий ответ
Единственного правильного решения нет: идемпотентности можно добиться полной перезаписью результата за обрабатываемый период при каждом запуске, либо через upsert-логику по бизнес-ключу с удалением/заменой существующих записей перед вставкой новых — выбор зависит от объёма данных и возможностей хранилища, куда пишется результат.
Как это работает подробнее
неидемпотентный ETL (который просто "дописывает" новые строки при каждом запуске) при повторном запуске на тех же данных создаёт дубликаты; идемпотентный подход либо полностью удаляет и пересчитывает данные за целевой период (например, "удалить все строки за эту дату, затем вставить заново"), либо выполняет upsert по уникальному бизнес-ключу (обновить, если ключ уже существует, иначе вставить).
Пример кода
Пример на Python
import pandas as pd
def idempotent_write(new_data: pd.DataFrame, existing_data: pd.DataFrame, period_col: str, period_value: str) -> pd.DataFrame:
remaining = existing_data[existing_data[period_col] != period_value]
return pd.concat([remaining, new_data], ignore_index=True)
existing = pd.DataFrame({'date': ['2024-01-01', '2024-01-02'], 'total': [100, 150]})
new_run = pd.DataFrame({'date': ['2024-01-02'], 'total': [160]})
result = idempotent_write(new_run, existing, 'date', '2024-01-02')
print(result)Что использовать на практике
- На практике. полная перезапись периода проще в реализации и легче для отладки (результат всегда детерминирован относительно входных данных), но может быть дороже по времени на больших объёмах; upsert эффективнее для больших таблиц, но требует надёжного уникального ключа и аккуратной обработки случая изменения состава записей (например, строка, которая раньше была в периоде, а теперь должна быть удалена из источника).
- Проверка. при повторном запуске idempotent_write с теми же new_run и existing результат останется тем же самым (одна строка на 2024-01-02 со значением 160), а не превратится в две дублирующиеся строки за одну и ту же дату.
Подводные камни
- Частые ошибки. реализуют ETL через простое append в целевую таблицу без проверки, не запускался ли уже процесс за этот период, что при повторном или параллельном запуске приводит к задвоению данных; при upsert забывают обработать случай удаления записей, которые пропали из источника, из-за чего в целевой таблице остаются "осиротевшие" устаревшие строки.