FinCodeCow · Управление данными

Золотые источники и контракты данных: от принципов к работающему коду

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

Настоящий материал представляет собой обзор и практическую разработку идей из книги Питхейн Стренгхольт, «Масштабируемые данные. Лучшие шаблоны высоконагруженных архитектур» (СПб.: Питер, 2022; оригинал: Piethein Strengholt, «Data Management at Scale», O'Reilly Media, 2020).

Прямые ссылки на главы книги и её принципы приводятся с указанием источника. Остальной текст — авторская систематизация, расширение принципов до рабочего кода, сопоставление с актуальными инструментами 2026 года (PyDeequ, dbt, JSON Schema-контракты, RAG-пайплайны) и дополнительные замечания, выходящие за пределы исходного текста книги.

§Проблема, которая не стареет

В отчёте HR и финансов — разные суммы по одному клиенту. В воронке продаж — другие имена, чем в CRM. Каждый, кто работал с корпоративными данными, видел это, и почти каждый винил «грязные данные». На деле причина обычно глубже: данные разошлись потому, что не существует места, где им положено быть одинаковыми.

Рассмотрим типичную сцену, знакомую почти любой компании среднего размера и старше. Клиентка Ольга, руководитель отдела закупок, меняет в CRM рабочий телефон. Через месяц маркетинг получает телефоны из выгрузки ERP — со старым номером, потому что синхронизация в ERP шла из CRM, но поправку внесли в интерфейс CRM «для текущей кампании», минуя API. Через квартал финансовый сервис сверяет остатки по договору с биллингом — и находит расхождение в 4 тысячи рублей: биллинг считает по событию активации, ERP — по данным накладной, а обе системы «владели» датой активации. Отчётность собирается из копий, копии расходятся, а расхождения всплывают в самых дорогих местах — на сверке с контрагентом или на налоговой проверке.

Питхейн Стренгхольт (главный архитектор данных ABN AMRO) в книге «Масштабируемые данные» называет место, где этот сценарий останавливается: золотой источник — авторитетное приложение, в котором все достоверные данные создаются и обрабатываются в определённом ограниченном контексте. А золотой набор данных — подлинные, уникальные и точные данные, которые из него выходят. Это не маркетинговые термины: это контракт между системами, без которого распределённая архитектура данных деградирует в «двухточечный ад» — десятки копий одного и того же факта, каждая со своей версией правды.

В этой статье — семь принципов Стренгхольта (в первой редакции обзора их было шесть, седьмой заслужил отдельного слова), контракт на поставку данных и рабочий код: валидатор доставки, который ловит нарушение контракта до того, как испорченные данные попадут в аналитику. Плюс — то, чего в книге нет, а в 2026-м стало стандартом: проверки качества через PyDeequ и dbt, политика версионирования контрактов и прямой мост к RAG-пайплайнам.

• • •

IСемь принципов, которые переживут любой стек

Формулировки из главы 2 книги, сгущённые до сути — с анти-паттерном к каждому.

Принцип 01

Изменения происходят только в золотых источниках

Данные меняются там, где создаются, и с одобрения владельца — не в копиях и не в отчётах.

Анти-паттерн

Аналитик «подправил» статус клиента прямо в витрине, потому что источник недоступен. Через месяц витрина и источник уже рассказывают разные истории, и никто не помнит, кто что правил.

Если правку нельзя внести в источник — значит, либо у источника нет API, либо у правки нет владельца. Обе проблемы решаются дороже, если тянуть их месяцами.

Принцип 02

Поставляются только золотые наборы данных

Из системы-источника уходят оригинальные данные, а не их очередные интерпретации.

Анти-паттерн

Команда отчётности просит источник «отдать данные уже агрегированными, как нам удобно». Через полгода агрегатное правило меняется — а под него подпадают восемь витрин, построенных на старой логике.

Принцип 03

Золотой набор — подмножество одного приложения

Набор, охватывающий несколько систем, — не золотой, а вопрос о его владельце открыт навсегда.

Анти-паттерн

«Сводный реестр клиентов» из CRM, биллинга и ERP, который завели как общий справочник. При каждом расхождении систем начинается спор, чьё значение правильное — и это спор о данных, у которых нет владельца.

Принцип 04

Золотые наборы и элементы регистрируются централизованно

Метаданные о владельцах доступны всем — иначе прозрачность невозможна.

Анти-паттерн

Владение описано в вики-странице, которую редактировали в последний раз три года назад; актуальный владелец живёт в голове инженера, который уволился.

Принцип 05

Новые факты — новый владелец

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

Анти-паттерн

«LTV-клиента» построили соединением биллинга, логов и CRM — и никто не назначил владельца факта LTV, потому что «это же просто join». Когда маркетинг и финансы получили разные LTV, разбираться стало некому.

Именно принцип 5 отвечает на вопрос «кто отвечает за эту витрину»: не за таблицу, а за факт.

Принцип 06

Распространять чужие золотые наборы без одобрения владельца нельзя

Потребитель, который раздаёт данные дальше, становится поставщиком — и обязан соблюдать те же правила.

Анти-паттерн

Команда А отдала выгрузку команде Б «просто посмотреть», команда Б отлила её в общий слой для команды В. Через два года цепочка из четырёх передач не имеет ни владельца, ни метаданных — а данные из неё уже ушли в ML-модель и в партнёрский кабинет.

Принцип 07

Не создавайте хранилищ предметных данных без необходимости

Неуправляемые копии золотых наборов дороги в обслуживании и плохо контролируются. Хранить данные у потребителя оправдано только тогда, когда нужно сохранить вновь созданные данные.

Анти-паттерн

Каждый аналитический проект начинается с «сделаем свою копию, чтобы никого не дёргать» — и организация накапливает слой копий, ни одна из которых не обновляется с момента создания.

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

Итоги раздела I
Ключевое положение

Золотой источник — авторитетное приложение, в котором факты рождаются и изменяются по бизнес-правилам. Золотой набор — подлинные данные, которые из него выходят, и они всегда подмножество одного приложения.

Ограничение

Принципы 1–7 — это не технические решения, а контракт между командами. Их нельзя купить инструментом — только договориться и зафиксировать.

Практическое следствие

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

• • •

IIКак выбрать золотой источник: три спора о владении

Самый частый вопрос на этом месте: «хорошо, а чья система станет золотым источником?» Стренгхольт честен: «поиск надёжных приложений порой непрост» — и допускает исключения, когда золотой набор не управляется той системой, которая фактически создала данные. Разберём три типичных спора и как их решает модель.

Спор 1 · Операционная система против витрины

У компании есть биллинг (создаёт факты) и витрина отчётности (агрегирует красивее). Витрина удобнее — но удобство не создаёт владения. Критерий один: где факт рождается и где его изменяют бизнес-правила — там и золотой источник.

Витрина может владеть только своими новыми фактами (например, «рейтинг клиента по нашей методике»), а не чужими датами активации.

Спор 2 · Два источника рождены одновременно

Дату активации создаёт биллинг, но сумма договора живёт в ERP. Классическая ошибка — назначить владельцем «систему, где поле называется красивее».

Правильный разбор: поле «сумма договора» в биллинге — это копия факта из ERP, и если она попадает в золотой набор биллинга, она должна тянуть за собой происхождение: ссылку на элемент из ERP и одобрение владельца. Форма таблицы не создаёт владения — только место рождения факта.

Спор 3 · Наследие против нового стека

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

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

Отдельно важен переход «потребитель → поставщик». Если интегрированные данные нужно раздать в другие контексты, потребитель становится поставщиком: данные возвращаются на уровень данных, и он обязан соблюдать все критерии поставщика. При этом Стренгхольт советует различать золотые данные и интегрированные данные: происхождение интегрированных показывает все зависимости от чужих областей, а распространение требует одобрения владельцев оригиналов.

• • •

IIIКонтракт на поставку: от абзаца книги к инструменту

Стренгхольт пишет, что поставка и потребление данных обеспечиваются контрактом: условия использования, SLA, версии и совместимость интерфейсов. В главе 3 он даёт пример дескриптора в формате CSVW — XML-файл, описывающий владельца, контакт, формат, размеры и контрольные суммы фрагментов. Концептуально это было на три года впереди — но к 2026 году такой дескриптор должен уметь читать любой пайплайн, и XML здесь уже не лучший выбор.

Модернизируем дескриптор книги до JSON Schema-контракта — сохранив все его поля и добавив то, чего в 2020-м ещё не было: машиночитаемые пороги качества и классификацию.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "crm_customer_data",
  "contract_version": "1.2.0",
  "owner": {
    "id": 63845,
    "contact": "John Snow <johnsnow@example.com>",
    "department": "HR"
  },
  "golden_source": { "application_id": 23, "application_name": "CRMR" },
  "delivery": {
    "format": "csv",
    "compression": "gzip",
    "separator": "\t",
    "chunks": [
      { "location": "data_1.csv", "size": 1234, "checksum_sha256": "…" }
    ]
  },
  "sla": {
    "uptime": "99.5%",
    "max_error_rate": "0.1%",
    "refresh": "daily 03:00 UTC",
    "deprecation_notice_days": 90
  },
  "usage_terms": ["production", "test"],
  "classification": "sensitive-pii",
  "quality": {
    "completeness_min": 0.98,
    "uniqueness_keys": ["customer_id"]
  },
  "schema": [
    { "name": "customer_id", "type": "int",    "nullable": false, "golden_element": 987, "sensitive": false },
    { "name": "first_name",  "type": "string", "nullable": true,  "golden_element": 988, "sensitive": true }
  ]
}
Три поля, которых нет у Стренгхольта, а должны быть у вас

golden_element — связь каждого поля с зарегистрированным элементом данных (центральный реестр из принципа 4).

classification — классификация на уровне контракта, а не только на уровне отдельного поля.

quality — числовые пороги, по которым поставка принимается или отклоняется автоматически.

• • •

IVПолитика версионирования: что делать, когда контракт надо менять

Контракт без политики версионирования — это обещание, которое забывают. Рабочее правило заимствуем из API-мира (глава 4 книги о совместимости интерфейсов — ровно об этом):

  • patch 1.2.0 → 1.2.1 — исправления валидатора, документации, метаданных без изменения схемы. Потребители обновляются свободно.
  • minor 1.2 → 1.3 — добавление nullable-полей, новых колонок в конец. Обратно совместимо: старые потребители не ломаются, новые поля игнорируют.
  • major 1.x → 2.0 — изменение типа, переименование, удаление поля. Старая версия живёт deprecation_notice_days (у нас — 90 дней), потом отмирает.

Механика deprecation важнее самой нумерации: при выпуске major-версии контракт публикуется в двух версиях одновременно, валидатор приёмки подхватывает обе, а в метаданные delivery-канала ставится deprecation_after — дата, после которой старая версия отвергается с внятной ошибкой, а не с загадочным падением джоба. Тот пункт чек-листа про «потребители узнают о поломке по падению джобов» закрывается ровно этим механизмом — и это пятнадцать строк конфигурации, а не квартал работ.

• • •

VВалидатор доставки: проверка на границе, а не в отчёте

Ключевая идея книги — качество контролируется в источнике или в RDS, до приёма данных, а не постфактум в дашборде. И это связано с другим принципом книги: RDS неизменяемы и доступны только для чтения — из них нельзя восстановить разные версии истины для одних и тех же данных, а «истину» можно доработать только в золотом источнике. Если поставку не проверить на границе, испорченные данные осядут в неизменяемом хранилище, и вернуть их можно только перестройкой — то есть дороже в десять раз.

Ниже — валидатор, который проверяет поставку на соответствие контракту: формат, размеры, контрольные суммы, схему, типы, обязательность полей. При нарушении — отчёт владельцу и отказ, ровно как в «службе квитирования» из главы 6 книги.

#!/usr/bin/env python3
"""Валидатор доставки данных по контракту.

Использование:
    python3 validate_delivery.py delivery/ data_contract.json
Коды возврата: 0 — принята, 1 — отклонена с отчётом.
"""
import csv, hashlib, json, os, sys
from datetime import datetime

def sha256(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()

def fail(check, msg):
    print(f"[FAIL] {check}: {msg}")
    return False

def ok(check, msg):
    print(f"[ OK ] {check}: {msg}")
    return True

def parse_type(value, declared):
    if declared == "int":
        int(value); return
    if declared == "float":
        float(value); return
    if declared == "date":
        datetime.strptime(value, "%Y-%m-%d"); return
    return  # string — только nullable-проверка

def main(delivery_dir, contract_path):
    with open(contract_path, encoding="utf-8") as f:
        c = json.load(f)
    verdict = True
    print(f"Контракт: {c['title']} v{c['contract_version']}, "
          f"владелец #{c['owner']['id']} ({c['owner']['department']})")
    print("-" * 70)

    # 1. Формат поставки
    if c["delivery"]["format"] != "csv":
        verdict &= fail("delivery.format",
                        f"поддержан только csv, в контракте {c['delivery']['format']}")
    else:
        ok("delivery.format", "csv")

    # 2. Контрольные суммы: файл не менялся после подписания контракта
    declared = {ch["location"]: ch for ch in c["delivery"]["chunks"]}
    found = set()
    for fname in sorted(os.listdir(delivery_dir)):
        if not fname.endswith(".csv"):
            continue
        found.add(fname)
        path = os.path.join(delivery_dir, fname)
        ch = declared.get(fname)
        if ch is None:
            verdict &= fail("delivery.chunks", f"{fname} не объявлен в контракте")
            continue
        if os.path.getsize(path) != ch["size"]:
            verdict &= fail("size", f"{fname}: {os.path.getsize(path)} против {ch['size']}")
        else:
            ok("size", fname)
        actual = sha256(path)
        if actual != ch["checksum_sha256"]:
            verdict &= fail("checksum", f"{fname}: изменился после подписания ({actual[:12]}…)")
        else:
            ok("checksum", fname)
    for missing in set(declared) - found:
        verdict &= fail("delivery.chunks", f"{missing} объявлен, но отсутствует")

    # 3. Схема: колонки, типы, nullability
    rows, header = [], None
    for fname in sorted(f for f in os.listdir(delivery_dir) if f.endswith(".csv")):
        with open(os.path.join(delivery_dir, fname), encoding="utf-8") as fh:
            r = csv.DictReader(fh, delimiter=c["delivery"]["separator"])
            header = header or r.fieldnames
            rows.extend(r)
    expected = [s["name"] for s in c["schema"]]
    if header != expected:
        verdict &= fail("schema.columns", f"ожидались {expected}, получены {header}")
    else:
        ok("schema.columns", "совпадают с контрактом")

    errors = []
    for i, row in enumerate(rows):
        for s in c["schema"]:
            v = row.get(s["name"])
            if v in (None, "") and not s["nullable"]:
                errors.append(f"строка {i+1}: пусто в обязательном поле {s['name']}")
            elif v:
                try:
                    parse_type(v, s["type"])
                except ValueError:
                    errors.append(f"строка {i+1}: {s['name']}={v!r} не {s['type']}")
    if errors:
        verdict &= fail("schema.checks", f"{len(errors)} нарушений: {errors[:3]}…")
    else:
        ok("schema.checks", "типы и обязательность в порядке")

    if rows:
        print(f"[ OK ] delivery.rows: принято {len(rows)} строк")
        print("-" * 70)
        print("ПОСТАВКА ПРИНЯТА" if verdict
              else "ПОСТАВКА ОТКЛОНЕНА — верните отчёт владельцу (принцип 8)")
    sys.exit(0 if verdict else 1)

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(__doc__); sys.exit(2)
    main(sys.argv[1], sys.argv[2])

Как это работает на тестовой поставке из 100 строк

Чистая поставка:

[ OK ] checksum: data_1.csv
[ OK ] checksum: data_2.csv
[ OK ] schema.checks: типы и обязательность в порядке
ПОСТАВКА ПРИНЯТА

А теперь кто-то дописал в data_2.csv строку после подписания контракта:

[FAIL] size: data_2.csv: 1552 байт против 1525 в контракте
[FAIL] checksum: data_2.csv: изменился после подписания (0627eb4c4058…)
ПОСТАВКА ОТКЛОНЕНА — верните отчёт владельцу (принцип 8)

Обратите внимание: схема при этом осталась корректной — колонки на месте, типы сходятся. Контрольная сумма ловит то, что не ловит ни одна проверка схемы: подмену содержимого. Именно поэтому в дескрипторе Стренгхольта checksum появился раньше, чем в большинстве реальных конвейеров.

Граница применимости

Валидатор проверяет доставку, а не содержимое фактов. Он не узнает, что «дата активации» у биллинга и ERP указывает на разные события — для этого нужны проверки качества следующего уровня.

• • •

VIПроверки качества: Deequ, dbt и ассистент

Схема и целостность — технический уровень проверки. Дальше идут функциональные показатели качества: полнота, уникальность, согласованность. Стренгхольт рекомендует Deequ (AWS) и Delta Lake — Spark-фреймворк прогоняет сотни миллионов строк за минуты. Вот как выглядит набор проверок на PyDeequ, соответствующий порогам из контракта:

from pyspark.sql import SparkSession
from pydeequ.checks import Check, CheckLevel
from pydeequ.verification import VerificationSuite

spark = SparkSession.builder.appName("quality-gate").getOrCreate()
df = spark.read.parquet("crm_customer_data/")

check = Check(spark, CheckLevel.Error, "contract-1.2.0")

result = VerificationSuite(spark) \
    .onData(df) \
    .addCheck(
        check.hasSize(lambda x: x > 0)                          # поставка не пустая
        .isComplete("customer_id")                              # обязательность
        .isUnique("customer_id")                                # uniqueness_keys
        .hasCompleteness("first_name", lambda x: x >= 0.98)     # completeness_min
        .isContainedIn("status", ["active", "frozen", "closed"])
    ) \
    .run()

result_df = VerificationSuite(spark).onData(df).run()
check_result_as_dataframe(spark, result_df)

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

Промт для генерации черновика проверок

«Ты — инженер по качеству данных. Вот описание домена: [описание]. Контракт полей: [schema]. Сгенерируй: (1) Deequ-проверки на Scala/PyDeequ — uniqueness для ключей, completeness для обязательных полей, referential integrity для связей; (2) dbt-тесты из того же набора; (3) для каждой проверки — причину, почему она нужна.»

Предостережение

Сгенерированное — это черновик, а не готовый контроль. LLM уверенно напишет вам проверку на уникальность first_name — и формально будет права, а фактически сломает процесс, потому что одноимённых Ивановых много. Без ревью владельцем домена вы получите аккуратно упакованный мусор с высокой уверенностью.

Правильный порядок: генерация → ревью владельца (принцип 5 — он и есть владелец этих фактов) → включение в контракт.

Отдельно стоит сказать про самый дешёвый уровень проверок, который часто пропускают: сверку метаданных друг с другом. Стренгхольт в главе 7 прямо рекомендует: «для проверки общей целостности метаданных управление данными должно использовать те же инструменты качества данных, которые применяются для профилирования любых других данных». Проще говоря: реестр золотых источников — тоже данные, и он ломается так же, как ломается ваша витрина. Если в реестре два владельца у одного набора или набор привязан к несуществующему приложению — это должно ронять CI, а не ждать годовой инвентаризации.

• • •

VIIРеестр золотых источников: артефакт, без которого принципы — лозунги

Принцип 4 требует централизованной регистрации, но что это значит на практике? У Стренгхольта это конкретные компоненты, и они переживают любой стек:

List of Golden Sources (LoGS)

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

Репозиторий приложений

Центральное хранилище всех приложений и их владельцев (в книге — вплоть до ServiceNow): кто развёрнут, где продаётся, какой у него рейтинг конфиденциальности, целостности и доступности. Это второй край связи: элемент данных указывает на приложение, приложение — на владельца.

Таблица соответствия идентификаторов

Из главы 9: когда объект становится предметом MDM, мастер-идентификаторы возвращаются назад в системы-источники и сохраняются там; альтернатива — таблица поиска, которая описывает, какие локальные данные каким мастер-данным принадлежат, и публикуется в MDM. Без этой таблицы каждая сверка — ручная археология.

Минимальная работающая версия реестра для организации, которая только начинает: JSON-файл в git-репозитории со списком наборов (владелец, приложение, контракт, классификация), проверки целостности в CI и процедуру изменения через pull request. Это принципиально проще каталогов-монстров и, что важно, честно отражает принцип 5: изменение владельца — это явное, обсуждаемое решение, а не тихая правка в UI каталога.

Вернёмся к Ольге из первого абзаца. С реестром её история решается за пятнадцать минут: поиск по элементу phone_work показывает, что золотой набор crm_customer_data принадлежит владельцу из CRM, поставка проверена checksum, а витрина маркетинга получила данные по принципу 6 — с одобрения владельца. Правка «минуя API» видна в origin — это нарушение принципа 1, и исправляется она в источнике, а не в копии.

• • •

VIIIУгол для ИИ-систем: золотой источник как первый закон Retrieval

Всё сказанное напрямую бьёт по RAG и LLM-пайплайнам. Retrieval, который индексирует «что-то похожее на золотой набор» — выгрузку из копии копии, — производит тихие галлюцинации: модель уверенно отвечает по данным, которые уже разошлись с первоисточником.

Разберём на той же истории с Ольгой. Агент поддержки получает вопрос: «какой у клиента телефон для экстренной связи?» В индексе — три фрагмента: из витрины маркетинга (старый номер), из выгрузки HR (без номера), из золотого набора CRM (актуальный). Ранжирование по релевантности не знает, что один из фрагментов «чище» — оно сортирует по текстовому совпадению с запросом. Модель с вероятностью, зависящей от формулировки, возьмёт старый номер — и ответит уверенно, без всяких оговорок.

Правило отсюда простое, и оно формулируется через ту же модель: индексируйте только золотые наборы или данные с контрактной гарантией свежести и происхождения — и храните в метаданных чанка golden_source и contract_version, чтобы можно было ответить на вопрос «из какой версии фактов этот ответ».

Минимальный набор полей в метаданных каждого чанка RAG

golden_source_id, dataset_id (из LoGS), contract_version, classification, refresh_at. Пять полей — и вопрос «на что ссылается ваш ответ» перестаёт быть философией.

• • •

IXМетрики зрелости: как понять, что принципы внедряются

Чек-лист диагностирует поломку; для управления изменениями нужны метрики, которые не устареют вместе со стеком:

1
Доля данных с известным владельцем

Сколько процентов наборов в LoGS имеют назначенного владельца и живой контакт. Целевое значение для первого полугодия — 80%; не достигли — проблема в процессе регистрации, а не в инструментах.

2
Число нарушений принципа 6 за квартал

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

3
Медианное время разрешения расхождения

От обнаружения («в отчётах разные суммы») до исправления в золотом источнике. Растёт — значит, происхождение не работает или владельца нельзя найти.

4
Доля поставок, проверяемых программой

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

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

• • •

XЧек-лист практикующего

Диагностика «у вас сломано, если…» — теперь с рецептом к каждому пункту:

  1. Одно и то же значение клиента в двух системах различается, и никто не может сказать, какая из них первична. Назначьте владельца факта по принципу 5, заведите элемент в LoGS, проведите разовую сверку по таблице соответствия идентификаторов.
  2. За витрину или отчёт отвечает команда, которая не создавала данные в нём. Разделите владение: витрина владеет своими новыми фактами, исходные — источником. Зафиксируйте в контракте.
  3. В организации есть копии данных, которые распространяются дальше без ведома владельца — а владелец узнаёт об этом из письма аналитика. Введите правило принципа 6: передача — через одобрение владельца; техническая реализация — флаг distribution_approved в контракте и проверка валидатором при выходе в новый контекст.
  4. Поставка данных в аналитическое хранилище проверяется глазами, а не программой: нет checksum, нет схемы-контракта, нет отказа в приёме. Внедрите валидатор из этой статьи — он покрывает минимальный уровень; для Spark-нагрузок добавьте Deequ, для аналитических моделей — dbt-тесты.
  5. При изменении схемы источника потребители узнают о поломке по падению джобов — а не по deprecation-уведомлению за 90 дней. Политика версионирования: major-версия с двойной публикацией и датой deprecation_after в метаданных канала.
  6. Новый факт (агрегат, витрина, объединение) появляется без назначенного владельца — потому что «это же просто join». Правило: новый факт — новый владелец. Никаких исключений на «маленькие» факты: маленькие факты и растут в институты без ответственных.
  7. У вас нет реестра золотых источников — владение описано в вики, презентациях и памяти людей. Заведите LoGS минимум в виде JSON в git; проверки целостности реестра в CI — тот же валидатор, только к метаданным.

Каждый пункт — прямое следствие одного из семи принципов. Прошли по чек-листу с четырьмя «да» и больше — архитектура данных уже не масштабируется, сколько бы инструментов вы ни купили.

• • •

XIЧто дальше

Эта статья — первая из трёх, построенных на книге. Следующая — дерево решений по трём архитектурам интеграции (RDS / API / стриминг): когда какой канал, какие анти-паттерны и как автоматизировать выбор. Третья — метаданные как клей: каталог, происхождение, самообслуживание. До встречи в следующем материале — и присылайте свои истории про «кто владеет этим фактом», они живее любого учебного примера.

Общий итог обзора

Материал Стренгхольта — про то, что архитектура данных начинается не с инструментов, а с договорённостей о владении. Золотой источник — это не про технологии, а про то, что у каждого факта есть место рождения и владелец, который отвечает за его изменения. Контракт — единственная часть этой модели, которую можно проверить программой, и именно с него проще всего начать: валидатор на 100 строк ловит подмену содержимого, которую не ловит ни одна проверка схемы.

Расширения 2026 года — PyDeequ, dbt-тесты, JSON Schema-контракты, метаданные чанков RAG — не отменяют принципов, а дают им рабочий интерфейс. Главный вывод: инвестиции в договорённости о владении окупаются быстрее, чем инвестиции в очередной каталог. Каталог без владельцев — это витрина без товара.

Источники: 1. Стренгхольт П. «Масштабируемые данные. Лучшие шаблоны высоконагруженных архитектур» — СПб.: Питер, 2022 (главы 2, 3, 6, 7, 9). Оригинал: Piethein Strengholt, «Data Management at Scale», O'Reilly Media, 2020. — принципы 1–8, контракт на поставку, CSVW-дескриптор, неизменяемость RDS, List of Golden Sources, различение золотых и интегрированных данных, таблицы соответствия мастер-идентификаторов. 2. AWS Labs, Deequ — открытый фреймворк проверки качества данных. 3. Delta Lake, валидация схемы и качества — в книге рекомендованы для проверки поставок. 4. dbt Labs, тесты моделей и колонок — современный стандарт declarative-проверок, о котором книга ещё не могла знать.

FinCodeCow · Data Management

Golden Sources and Data Contracts: From Principles to Working Code

Why data breaks between systems — and how to stop it at the boundary, not in the report. A review of Piethein Strengholt's principles with production-ready code.
About the source

This material is a review and practical development of ideas from Piethein Strengholt, «Data Management at Scale» (O'Reilly Media, 2020; Russian translation: «Масштабируемые данные», Питер, 2022).

Direct references to the book's chapters and principles are attributed to the source. The rest of the text is the author's systematization, extension of the principles to working code, comparison with current 2026 tools (PyDeequ, dbt, JSON Schema contracts, RAG pipelines), and additional observations beyond the original book.

§The Problem That Doesn't Age

HR and finance reports show different totals for the same customer. The sales funnel lists different names than the CRM. Anyone who has worked with enterprise data has seen it, and almost everyone blamed "dirty data." The real cause is usually deeper: data diverged because there's no single place where it's supposed to be identical.

Consider a typical scene familiar to almost any mid-sized or older company. Olga, a procurement manager, changes her work phone in the CRM. A month later, marketing pulls phones from an ERP export — with the old number, because ERP was syncing from CRM but the correction was made in the CRM UI "for the current campaign," bypassing the API. A quarter later, the finance service reconciles contract balances with billing — and finds a 4,000-ruble discrepancy: billing counts by activation event, ERP by invoice data, and both systems "owned" the activation date.

Piethein Strengholt (Chief Data Architect at ABN AMRO) in «Data Management at Scale» names the place where this scenario stops: the golden source — the authoritative application where all reliable data is created and processed within a specific bounded context. And the golden dataset — the authentic, unique and accurate data that leaves it. These are not marketing terms: they are a contract between systems, without which a distributed data architecture degrades into "two-point hell" — dozens of copies of the same fact, each with its own version of the truth.

This article covers Strengholt's seven principles, a delivery contract, and working code: a delivery validator that catches contract violations before corrupted data reaches analytics. Plus what the book couldn't know but has become standard by 2026: quality checks via PyDeequ and dbt, a contract versioning policy, and a direct bridge to RAG pipelines.

• • •

ISeven Principles That Outlive Any Stack

Formulations from Chapter 2 of the book, distilled to the essence — with an anti-pattern for each.

Principle 01

Changes only occur in golden sources

Data changes where it's created, with the owner's approval — not in copies or reports.

Anti-pattern

An analyst "fixed" a customer status directly in the mart because the source was unavailable. A month later the mart and the source tell different stories, and no one remembers who changed what.

Principle 02

Only golden datasets are delivered

Original data leaves the source system, not yet another interpretation of it.

Anti-pattern

The reporting team asks the source to "deliver data pre-aggregated, the way we like it." Six months later the aggregation rule changes — and eight marts built on the old logic fall under it.

Principle 03

A golden dataset is a subset of one application

A dataset spanning multiple systems is not golden — and its owner will forever be an open question.

Anti-pattern

A "consolidated customer registry" from CRM, billing and ERP, set up as a shared directory. At every discrepancy between systems, an argument starts about whose value is correct — an argument about data that has no owner.

Principle 04

Golden datasets and elements are registered centrally

Metadata about owners is available to everyone — otherwise transparency is impossible.

Anti-pattern

Ownership is described in a wiki page last edited three years ago; the actual owner lives in the head of an engineer who has left.

Principle 05

New facts — new owner

Filtering, renaming fields, aggregating — syntactic transformations, the owner doesn't change. Once data is joined into a new fact — new context, new owner.

Anti-pattern

A "customer LTV" was built by joining billing, logs and CRM — and nobody assigned an owner to the LTV fact, because "it's just a join." When marketing and finance got different LTVs, there was no one to sort it out.

Principle 5 answers the question "who owns this mart": not the table, but the fact.

Principle 06

Distributing someone else's golden datasets without the owner's approval is not allowed

A consumer that passes data further becomes a supplier — and must obey the same rules.

Anti-pattern

Team A handed an export to Team B "just to look at," Team B dumped it into a shared layer for Team C. Two years later, a chain of four hops has neither owner nor metadata — and data from it has already flowed into an ML model and a partner portal.

Principle 07

Don't create domain data stores unless necessary

Unmanaged copies of golden datasets are expensive to maintain and poorly controlled. Storing data at the consumer is justified only when newly created data must be persisted.

Anti-pattern

Every analytics project starts with "let's make our own copy so we don't bother anyone" — and the organization accumulates a layer of copies, none of which has been updated since creation.

The book adds an eighth principle — the contract — and we'll get to it now, because the contract is the only principle that can be checked by a program.

Section I Summary
Key point

A golden source is the authoritative application where facts are born and changed by business rules. A golden dataset is the authentic data that leaves it, and it's always a subset of one application.

Limitation

Principles 1–7 are not technical solutions but a contract between teams. They cannot be bought as a tool — only agreed upon and fixed.

Practical consequence

The only principle that can be checked by a program is the eighth — the delivery contract. It becomes the minimum entry point.

• • •

IIChoosing a Golden Source: Three Ownership Disputes

The most frequent question here: "fine, which system becomes the golden source?" Strengholt is honest: "finding reliable applications is sometimes difficult" — and allows exceptions where a golden dataset is not managed by the system that actually created the data. Let's examine three typical disputes and how the model resolves them.

Dispute 1 · Operational system vs. mart

The company has billing (creates facts) and a reporting mart (aggregates more nicely). The mart is more convenient — but convenience doesn't create ownership. The criterion is one: where the fact is born and where its business rules are changed — that's the golden source.

The mart can only own its own new facts (e.g., "customer rating by our methodology"), not someone else's activation dates.

Dispute 2 · Two sources born simultaneously

The activation date is created by billing, but the contract amount lives in ERP. The classic mistake is to appoint "the system where the field sounds nicer" as the owner.

The correct parsing: the "contract amount" field in billing is a copy of a fact from ERP, and if it lands in billing's golden dataset, it must drag its provenance with it: a reference to the ERP element and the owner's approval. A table's shape doesn't create ownership — only the place of a fact's birth does.

Dispute 3 · Legacy vs. new stack

The old system has run for twenty years, the new application is elegant but not covered by integrations. Strengholt proposes role-based classification: an application is labeled a golden source or a DDS (domain data store), and classification is not a shameful label but an explicit distribution of responsibility.

A DDS that itself creates new facts becomes a new golden source — migration is possible, but it passes through explicit ownership assignment, not silence.

The "consumer → supplier" transition is separately important. If integrated data must be distributed to other contexts, the consumer becomes a supplier: the data returns to the data layer, and it must comply with all supplier criteria. Strengholt also advises distinguishing golden data from integrated data: integrated data's provenance shows all dependencies on other domains, and distribution requires approval from the original owners.

• • •

IIIDelivery Contract: From a Book Paragraph to a Tool

Strengholt writes that data delivery and consumption are ensured by a contract: usage terms, SLA, versions, and interface compatibility. In Chapter 3 he gives an example descriptor in CSVW format — an XML file describing owner, contact, format, sizes, and chunk checksums. Conceptually this was three years ahead — but by 2026 any pipeline should be able to read such a descriptor, and XML is no longer the best choice.

Let's modernize the book's descriptor to a JSON Schema contract — preserving all its fields and adding what didn't exist in 2020: machine-readable quality thresholds and classification.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "crm_customer_data",
  "contract_version": "1.2.0",
  "owner": {
    "id": 63845,
    "contact": "John Snow <johnsnow@example.com>",
    "department": "HR"
  },
  "golden_source": { "application_id": 23, "application_name": "CRMR" },
  "delivery": {
    "format": "csv",
    "compression": "gzip",
    "separator": "\t",
    "chunks": [
      { "location": "data_1.csv", "size": 1234, "checksum_sha256": "…" }
    ]
  },
  "sla": {
    "uptime": "99.5%",
    "max_error_rate": "0.1%",
    "refresh": "daily 03:00 UTC",
    "deprecation_notice_days": 90
  },
  "usage_terms": ["production", "test"],
  "classification": "sensitive-pii",
  "quality": {
    "completeness_min": 0.98,
    "uniqueness_keys": ["customer_id"]
  },
  "schema": [
    { "name": "customer_id", "type": "int",    "nullable": false, "golden_element": 987, "sensitive": false },
    { "name": "first_name",  "type": "string", "nullable": true,  "golden_element": 988, "sensitive": true }
  ]
}
Three fields Strengholt doesn't have that you should

golden_element — links each field to a registered data element (the central registry from Principle 4).

classification — classification at the contract level, not just per field.

quality — numeric thresholds by which a delivery is accepted or rejected automatically.

• • •

IVVersioning Policy: What to Do When the Contract Must Change

A contract without a versioning policy is a promise that gets forgotten. We borrow the working rule from the API world (Chapter 4 of the book on interface compatibility is exactly about this):

  • patch 1.2.0 → 1.2.1 — validator, documentation, metadata fixes without schema changes. Consumers update freely.
  • minor 1.2 → 1.3 — adding nullable fields, new columns at the end. Backward compatible: old consumers don't break, new fields are ignored.
  • major 1.x → 2.0 — type change, rename, field removal. The old version lives for deprecation_notice_days (90 days here), then dies.

The deprecation mechanics matter more than the numbering itself: when a major version is released, the contract is published in two versions simultaneously, the acceptance validator picks up both, and a deprecation_after date is set in the delivery channel metadata — a date after which the old version is rejected with a clear error, not with a mysterious job failure.

• • •

VDelivery Validator: Checking at the Boundary, Not in the Report

The key idea of the book: quality is controlled in the source or in the RDS, before data acceptance, not post-factum in a dashboard. It's tied to another principle of the book: RDSs are immutable and read-only — you cannot restore different versions of the truth for the same data from them, and "truth" can only be amended in the golden source. If a delivery is not checked at the boundary, corrupted data settles into the immutable store, and the only way to fix it is a rebuild — ten times more expensive.

Below is a validator that checks a delivery against the contract: format, sizes, checksums, schema, types, field nullability. On violation — a report to the owner and a rejection, exactly as in the "acknowledgement service" from Chapter 6 of the book.

#!/usr/bin/env python3
"""Delivery validator against a contract.

Usage:
    python3 validate_delivery.py delivery/ data_contract.json
Exit codes: 0 — accepted, 1 — rejected with a report.
"""
import csv, hashlib, json, os, sys
from datetime import datetime

def sha256(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()

def fail(check, msg):
    print(f"[FAIL] {check}: {msg}")
    return False

def ok(check, msg):
    print(f"[ OK ] {check}: {msg}")
    return True

def parse_type(value, declared):
    if declared == "int":
        int(value); return
    if declared == "float":
        float(value); return
    if declared == "date":
        datetime.strptime(value, "%Y-%m-%d"); return
    return  # string — nullable check only

def main(delivery_dir, contract_path):
    with open(contract_path, encoding="utf-8") as f:
        c = json.load(f)
    verdict = True
    print(f"Contract: {c['title']} v{c['contract_version']}, "
          f"owner #{c['owner']['id']} ({c['owner']['department']})")
    print("-" * 70)

    # 1. Delivery format
    if c["delivery"]["format"] != "csv":
        verdict &= fail("delivery.format",
                        f"only csv supported, contract has {c['delivery']['format']}")
    else:
        ok("delivery.format", "csv")

    # 2. Checksums: file unchanged since contract signing
    declared = {ch["location"]: ch for ch in c["delivery"]["chunks"]}
    found = set()
    for fname in sorted(os.listdir(delivery_dir)):
        if not fname.endswith(".csv"):
            continue
        found.add(fname)
        path = os.path.join(delivery_dir, fname)
        ch = declared.get(fname)
        if ch is None:
            verdict &= fail("delivery.chunks", f"{fname} not declared in contract")
            continue
        if os.path.getsize(path) != ch["size"]:
            verdict &= fail("size", f"{fname}: {os.path.getsize(path)} vs {ch['size']}")
        else:
            ok("size", fname)
        actual = sha256(path)
        if actual != ch["checksum_sha256"]:
            verdict &= fail("checksum", f"{fname}: changed after signing ({actual[:12]}…)")
        else:
            ok("checksum", fname)
    for missing in set(declared) - found:
        verdict &= fail("delivery.chunks", f"{missing} declared but missing")

    # 3. Schema: columns, types, nullability
    rows, header = [], None
    for fname in sorted(f for f in os.listdir(delivery_dir) if f.endswith(".csv")):
        with open(os.path.join(delivery_dir, fname), encoding="utf-8") as fh:
            r = csv.DictReader(fh, delimiter=c["delivery"]["separator"])
            header = header or r.fieldnames
            rows.extend(r)
    expected = [s["name"] for s in c["schema"]]
    if header != expected:
        verdict &= fail("schema.columns", f"expected {expected}, got {header}")
    else:
        ok("schema.columns", "match the contract")

    errors = []
    for i, row in enumerate(rows):
        for s in c["schema"]:
            v = row.get(s["name"])
            if v in (None, "") and not s["nullable"]:
                errors.append(f"row {i+1}: empty in required field {s['name']}")
            elif v:
                try:
                    parse_type(v, s["type"])
                except ValueError:
                    errors.append(f"row {i+1}: {s['name']}={v!r} not {s['type']}")
    if errors:
        verdict &= fail("schema.checks", f"{len(errors)} violations: {errors[:3]}…")
    else:
        ok("schema.checks", "types and nullability OK")

    if rows:
        print(f"[ OK ] delivery.rows: {len(rows)} rows accepted")
        print("-" * 70)
        print("DELIVERY ACCEPTED" if verdict
              else "DELIVERY REJECTED — return the report to the owner (Principle 8)")
    sys.exit(0 if verdict else 1)

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(__doc__); sys.exit(2)
    main(sys.argv[1], sys.argv[2])

How it works on a 100-row test delivery

A clean delivery:

[ OK ] checksum: data_1.csv
[ OK ] checksum: data_2.csv
[ OK ] schema.checks: types and nullability OK
DELIVERY ACCEPTED

And now someone appended a row to data_2.csv after the contract was signed:

[FAIL] size: data_2.csv: 1552 bytes vs 1525 in contract
[FAIL] checksum: data_2.csv: changed after signing (0627eb4c4058…)
DELIVERY REJECTED — return the report to the owner (Principle 8)

Note: the schema remained correct — columns are in place, types match. A checksum catches what no schema check catches: content substitution. That's why the checksum appeared in Strengholt's descriptor earlier than in most real pipelines.

Applicability boundary

The validator checks delivery, not fact content. It won't know that billing's and ERP's "activation date" points to different events — that requires next-level quality checks.

• • •

VIQuality Checks: Deequ, dbt and the Assistant

Schema and integrity are the technical level of validation. Beyond them lie functional quality metrics: completeness, uniqueness, consistency. Strengholt recommends Deequ (AWS) and Delta Lake — a Spark framework that runs hundreds of millions of rows in minutes. Here's a PyDeequ check set matching the contract's thresholds:

from pyspark.sql import SparkSession
from pydeequ.checks import Check, CheckLevel
from pydeequ.verification import VerificationSuite

spark = SparkSession.builder.appName("quality-gate").getOrCreate()
df = spark.read.parquet("crm_customer_data/")

check = Check(spark, CheckLevel.Error, "contract-1.2.0")

result = VerificationSuite(spark) \
    .onData(df) \
    .addCheck(
        check.hasSize(lambda x: x > 0)                          # delivery not empty
        .isComplete("customer_id")                              # required
        .isUnique("customer_id")                                # uniqueness_keys
        .hasCompleteness("first_name", lambda x: x >= 0.98)     # completeness_min
        .isContainedIn("status", ["active", "frozen", "closed"])
    ) \
    .run()

result_df = VerificationSuite(spark).onData(df).run()
check_result_as_dataframe(spark, result_df)

An AI assistant is appropriate here — but with a caveat that cannot be dropped. The prompt that generates a draft check suite from a domain description works like this:

Prompt for generating a draft check suite

"You are a data quality engineer. Here is the domain description: [description]. Field contract: [schema]. Generate: (1) Deequ checks in Scala/PyDeequ — uniqueness for keys, completeness for required fields, referential integrity for relationships; (2) dbt tests from the same set; (3) for each check — the reason why it's needed."

Caution

What's generated is a draft, not a finished control. An LLM will confidently write you a uniqueness check on first_name — technically correct but practically broken, because many people share the same first name. Without owner review, you get neatly packaged garbage with high confidence.

The right order: generation → owner review (Principle 5 — they are the owner of these facts) → inclusion in the contract.

Separately worth mentioning is the cheapest level of checks, often skipped: cross-checking metadata against each other. In Chapter 7 Strengholt directly recommends: "for checking overall metadata integrity, data management should use the same data quality tooling as it does for profiling any other data." In plain terms: the golden source registry is also data, and it breaks just like your mart breaks. If the registry has two owners for one dataset or a dataset tied to a non-existent application — that should fail CI, not wait for an annual inventory.

• • •

VIIGolden Source Registry: The Artifact Without Which Principles Are Slogans

Principle 4 requires centralized registration, but what does that mean in practice? Strengholt has specific components, and they outlive any stack:

List of Golden Sources (LoGS)

An autonomous repository holding general information about all unique data and its ownership. Important detail: registration is at the level of logical datasets, because relationships between datasets and applications are many-to-many: one application may have several data owners, and several physical datasets may originate from one application.

Application repository

A central store of all applications and their owners (in the book — down to ServiceNow): who is deployed, where it's sold, what its confidentiality, integrity and availability ratings are. This is the second edge of the link: a data element points to an application, an application to an owner.

Identifier mapping table

From Chapter 9: when an object becomes an MDM subject, master identifiers are returned to the source systems and stored there; the alternative is a lookup table that describes which local data belongs to which master data and is published to MDM. Without this table, every reconciliation is manual archaeology.

The minimum viable registry for an organization just starting: a JSON file in a git repository with a list of datasets (owner, application, contract, classification), integrity checks in CI, and a change procedure via pull request. This is fundamentally simpler than monster catalogs and, importantly, honestly reflects Principle 5: a change of owner is an explicit, discussed decision, not a silent edit in a catalog UI.

Back to Olga from the first paragraph. With the registry, her story is solved in fifteen minutes: searching for the phone_work element shows that the crm_customer_data golden dataset belongs to a CRM owner, the delivery is checksum-verified, and the marketing mart received data under Principle 6 — with the owner's approval. The edit "bypassing the API" is visible in origin — this is a violation of Principle 1, and it's fixed in the source, not in the copy.

• • •

VIIIAngle for AI Systems: Golden Source as the First Law of Retrieval

Everything said directly affects RAG and LLM pipelines. Retrieval that indexes "something resembling the golden dataset" — an export of a copy of a copy — produces silent hallucinations: the model confidently answers by data that has already diverged from the primary source.

Let's take the same Olga story. A support agent receives the question: "what's the customer's emergency contact phone?" The index has three fragments: from the marketing mart (old number), from the HR export (no number), from the CRM golden dataset (current). Relevance ranking doesn't know that one fragment is "cleaner" — it sorts by textual match with the query. The model will, with a probability depending on phrasing, pick the old number — and answer confidently, without any caveats.

The rule from this is simple and formulated through the same model: index only golden datasets or data with a contractual guarantee of freshness and provenance — and keep golden_source and contract_version in the chunk metadata, so you can answer the question "which version of facts does this answer reference."

Minimum set of fields in each RAG chunk's metadata

golden_source_id, dataset_id (from LoGS), contract_version, classification, refresh_at. Five fields — and the question "what does your answer reference" stops being philosophy.

• • •

IXMaturity Metrics: How to Know Principles Are Being Adopted

A checklist diagnoses a breakdown; managing change requires metrics that don't age with the stack:

1
Share of data with a known owner

What percentage of datasets in LoGS have an assigned owner and a live contact. Target for the first six months — 80%; if not reached, the problem is in the registration process, not the tools.

2
Number of Principle 6 violations per quarter

Transfers of golden datasets without the owner's approval. Each detection is also a provenance question: how did the transfer go unnoticed?

3
Median time to resolve a discrepancy

From detection ("reports show different totals") to fixing it in the golden source. If it grows — provenance isn't working or the owner can't be found.

4
Share of programmatically verified deliveries

Validator, Deequ, dbt tests — versus eyeball verification. This is the only one of the four metrics that requires almost no cultural effort — only automation.

These metrics are deliberately modest. Strengholt doesn't promise the architecture will be built in a quarter; his book is about not building yet another slowly disintegrating centralized monolithic copy, but growing a distributed system in which every copy is responsible for its own existence.

• • •

XPractitioner's Checklist

Diagnostics "you're broken if…" — now with a remedy for each item:

  1. The same customer value differs in two systems, and no one can say which is primary. Assign the fact owner per Principle 5, register the element in LoGS, run a one-off reconciliation via the identifier mapping table.
  2. A mart or report is owned by a team that didn't create the data in it. Split ownership: the mart owns its own new facts, the originals belong to the source. Record this in the contract.
  3. There are data copies in the organization that are distributed further without the owner's knowledge — and the owner learns about it from an analyst's email. Introduce Principle 6: transfer via owner approval; technical implementation — a distribution_approved flag in the contract and a validator check when entering a new context.
  4. Data delivery to the analytical store is checked by eye, not by program: no checksum, no schema contract, no rejection on acceptance. Deploy the validator from this article — it covers the minimum level; for Spark workloads add Deequ, for analytical models — dbt tests.
  5. When the source schema changes, consumers learn about breakage from job crashes — not from a deprecation notice 90 days in advance. Versioning policy: a major version with dual publishing and a deprecation_after date in the channel metadata.
  6. A new fact (aggregate, mart, join) appears without an assigned owner — because "it's just a join." Rule: new fact — new owner. No exceptions for "small" facts: small facts grow into institutions without owners.
  7. There's no golden source registry — ownership lives in wikis, presentations and people's memories. Set up LoGS at minimum as JSON in git; registry integrity checks in CI — the same validator, applied to metadata.

Each item is a direct consequence of one of the seven principles. If you've gone through the checklist with four or more "yes" answers — your data architecture is no longer scaling, no matter how many tools you buy.

• • •

XIWhat's Next

This article is the first of three built on the book. The next one covers the decision tree for three integration architectures (RDS / API / streaming): when to use which channel, what anti-patterns to expect, and how to automate the choice. The third is on metadata as glue: catalog, provenance, self-service.

Overall Review Summary

Strengholt's material is about how data architecture starts not with tools but with agreements about ownership. A golden source is not about technology — it's about every fact having a birthplace and an owner responsible for its changes. The contract is the only part of this model that can be checked by a program, and it's the easiest place to start: a 100-line validator catches content substitution that no schema check catches.

The 2026 extensions — PyDeequ, dbt tests, JSON Schema contracts, RAG chunk metadata — don't overturn the principles, they give them a working interface. The main takeaway: investments in ownership agreements pay off faster than investments in yet another catalog. A catalog without owners is a shop window without merchandise.

Sources: 1. Piethein Strengholt, «Data Management at Scale», O'Reilly Media, 2020 (Russian translation: «Масштабируемые данные», Питер, 2022) — Chapters 2, 3, 6, 7, 9. Principles 1–8, delivery contract, CSVW descriptor, RDS immutability, List of Golden Sources, golden vs. integrated data distinction, master identifier mapping tables. 2. AWS Labs, Deequ — open-source data quality framework. 3. Delta Lake, schema and quality validation — recommended in the book for delivery validation. 4. dbt Labs, model and column tests — the modern declarative-check standard the book couldn't have known about.