Допустим, аналитический набор нельзя изменять задним числом, но в нём нужно исправить ошибочное значение. Как представить такую коррекцию в append-only модели?
Коррекцию нужно представить новой записью, которая однозначно ссылается на исправляемую логическую сущность и описывает новое состояние или компенсирующее изменение. Исходная запись сохраняется, поэтому история поступления данных не теряется, а отчёты используют правило выбора актуальной версии или расчёта чистого эффекта.
Простое добавление новой строки без идентификатора сущности, версии и типа операции недостаточно: аналитическая система может посчитать и ошибочное, и исправленное значение одновременно.
Append-only-модель возникла как способ сохранять данные без разрушения исходной истории. Это упрощает аудит, повторную обработку, расследование ошибок и восстановление производных наборов после изменения логики преобразования.
В аналитических системах запись данных часто отделена от их представления в отчётах. Поэтому исправление обычно выражается дополнительным фактом, а не изменением уже загруженной строки.
Например, в хранилище есть факт продажи на сумму 100, хотя правильная сумма равна 80. Если просто добавить строку на 80, отчёт может показать 180. Если удалить исходную строку или изменить её на месте, исчезнет информация о том, какое значение поступило первоначально и когда оно было исправлено.
Нужно разделить два понятия: бизнес-состояние и историю изменений. Также важно определить, что должны показывать исторические отчёты: значение, известное на момент формирования отчёта, или окончательно исправленное значение с учётом более поздних сведений.
Обычно каждая запись получает стабильный идентификатор логического факта, например идентификатор продажи. Дополнительно хранятся идентификатор версии или коррекции, тип операции, время события и время загрузки. Новая запись может содержать полное новое состояние, а может быть компенсирующей дельтой: минус 100 для отмены ошибочного факта и плюс 80 для правильного.
Есть два распространённых способа расчёта. При модельном представлении состояния запрос выбирает последнюю действующую версию факта по его бизнес-идентификатору. При журнальном представлении отчёт суммирует исходные и компенсирующие операции, получая чистый результат.
Выбор зависит от семантики данных. Полная новая версия удобна для сущностей с набором атрибутов, а компенсирующие записи естественны для финансовых движений и других аддитивных фактов. Смешивать эти подходы без явного правила опасно: одна и та же коррекция может быть применена дважды.
Для защиты от повторной доставки нужен стабильный идентификатор события или коррекции и идемпотентная обработка. Он должен позволять отличить повтор того же сообщения от новой, самостоятельной корректировки.
Нужно также различать время события и время обработки. Время события отвечает на вопрос, к какому моменту относится исправление по бизнес-смыслу, а время обработки — когда система о нём узнала. Без этого невозможно однозначно построить отчёт на прошлую дату.
Компромисс append-only-подхода — рост объёма данных и усложнение запросов. Эти затраты компенсируются трассируемостью и возможностью пересчитать витрины; на практике для удобства поверх журнала часто строят отдельную актуальную витрину.
Платёжный провайдер сначала сообщил о списании 100 единиц, а через несколько часов прислал исправление: фактически списано 80. Рассматривались три варианта.
Был выбран третий вариант. В журнал записывались исходный факт и новая версия, а витрина текущего состояния выбирала последнюю подтверждённую версию. Для финансового аудита сохранялись обе записи; для итоговых отчётов использовалась только актуальная версия. Повторная доставка коррекции отбрасывалась по её стабильному идентификатору.
1. Как отличить коррекцию от повторной доставки той же записи?
Нужен стабильный идентификатор события или операции, сохраняемый при повторной передаче. Хранилище или слой обработки проверяет, применялся ли этот идентификатор ранее; повтор не должен создавать новую бизнес-версию. Одного времени загрузки недостаточно, поскольку повтор может прийти позже и выглядеть как новое изменение.
2. Что делать, если коррекция пришла раньше исходной записи?
Нельзя безусловно отбрасывать такую коррекцию. Её следует сохранить по бизнес-идентификатору и версии, а при появлении исходной записи сопоставить их в процессе построения актуального состояния. Иначе порядок доставки ошибочно станет условием корректности данных.
3. Как построить отчёт, воспроизводящий состояние на прошлый момент?
Нужно хранить как минимум время, к которому относится изменение по бизнес-смыслу, и время, когда оно стало известно системе. Отчёт должен явно выбрать семантику: использовать только данные, поступившие до указанного момента, либо учитывать поздние коррекции, относящиеся к прошлому периоду. Если хранить только последнюю версию без времени поступления, воспроизвести прежний результат будет невозможно.