A B C D E F G H I J K L M N O P Q R S T V W Y Z А Б В Г Е И К М О П С Т Ц

Data Contract

Data Contract

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

 

Что такое контракт данных и какую боль он лечит

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

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

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

Путь контракта от продюсера через CI-гейт к потребителю, где сборка падает при несовместимом изменении схемы

 

Из чего состоит документ ODCS

Де-факто стандартом стал Open Data Contract Standard (ODCS), который развивает проект Bitol под крылом Linux Foundation AI & Data. Версия 3.1.0 вышла 7 декабря 2025 года и принесла блок связей между наборами данных плюс строгую валидацию по JSON Schema, о чём подробно написано в анонсе релиза ODCS 3.1.0. Вторая известная спецификация, Data Contract Specification, объявлена устаревшей и схлопнулась в ODCS, так что новые контракты писать имеет смысл сразу по нему.

Документ ODCS это YAML-файл из нескольких независимых блоков. Каждый блок отвечает за свой вопрос, и на практике команды заполняют их не одновременно, а по мере взросления процесса.

Блоки документа ODCS 3.1.0 и что описывает каждый из них

 

Паспорт контракта и схема

Верхний уровень документа отвечает на вопрос, что это за набор данных и кто за него отвечает. Сюда идут идентификатор, имя, версия контракта, статус жизненного цикла, назначение и ограничения из блока description, а также список ролей в блоке team. Версия контракта отдельная от версии данных, и меняется она по правилам семантического версионирования, потому что именно по ней потребитель понимает, ждать ему поломки или нет.

Блок schema описывает структуру. У каждого поля есть логический тип, независимый от СУБД, и физический тип, который сравнивается со строкой из каталога конкретной базы. Разделение неочевидное, но полезное, потому что один контракт может обслуживать таблицу в PostgreSQL и файл Parquet в озере одновременно.

 

Правила качества и SLA

Блок quality вешается и на отдельное поле, и на набор данных целиком. Внутри поля это ограничения вида «пропусков ноль» или «значения только из согласованного списка», на уровне набора это счётчик строк и прочие агрегаты. Именно этот блок отличает контракт от голого описания схемы, потому что схема говорит о форме данных, а качество об их содержимом.

Блок slaProperties фиксирует сроки, а именно частоту обновления и допустимое отставание. Автоматически он обычно не проверяется, зато снимает вечный спор о том, устарела витрина или ещё нет.

 

Серверы и связи

Блок servers говорит, где физически лежат данные, и без него проверить контракт против живой базы невозможно. Учётные данные в контракт не кладутся никогда, инструмент забирает их из переменных окружения. Блок relationships, появившийся в версии 3.1.0, описывает связи между наборами данных, то есть внешние ключи и зависимости, которые раньше приходилось держать в голове.

 

Как контракт проверяется на практике

Сам по себе YAML ничего не гарантирует, гарантии появляются от инструмента, который его исполняет. Самый ходовой из открытых это datacontract-cli, и у него четыре осмысленные команды.

  • lint. Проверяет синтаксис самого документа по JSON Schema стандарта. К базе не ходит, отрабатывает за доли секунды, поэтому lint вешают на pre-commit.
  • test. Подключается к живому источнику и сверяет реальную таблицу с контрактом. Проверки инструмент собирает из документа сам.
  • ci. Делает то же, что test, но дополнительно отдаёт отчёт в форматах JUnit и JSON и печатает аннотации GitHub Actions.
  • export. Разворачивает контракт в артефакт другого формата, от DDL для конкретного диалекта SQL до модели dbt или схемы Avro.

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

Одна деталь ломает представления, унаследованные из старых статей. Проверки в актуальной версии исполняет ibis, движок аналитических запросов на Python. Раньше эту роль играл soda-core, открытый движок проверок качества данных от компании Soda, но из datacontract-cli его выпилили, и правила с явным указанием Soda теперь дают предупреждение вместо работы. Половина материалов в вебе про инструмент до сих пор описывает связку с Soda, которой больше нет.

 

Чем контракт данных отличается от реестра схем

Вопрос возникает у всех, кто работал с Schema Registry в экосистеме Kafka. Механика похожа, потому что там тоже есть версионированное описание структуры и проверка совместимости, но назначение разное.

Признак Реестр схем Контракт данных
Что описывает структуру сообщения структуру, качество, сроки, владельца
Где хранится отдельный сервис со своей базой файл в репозитории рядом с кодом
Когда срабатывает в момент публикации сообщения на сборке, до выкатки изменения
Область применения потоковые сообщения таблицы, файлы, потоки, API
Кто указан ответственным никто, реестр анонимен команда-владелец в самом документе
Проверка содержимого нет, только форма есть, блок quality

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

Архитектура Данных

Код курса
ARMG
Ближайшая дата курса
28 сентября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
76 800

 

Грабли внедрения

Контракт легко превратить в дорогую бюрократию, и происходит это по нескольким повторяющимся сценариям.

  • Контракт без владельца. Блок team заполнен названием отдела, который расформировали год назад. Проверка падает, а чинить некому, и через месяц шаг конвейера отключают.
  • Правило, которое ничего не проверяет. Ограничение качества без порога или список допустимых значений без самих значений инструмент молча пропускает. В логе остаётся предупреждение, тест зелёный, ощущение защищённости ложное.
  • Физический тип наугад. Тип сравнивается строкой с каталогом СУБД, поэтому timestamptz не пройдёт там, где база отвечает timestamp with time zone. Симптом узнаваемый, а именно все проверки типов валятся разом на рабочей таблице.
  • Непонятно, кто чинит красную сборку. Проверка стоит в конвейере продюсера, но правило качества придумал потребитель. Договариваться об этом надо до внедрения, а не в момент первого падения.
  • Версионирование по настроению. Без правила о том, что несовместимое изменение поднимает мажорную версию контракта, номер версии перестаёт нести информацию.

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

 

Когда применять, а когда не стоит

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

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

Практическая архитектура данных

Код курса
PRAR
Ближайшая дата курса
14 сентября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
76 800

 

Практика, контракт на таблицу PostgreSQL и падение сборки

По традиции весь код используемый в статье выкладываем на наш GitHub репозиторий

КОНТРАКТ ДАННЫХ (DATA CONTRACT) from airflow import DAG from airflow.operators.bash import BashOperator from datetime import datetime with DAG( dag_id="spark_submit_demo", start_date=datetime(2025, 1, 1), schedule="@daily", catchup=False ) as dag: run = BashOperator( task_id="run_job", bash_command="spark-submit app.py" ) GitHub code example КОНТРАКТ ДАННЫХ (DATA CONTRACT)

Демо развёрнуто на PostgreSQL 18.4 и таблице заказов на 5000 строк. Инструмент datacontract-cli версии 1.1.1 поставлен с экстрой postgres, стандарт ODCS 3.1.0. Ниже полный текст контракта на таблицу orders, файл orders_contract.yaml.

# ODCS 3.1.0, проверено datacontract-cli 1.1.1 против PostgreSQL 18.4, 2026-08-25
apiVersion: v3.1.0
kind: DataContract
id: bds-orders-contract
name: Orders
version: 1.0.0
status: active

description:
  purpose: Таблица заказов интернет-магазина, витрина для аналитики и биллинга.
  usage: Читают отчёты по выручке и джоба начисления бонусов.
  limitations: История хранится 90 дней, персональные данные покупателя тут не лежат.

tags:
  - orders
  - demo

team:
  - username: data-platform
    role: owner
  - username: analytics
    role: consumer

servers:
  - server: local
    type: postgres
    host: localhost
    port: 5432
    database: contract_demo
    schema: public

schema:
  - name: orders
    physicalName: orders
    logicalType: object
    physicalType: table
    description: Один заказ на строку.
    properties:
      - name: id
        logicalType: integer
        physicalType: bigint
        primaryKey: true
        required: true
        description: Идентификатор заказа.
      - name: customer_id
        logicalType: integer
        physicalType: integer
        required: true
        description: Идентификатор покупателя.
      - name: order_amount
        logicalType: number
        physicalType: numeric
        required: true
        description: Сумма заказа в рублях.
        quality:
          - metric: nullValues
            mustBe: 0
            description: Сумма заказа обязана быть заполнена.
      - name: status
        logicalType: string
        physicalType: text
        required: true
        description: Статус заказа.
        quality:
          - metric: invalidValues
            mustBe: 0
            arguments:
              validValues: [new, paid, shipped, cancelled]
            description: Статус берётся только из согласованного списка.
      - name: created_at
        logicalType: date
        physicalType: timestamp with time zone
        required: true
        description: Момент создания заказа.
    quality:
      - metric: rowCount
        mustBeGreaterThan: 0
        description: Витрина не должна приезжать пустой.

slaProperties:
  - property: frequency
    value: 1
    unit: d
  - property: latency
    value: 6
    unit: h

Проверка самого документа занимает мгновение и к базе не подключается.

# datacontract-cli 1.1.1, прогнано на стенде 2026-08-25
./.venv/bin/datacontract lint orders_contract.yaml
🟢 data contract is valid. Run 1 checks. Took 0.111909 seconds.

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

# datacontract-cli 1.1.1, PostgreSQL 18.4, прогнано на стенде 2026-08-25
export DATACONTRACT_POSTGRES_USERNAME=techfriends
export DATACONTRACT_POSTGRES_PASSWORD=stub
./.venv/bin/datacontract test orders_contract.yaml
🟢 data contract is valid. Run 19 checks. Took 0.908473 seconds.

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

Отдельная команда разворачивает контракт обратно в DDL, и это меняет направление зависимости. Первичен становится контракт, а таблица вторична.

# datacontract-cli 1.1.1, прогнано на стенде 2026-08-25
./.venv/bin/datacontract export sql --dialect postgres orders_contract.yaml
-- Data Contract: bds-orders-contract
-- SQL Dialect: postgres
CREATE TABLE orders (
  id bigint not null primary key,
  customer_id integer not null,
  order_amount numeric not null,
  status text not null,
  created_at timestamp with time zone not null
);

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

-- PostgreSQL 18.4, прогнано на стенде 2026-08-25
ALTER TABLE orders ALTER COLUMN order_amount TYPE text;
ALTER TABLE orders RENAME COLUMN status TO order_status;
применено: сумма заказа numeric(10,2) -> text
применено: колонка status переименована в order_status
схема после изменения, строк по-прежнему 5000:
  id: bigint
  customer_id: integer
  order_amount: text
  order_status: text
  created_at: timestamp with time zone

Та же команда проверки на той же таблице теперь даёт другой ответ.

🔴 data contract is invalid, found the following errors:
1) order_amount Check that field order_amount has physical type numeric:
expected physical type 'numeric' but the column is 'text'
2) status Check that field 'status' is present: Required column 'status' is
missing
3) status Check that field status has physical type text: Column 'status' is
missing
4) status Check that field status has no missing values: Column 'status' not
found
5) status Check that field status has invalid_count = 0: Column 'status' not
found
rc=1

Код возврата единица это и есть та самая точка, в которой падает шаг конвейера сборки. Одно переименование колонки утащило за собой четыре проверки, потому что без колонки не выполняется ничего из того, что на ней висело. В GitHub Actions вместо test ставится команда ci, которая дополнительно отдаёт отчёт в JUnit и JSON, а роняет сборку тем же ненулевым кодом возврата.

Из практических граблей стоит запомнить две. Пустая строка в переменной с паролем считается незаданным значением, и проверка падает до подключения к базе, поэтому локальному серверу с доверительной аутентификацией всё равно нужна непустая заглушка. А генерация DDL на паре логического типа date и физического timestamp with time zone пишет предупреждение о невозможности сопоставить тип и просто копирует физический тип как есть, что на результат не влияет, но в выводе висит.

 

Заключение

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

 

Референсные ссылки