Контракт снимка и дельты, допуск завершённых поставок, удаление, идемпотентность и пересчёт финансовой истории.
7 мин чтения·База знаний
Справочный материал. Контракты данных, архитектурные решения и ограничения применения. Источники приведены в материалах, где используются внешние основания.
Инкремент - это правило изменений, а не только фильтр даты#
В финансовом EDWH поступающие данные могут содержать новые события, исправления существующих записей, сторно, удаление, переиздание документа, отложенную доставку и повторное сообщение. Фильтр WHERE source_timestamp > max(target_timestamp) не решает все эти случаи.
У корректной загрузки должны быть контракт идентичности, политика изменений, определение полноты и граница повторного расчёта.
Для неизменяемого потока событий повторная обработка одного и того же входа не должна менять логический результат:
load(S, load(S, T)) = load(S, T)
где S - один и тот же набор исходных событий, T - состояние приёмника. Это не означает, что повторная обработка должна игнорировать изменившееся содержимое под прежним ID. Такое противоречие требуется обнаружить и отдельно обработать.
В лаборатории для каждого события вычисляется SHA-256 канонического содержимого. Повтор с тем же ID и содержимым безопасно пропускается; совпадение ID с другой полезной нагрузкой считается ошибкой контракта источника.
Watermark - ориентир уже обработанного потока, но его значение зависит от выбранного временного поля. Изменение business_date задним числом не всегда меняет timestamp источника, а события могут приходить не по порядку.
Поэтому в надёжной архитектуре возможны сочетания:
продвижение watermark после успешной атомарной фиксации загрузки;
Здесь рассматривается поставка состояния финансовых строк источника, а не алгоритм загрузчика. Зерно снимка - одна актуальная строка по ключу (source_system, legal_entity, ledger, document_id, line_id) на согласованной позиции источника. Зерно дельты - одно изменение этой строки с позицией и порядком внутри транзакции. Ключ подходит только источникам, где эти компоненты действительно обеспечивают идентичность. Неизменяемый журнал событий имеет другой контракт: удаление строки состояния нельзя автоматически считать сторно хозяйственной операции.
Для сумм закрепляют валюту, масштаб и смысл знака до преобразования. Например, целые копейки RUB и положительный дебет с отрицательным кредитом допустимы как проектное соглашение, но не как универсальное правило источника. Контрольные суммы считают раздельно по документу и валюте; общий ноль не доказывает полноту поставки.
Поле манифеста
Что нужно согласовать
Режим и версия контракта
Полный снимок состояния или дельта; допустимые операции и схема
Периметр
Юридические лица, книги, таблицы, фильтры, архивные и удалённые записи; версия отбора
Граница
Для снимка - согласованная позиция состояния; для дельты - диапазон (start, end] и исходная база
Завершённость
Ожидаемый реестр частей, контрольные характеристики и подтверждение источника после завершения выгрузки
Идентичность поставки
Идентификатор, ревизия и связь с заменяемой поставкой
business_date определяет финансовый период строки, snapshot_at описывает момент состояния источника, observed_at - получение EDWH. Для упорядочения используют согласованные позиции источника, а не время получения файлов. Часы и позиции разных систем не считаются автоматически сопоставимыми. Если источник не умеет формировать согласованный снимок, это ограничение записывают явно: последовательное чтение таблиц может дать смесь состояний и не допускается как единый согласованный срез.
Отсутствие ключа означает прекращение его присутствия только в полностью принятом снимке того же периметра и при явном соглашении, что снимок перечисляет все актуальные строки. Отсутствие в дельте означает отсутствие переданного изменения, а не удаление. Нельзя вычислять удаления из частично полученных файлов, недоступной таблицы или выгрузки с изменившимся фильтром.
Пустой снимок может быть корректным, но только при независимом подтверждении источника, что этот периметр пуст на закреплённой границе. Нулевой размер файла и успешная доставка не являются таким подтверждением. Получение последнего файла также не доказывает наличие всех предыдущих частей. Реестр частей создают на стороне источника; сверка реестра с поставкой описана в контроле партии событий.
Явное удаление в дельте содержит ключ, операцию, позицию источника и достаточный контекст периметра. Выход строки из фильтра, физическое удаление, архивирование и сторно имеют разный смысл. Если протокол их не различает, EDWH фиксирует только прекращение присутствия в данном наборе, а финансовую трактовку согласует отдельно. Удаление неизвестного ключа сохраняют как наблюдение и оценивают относительно базового снимка, а не молча отбрасывают.
Для снимка на позиции P и дельты (A, B], где A < P < B, сначала принимают состояние P, затем применяют только изменения после P, если контракт позволяет точно отделить их и сохранить границы транзакций. Изменения до P уже отражены в снимке. Если отделение невозможно, поставку блокируют и запрашивают совместимую дельту; время доставки не выбирает победителя. Для независимых потоков нужна отдельная граница каждого потока и подтверждённое правило согласованности, а не один выдуманный глобальный номер.
Поздний старый снимок не заменяет более новое состояние автоматически. Он сохраняется для аудита или допускается как отдельная историческая база. Изменение периметра требует нового базового снимка либо явного переходного контракта: строка вне нового фильтра не становится доказанно удалённой из источника. Повтор поставки и конфликт содержимого рассматриваются отдельно в разделах выше.
До допуска проверяют контракт, периметр, согласованность границы, все части, контрольные результаты и непрерывность относительно принятой базы. Частично принятую дельту нельзя объявлять полным новым состоянием. Исходные байты, манифест, позиции, решение, причины отказа и связь прежней версии с новой сохраняют; обновление текущего состояния не уничтожает историю. Предыдущий финансовый выпуск сохраняет свои входы и не меняется вслед за удалением в новом снимке.
Альтернатива выводу удалений по полному снимку - явный журнал изменений с операциями удаления. Он уменьшает объём повторного чтения, но требует доказуемого перехода от базы и непрерывности потока. Полные снимки проще для источника без такого журнала, но дороже и нуждаются в подтверждённой согласованности. Эти правила - инженерный контракт EDWH, а не требования IFRS или универсальная гарантия CDC.
PostgreSQL 18 описывает начальное копирование снимка и последующую передачу изменений с сохранением порядка и транзакционной согласованности в рамках одной подписки. Это не гарантия согласованности произвольных файлов или нескольких независимых источников. При фильтрации строк PostgreSQL 18 выход строки из фильтра при UPDATE может передаваться как DELETE. Поэтому транспортная операция удаления сама по себе не доказывает физическое удаление в источнике.
Критерии для будущей реализации: неполный снимок не меняет принятый набор и не порождает удаления; подтверждённый пустой снимок меняет только свой периметр; пустая дельта не удаляет строки; старый снимок не откатывает актуальную базу; пересечение снимка и дельты не применяет изменение дважды; смена фильтра не маскируется под удаление. При отсутствии доказательства границы или завершённости решение - отказ в допуске с сохранённой причиной. Код здесь не исполнялся; SQL-фильтр в начале статьи - иллюстрация опасного подхода, не готовый запрос.
An increment is a change policy, not just a date filter#
Incoming financial data may contain new events, corrections to existing records, reversals, deletions, reissued documents, delayed delivery, and duplicate messages. WHERE source_timestamp > max(target_timestamp) does not handle all these cases.
A correct load needs an identity contract, a change policy, a completeness definition, and a recalculation boundary.
For an immutable event stream, processing the same input again must not change the logical result:
load(S, load(S, T)) = load(S, T)
Here S is the same set of source events and T is the target state. This does not mean that changed content under a reused ID should be ignored. Detect and handle that contract violation separately.
The lab computes a SHA-256 hash of canonical event content. A repeated ID with identical content is safely skipped; the same ID with a different payload is treated as a source-contract error.
A watermark marks progress through a stream, but its meaning depends on the chosen time field. A backdated change to business_date may not change the source timestamp, and events may arrive out of order.
A reliable architecture may combine:
Advancing the watermark only after an atomic successful load.
Periodically rereading an overlapping window.
CDC or explicit correction events.
Completeness checks using sequence numbers, snapshots, or control registers.
Targeted recalculation of affected financial periods.
This section concerns deliveries of financial source-row state, rather than a loader algorithm. Snapshot grain is one current row under (source_system, legal_entity, ledger, document_id, line_id) at an agreed source position. Delta grain is one change to that row, with a position and ordering within its transaction. This key applies only where its components actually establish identity. An immutable event journal has a different contract: deleting a state row must not automatically be interpreted as reversing a business transaction.
Fix currency, scale, and sign semantics before transforming amounts. For example, integer RUB kopecks with positive debits and negative credits are a possible project convention, not a universal source rule. Calculate control totals separately by document and currency; an overall zero does not establish delivery completeness.
Manifest field
Agreement required
Mode and contract version
Full state snapshot or delta; permitted operations and schema
Scope
Legal entities, ledgers, tables, filters, archived and deleted records; selection version
Boundary
An agreed state position for a snapshot; the interval (start, end] and starting baseline for a delta
Completion
An expected inventory of parts, control characteristics, and source confirmation after extraction completes
Delivery identity
Identifier, revision, and relationship to the delivery being replaced
business_date determines a row's financial period, snapshot_at describes the time of source state, and observed_at records EDWH receipt. Ordering uses agreed source positions rather than file arrival times. Clocks and positions from different systems are not automatically comparable. If the source cannot produce a consistent snapshot, document this limitation explicitly: sequential table reads can mix states and cannot be admitted as one consistent view.
A missing key establishes that it is no longer present only in a fully admitted snapshot of the same scope and under an explicit agreement that the snapshot enumerates all current rows. Absence from a delta means no change was supplied, not deletion. Do not infer deletions from partially received files, an unavailable table, or an extract whose filter has changed.
An empty snapshot can be valid, but only with independent source confirmation that this scope is empty at the fixed boundary. A zero-length file and successful delivery are not that confirmation. Receiving the final file also does not prove that all earlier parts arrived. The source produces the inventory of parts; comparing it with the delivery is covered in event-set reconciliation.
An explicit delta deletion carries the key, operation, source position, and sufficient scope context. Leaving a filter, physical deletion, archiving, and reversal have different meanings. If the protocol cannot distinguish them, the EDWH records only that the row is no longer present in this dataset; its financial interpretation requires a separate agreement. Retain a deletion of an unknown key as an observation and evaluate it against the baseline snapshot rather than silently discarding it.
For a snapshot at position P and a delta (A, B] with A < P < B, first admit state P, then apply only changes after P, provided the contract supports separating them precisely while preserving transaction boundaries. Changes before P are already reflected in the snapshot. If separation is impossible, block admission and request a compatible delta; arrival time does not select the winner. Independent streams require a separate boundary for each stream and a substantiated consistency rule, not an invented global sequence number.
A late older snapshot does not automatically replace newer state. Retain it for audit or admit it as a separate historical baseline. A scope change requires a new baseline snapshot or an explicit transition contract: exclusion by a new filter does not prove deletion from the source. Delivery replays and content conflicts are discussed separately above.
Before admission, check the contract, scope, boundary consistency, all parts, control results, and continuity from the admitted baseline. A partially admitted delta cannot be declared a complete new state. Retain original bytes, the manifest, positions, the decision, rejection reasons, and the relationship between old and new versions; updating current state does not destroy history. A previous financial release retains its inputs and does not change when a row is deleted in a new snapshot.
An alternative to inferring deletions from full snapshots is an explicit change log with deletion operations. It reduces repeated reading but requires a demonstrable transition from the baseline and stream continuity. Full snapshots are simpler for sources without such a log, but cost more and need confirmed consistency. These rules are an EDWH engineering contract, not IFRS requirements or a universal CDC guarantee.
Database-specific behavior and acceptance criteria#
PostgreSQL 18 describes initial snapshot copying followed by change delivery that preserves ordering and transactional consistency within a single subscription. This does not guarantee consistency across arbitrary files or multiple independent sources. With PostgreSQL 18 row filters, an UPDATE that moves a row outside the filter can be transmitted as DELETE. A deletion operation in the transport therefore does not by itself establish physical deletion at the source.
Criteria for a future implementation: an incomplete snapshot does not change the admitted dataset or generate deletions; a confirmed empty snapshot changes only its own scope; an empty delta does not delete rows; an older snapshot does not roll back the current baseline; snapshot-delta overlap does not apply a change twice; a filter change is not disguised as deletion. Without evidence of the boundary or completion, refuse admission and retain the reason. No code was executed here; the SQL filter at the beginning is an illustration of an unsafe approach, not a ready-to-run query.