КОНТРОЛЬ КАЧЕСТВАP0L3

Сверка партии событий: полнота, повторы и конфликты

Как закрепить ожидаемый набор партии, различить повтор доставки и конфликт содержимого и получить воспроизводимый протокол сверки.

Справочный материал. Контракты данных, архитектурные решения и ограничения применения. Источники приведены в материалах, где используются внешние основания.

Задача и границы#

Перед допуском партии финансовых событий в EDWH нужно установить, что доставлены все ожидаемые события, повторные доставки не создали новые факты, а содержимое под прежним ключом не изменилось незаметно. Общая статья о сверках описывает уровни контроля; здесь рассматривается протокол одной партии до агрегации проводок.

Предлагаемый контракт рассчитан на неизменяемые события или явно нумерованные версии. Для выгрузки актуального состояния сначала нужен согласованный снимок; для CDC отдельно определяют порядок изменений и обработку удалений. Сверка партии не доказывает, что источник зарегистрировал все хозяйственные операции. Это материал knowledge, а не описание внедрения или проверенного лабораторного алгоритма.

Зерно, ключи и границы партии#

Зерно ожидаемого набора - одна версия события. Ключ K состоит из системы-источника, учётного контура, идентификатора события и версии, если версии предусмотрены контрактом. Контур включает юридическое лицо и книгу, когда они нужны для уникальности. Номер доставки и batch_id не входят в бизнес-ключ: одно событие может повторно поступить в другой партии. Если событие содержит несколько строк проводок, полноту строк проверяют отдельно по ключу события и номеру строки.

Зерно сырого приёма - одна доставка события с собственным delivery_id. Зерно принятого набора - одна версия события K. Поэтому равенство количества сообщений и количества фактов не является целью при допустимых повторах.

Для каждого денежного поля фиксируют валюту, масштаб и роль суммы. В синтетическом примере ниже сумма строки хранится целыми копейками RUB, дебет положителен, кредит отрицателен. Сравниваются также счёт, сторона и прочие значимые атрибуты: одинаковая сумма ещё не означает одинаковую запись. Валюты не суммируют вместе.

business_date задаёт учётную дату, observed_at - первое получение версии EDWH. Момент начала сверки и отсечка входов сохраняются отдельно. Позднее событие с прежней учётной датой не меняет уже закреплённый ожидаемый набор: оно входит в новую партию или оформленную ревизию партии.

До проверки закрепляют манифест: идентификатор партии и ревизии, область отбора, снимок или диапазон позиций источника, ожидаемые K, версии схемы и преобразования. Если сравнивают payload, закрепляют и ожидаемые значения либо их контрольные представления. Реестр строят на стороне источника для согласованного среза. Реестр, полученный из уже доставленных сообщений, не способен обнаружить потерянное сообщение. Без независимого ожидаемого набора результат формулируют как проверку доставленных данных, а не доказательство полноты.

Сравнение содержимого#

Сначала определяют значимые поля и их нормализацию, затем сравнивают их. Исходный payload сохраняют для расследования. Транспортные поля вроде времени повторной доставки исключают из логического сравнения, но валюту, масштаб, даты и признак удаления исключать нельзя, если они меняют смысл события. Отсутствующее поле, явный null и пустая строка считаются разными, пока контракт явно не установит обратное.

Для сравнения до преобразования обе стороны приводят к одному контракту источника. Для сравнения после преобразования ожидаемый набор получают по закреплённой версии отображения. Сравнивать сырой JSON с моделью проводок напрямую некорректно: одна запись может породить несколько фактов, а нормализация может менять представление суммы.

RFC 8785 задаёт схему канонизации JSON JCS, включая детерминированную сортировку свойств. Документ имеет статус Informational и не устанавливает бухгалтерские требования. JCS ограничивает представление чисел возможностями IEEE 754 double precision; для точных значений вне этих возможностей нужна согласованная строковая форма или другой контракт. Канонизация не выполняет предметную нормализацию валют, дат и Unicode. Обычная сортировка ключей при сериализации сама по себе не доказывает соответствие JCS.

Хеш канонического содержимого удобен для предварительного сравнения, но совпадение хешей не является математическим доказательством равенства. При требовании точного сравнения сопоставляют сами нормализованные поля или байты. Разные версии канонизации нельзя сравнивать как одну; смена версии требует нового контрольного представления.

Алгоритм сверки#

Следующий блок - псевдокод, не SQL или Python и не результат выполнения тестов. E - закреплённый ожидаемый набор, R - сырые доставки данной партии. Реестр уже принятых версий A нужен для проверки повторов между партиями.

проверить обязательные ключи и структуру E и R
сгруппировать E и R по K, сохранив количество доставок
для каждого K сравнить все варианты нормализованного содержимого
если у K несколько вариантов, записать конфликт и не выбирать победителя
одинаковые повторы R учесть отдельно, получить один кандидат на K
сравнить кандидатов с ранее принятыми версиями A по K и содержимому
missing = ключи E без доставки в R
extra = ключи R вне E
сравнить содержимое общих неконфликтных K с E
сохранить нарушения и решение о допуске партии

Конфликт в E означает неисправный ожидаемый набор. Доставка с повреждённым ключом регистрируется отдельно и не исчезает при группировке. Конфликт внутри R или с A блокирует принятие соответствующей версии; выбор последней доставки по времени скрыл бы нарушение неизменяемости. При допустимых изменениях требуется новый номер версии и правило последовательности, а не молчаливый выбор payload.

Разности ключей отвечают только на вопрос о наличии. Доставленный конфликтный ключ не считается отсутствующим, но партия не проходит контроль содержимого. После устранения конфликтов должны совпасть уникальные ключи и значимые значения E и принятого набора. Число сырых доставок раскладывают на число представленных ключей и избыточные доставки только для записей с валидными ключами; отклонённые записи учитывают отдельно. Политика допустимых идентичных повторов фиксируется заранее.

Проверочный пример и граничные случаи#

Арифметическая иллюстрация, не результат исполнения: E содержит строки A +10000 и B -10000 одного документа в копейках RUB. R содержит две одинаковые доставки A и ни одной B. Сравнение числа сообщений даёт два против двух; проверка ключей обнаруживает missing B и один повтор A. Если вместо второго A пришёл C -10000, общий ноль сохранится, но появятся missing B и extra C.

Сценарий Ожидаемое решение по предлагаемому контракту
Две доставки K с одинаковым содержимым Один факт, повтор отражён в протоколе
Две доставки K с разными счетами при одинаковой сумме Конфликт, автоматического победителя нет
K уже принят в предыдущей партии Проверить содержимое A, повтор не создаёт новый факт
E и R пусты Допуск только при подтверждённом пустом срезе источника
Нет манифеста или части его разделов Полнота не установлена, допуск заблокирован
Удаление передано как tombstone Сверить ключ, версию и действие, не отбрасывать как пустой payload
Новое событие пришло после отсечки Новая партия или ревизия, старый протокол сохранён

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

Масштаб партии и особенности СУБД#

Для крупных партий применимы группировка и соединения в СУБД или внешняя сортировка по K. Разбиение по стабильной функции от полного K позволяет обрабатывать разделы независимо, если обе стороны используют одинаковое правило. Манифест должен перечислять все разделы, включая пустые; итог собирают только после получения результатов каждого раздела. Выборочная проверка и сравнение суммарных хешей дешевле, но не дают полного списка расхождений и не заменяют точную сверку.

В PostgreSQL 18 EXCEPT удаляет дубликаты, а EXCEPT ALL сохраняет различия кратности. Поэтому разность уникальных ключей не проверяет повторы, а сравнение кратности сырых доставок не соответствует модели одного принятого факта на K. Для обнаружения конфликтов сначала группируют варианты содержимого, затем соединяют уникальные кандидаты: соединение двух необработанных наборов с повторами может размножить строки.

В PostgreSQL 18 обычное сравнение с NULL возвращает неизвестное значение; IS DISTINCT FROM позволяет обнаружить отличие, включая случай одного NULL. Это правило сравнения nullable-атрибутов не делает отсутствующий обязательный ключ допустимым. Указанные свойства относятся к PostgreSQL 18; SQL здесь не выполнялся и переносимость на другую СУБД не утверждается.

Протокол и вопросы#

Протокол сохраняет ревизию манифеста, отсечку, версии правил, число ожидаемых ключей и доставок, повторы, missing, extra, конфликты, повреждённые записи и решение. Для каждого нарушения сохраняют K и ссылки на исходные доставки с контролем доступа. Частичный допуск требует явной политики исключений и списка недопущенных ключей; его нельзя обозначить полной успешной сверкой. Решение о публикации и закрепление принятого набора должны быть согласованы, чтобы параллельная загрузка не изменила входы после проверки.

Обработка повторов раскрыта в инкрементальных загрузках, бизнес-ключи - в финансовых событиях, временные срезы - в истории и времени.

  1. Как обнаружить потерю целого файла, если манифест строится из полученных файлов?
  2. Почему одинаковая сумма и одинаковый ключ не исключают конфликт payload?
  3. Как сверить повтор события в новой партии, не увеличив число финансовых фактов?

Источники и документация

  1. PostgreSQL 18 - Combining Queries ↗
  2. PostgreSQL 18 - Comparison Functions and Operators ↗
  3. RFC 8785 - JSON Canonicalization Scheme ↗

Источники нужны для самостоятельной проверки. Примеры и интерпретации на сайте могут быть учебными упрощениями.