Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Инкрементальная загрузка в хранилище

Инкрементальная загрузка в хранилище данных

Инкрементальная загрузка: выбор границы изменения, обработка обновлений и удалений, идемпотентность повторов и проверка полноты результата.

Прямой ответ

Инкрементальная загрузка переносит только изменившиеся данные, поэтому объём переноса не зависит от общего объёма таблицы. Ключевой вопрос — определение границы изменения.

Способ определения границы Требование к источнику Обнаруживает удаления
Отметка обновления (updated_at) Столбец с отметкой, обновляемый при изменении Нет
Версия строки Монотонный счётчик либо версия Нет
Журнал изменений (CDC) Доступ к журналу транзакций Да
Сравнение наборов Возможность полного чтения Да
-- Граница изменения по отметке обновления
INSERT INTO dwh.orders AS d
SELECT o.*
FROM src.orders o
WHERE o.updated_at > :watermark
ON CONFLICT (order_id) DO UPDATE
SET customer_id  = EXCLUDED.customer_id,
    amount_minor = EXCLUDED.amount_minor,
    updated_at   = EXCLUDED.updated_at;

Граница изменения сохраняется после успешной загрузки, поэтому повторное выполнение переносит тот же интервал; об идемпотентности — в статье про повторы загрузки.

Выбор способа зависит от требования к удалениям: отметка обновления не обнаруживает удаление строки, поскольку удалённая строка не попадает в выборку. Об обнаружении изменений через журнал — в статье про CDC.

Обработка обновлений, удалений и поздних изменений

Инкрементальная загрузка сталкивается с тремя ситуациями, каждая из которых требует отдельного решения.

Обновление существующей строки. Строка с тем же ключом уже загружена, поэтому требуется обновление, а не вставка. Операция выполняется через upsert; о практике — в статье про upsert в витрине.

Удаление строки в источнике. При загрузке по отметке обновления удалённая строка не попадает в выборку, поэтому остаётся в хранилище. Обнаружение требует сравнения наборов либо чтения журнала изменений.

Позднее изменение. Строка изменена после того, как интервал уже загружен, при этом отметка обновления не изменилась либо изменение внесено задним числом. Часть данных остаётся устаревшей.

Практическое следствие: загрузка по отметке обновления даёт корректный результат только при монотонном обновлении отметки и отсутствии изменений задним числом.

Отдельный аспект — часы источника. Отметка обновления берётся из времени источника, поэтому расхождение часов источника и хранилища создаёт пропуск либо дублирование данных на границе интервала. Запас по границе уменьшает риск пропуска.

Транзакционность источника также влияет: транзакция, начатая до границы, но зафиксированная после, может содержать отметку внутри уже загруженного интервала. Повторная проверка интервала с запасом учитывает такую ситуацию.

О согласовании границ при распределённой загрузке — в статье про двойную запись.

Размер интервала и задержка данных

Интервал загрузки определяется расписанием и допустимой задержкой данных в хранилище.

Короткий интервал уменьшает задержку, но увеличивает число запусков и долю служебных операций в общем объёме работы. При интервале в одну минуту загрузка выполняется чаще, чем успевают поступить изменения, поэтому большинство запусков переносит пустой набор.

Длинный интервал сокращает число запусков, но увеличивает задержку данных и объём одного запуска. При интервале в сутки загрузка переносит большой объём за один раз, что требует времени и ресурсов.

Практическое следствие: интервал выбирается по допустимой задержке данных у потребителя, а не по удобству расписания.

Отдельный аспект — перекрытие интервалов. Запас по границе делает интервалы перекрывающимися, поэтому часть данных обрабатывается повторно. Идемпотентность загрузки исключает дубликаты при перекрытии.

Практика: настройка инкрементальной загрузки

1. Уточните наличие отметки обновления. Столбец должен обновляться при любом изменении строки, включая обновление связанных данных.

2. Сохраняйте границу изменения. Отметка последней успешной загрузки определяет начало следующего интервала.

3. Используйте запас по границе. Интервал начинается несколько раньше сохранённой границы, чтобы учесть поздние изменения.

4. Обрабатывайте удаление отдельно. При отсутствии журнала изменений выполняется периодическое сравнение наборов.

5. Делайте загрузку идемпотентной. Повторное выполнение интервала не должно создавать дубликаты; используется upsert по ключу.

6. Проверяйте полноту результата. Сравнение числа строк и агрегатов с источником выявляет пропуск данных.

7. Логируйте интервалы загрузки. Записи о границах и числе перенесённых строк позволяют найти пропущенный интервал.

Рекомендации:

  1. Различайте добавление и обновление. Загрузка только новых строк проще, но не отражает изменения существующих данных.
  2. Учитывайте размер интервала. Интервал определяется расписанием загрузки и допустимой задержкой данных.
  3. Проверяйте монотонность отметки. Немонотонная отметка приводит к пропуску данных, поскольку интервал строится по её значению.
  4. Не полагайтесь на часы источника. Запас по границе компенсирует расхождение часов и позднюю фиксацию транзакций.
  5. Планируйте полное обновление. Периодическое полное обновление исправляет расхождения, накопленные инкрементальной загрузкой.

Практический сценарий: часть изменений не попала в хранилище

Контекст: отчёт за период расходился с учётной системой на небольшую сумму.

  1. Диагностика. Загрузка выполняется каждые пять минут по столбцу updated_at с границей, равной времени предыдущего запуска. Расхождение возникло в строках, изменённых транзакцией, которая началась до границы, но зафиксировалась после.
  2. Причина. Отметка обновления устанавливается в момент начала транзакции, поэтому значение оказалось внутри уже загруженного интервала. Граница следующего интервала начиналась строго с отметки предыдущего запуска, поэтому строка не попала в выборку ни разу.
  3. Действия. (а) Добавили запас по границе в пять минут, чтобы интервал перекрывал длительность транзакций; (б) перешли на upsert по ключу, чтобы повторная обработка строки не создавала дубликат; (в) добавили ежедневную сверку агрегатов с источником для обнаружения расхождений; (г) пересчитали расхождшийся период с запасом; (д) задокументировали, что отметка обновления отражает время начала транзакции.
  4. Результат. Расхождения перестали возникать, а сверка выявляет отклонение в течение суток.

Вывод: граница интервала, построенная строго по отметке обновления, пропускает изменения из длительных транзакций. Запас по границе в сочетании с идемпотентной загрузкой устраняет пропуск без риска дублирования.

FAQ

Как определить границу изменения, если отметки обновления нет? Через журнал изменений либо периодическое сравнение наборов. Без отметки и журнала загрузка выполняется полным объёмом.

Обнаруживает ли загрузка по updated_at удаления? Нет, поскольку удалённая строка не попадает в выборку. Обнаружение удалений требует журнала изменений либо сравнения наборов.

Нужен ли запас по границе? Да, если транзакции источника длятся заметное время либо часы источника и хранилища расходятся. Запас компенсирует позднюю фиксацию.

Как избежать дубликатов при повторной загрузке? Через upsert по уникальному ключу, чтобы повторная обработка строки обновляла существующую запись, а не добавляла новую.

Источники: PostgreSQL MERGE, logical decoding, date/time types — ссылки в шапке.