Отчёт ломается последним
Схема данных почти никогда не ломается сама — первой останавливается витрина, отчёт или выборка для модели. К моменту, когда кто-то приносит сломанный дашборд, изменение в источнике давно произошло, проехало половину цепочки, и разбираться придётся против хода данных: от симптома назад, слой за слоем. Это опоздание — не невнимательность, а устройство системы. Транспорт по природе снисходителен к форме данных: переименованную колонку загрузчик не заметит — на её месте поедут NULL, а NULL валиден; новое поле в полуструктурированном JSON молча выбросит селект с явным списком колонок; расширение типа с целого числа на строку проедет через большинство загрузчиков без единой ошибки. Пайплайн построен пропускать разное — поэтому он по определению не сигнализация.
Отсюда рабочее определение: поехавшая схема — это расхождение между тем, как данные устроены, и тем, как их ожидают. Расхождений на самом деле два. Живая схема источника может разойтись с заявленным контрактом — тогда врёт декларация. А свежие данные могут разойтись с вчерашними — тогда источник сменил форму без объявлений. Это разные болезни, и проверяются они разными средствами.
«Первым» в заголовке — не «раньше коллег». Это значит: раньше, чем о дрейфе узнает потребитель отчёта. И есть ещё одно, менее очевидное: незамеченный дрейф обрастает зависимостями. Пока никто не поднял тревогу, кто-то успевает встроиться на новое поведение данных — и чинить потом приходится не одно место, а все, что успели на него опереться.
Про контракты данных — способ перестать чинить отчёты постфактум — мы уже писали: там речь о фиксации ожиданий. Здесь — другая половина той же задачи: как увидеть момент, когда реальность от ожиданий поехала, а не факт поломки на выходе.
Тупик: договорённость вместо детектора
Самый дешёвый ход — процессное правило, и пробуют его первым именно поэтому: «меняешь схему — напиши в канал, заведи тикет, обнови вики». Вводится за один разговор и сразу создаёт ощущение управляемости. Но с дрейфом оно не работает по построению. Оповещение — дверь, а дрейф по определению то, что прошло мимо дверей. Ночной хотфикс, обновление на стороне вендора, изменившийся ответ чужого API — ничто из этого не заглянет в ваш канал, потому что ваш канал не входит в их путь. Правило связывает ровно тех, кто и так соблюдает правила; всё, что обходит процесс, обходит и оповещение. Канал будет полным — и пустым там, где нужно. Обманчивость в том, что он действительно срабатывает — но только на изменениях, прошедших через процесс, то есть на тех, которые и так были бы видны.
Вторая попытка на том же пути — дождаться красной джобы. Как детектор это честно, но опоздавшее: пайплайн краснеет после того, как данные доехали и уже что-то сломали дальше. А большинство дрейфов вообще ничего не красит: NULL — валидные данные, они спокойно доедут до конца и сломаются уже в чужой таблице.
Вывод из тупика один: проверка должна быть технической и автоматической, вопрос лишь в том, где на пути данных её поставить. Таких мест пять, и они не конкуренты, а слои.
Пять проверок — от источника к потребителю
Все пять — один и тот же приём: дифф «как есть» против «как ожидалось», поставленный на разном расстоянии от источника. Обмен честный и одинаковый для всех. Чем ближе к источнику, тем раньше узнаёшь — и тем меньше понимаешь, кому изменение важно. Чем ближе к потребителю, тем точнее знаешь, что именно сломалось, — и тем позже узнаёшь. Выбирать одно из пяти не нужно.
Дверь миграций
Если схема меняется только через миграции, эталон у вас уже есть — журнал миграций. Детектор тривиален: периодический дифф живого каталога против того, что даёт журнал. Расхождение означает ровно одно: кто-то менял схему в обход двери — ручной ALTER, скрипт «на минутку», обновление вендора. Срабатывает раньше всех остальных проверок, потому что смотрит на сам источник, а не на данные от него, и достаётся бесплатно, если дисциплина уже есть. Ограничение видно сразу: работает только там, где дверь действительно единственная, а единственность двери — организационный факт, не технический.
Контракт на границе
Следующее расстояние — реестр схем или явный контракт, который проверяется в момент публикации: производитель меняет схему, граница сразу решает, совместимо ли изменение. Добавить опциональное поле — совместимо; переименовать колонку — нет; сузить тип — нет. Реестр не обязан быть покупным продуктом: это может быть файл с контрактом в репозитории и проверка на сборке. Проверка срабатывает в момент изменения и там же, где оно произошло. Но у контракта есть слепое пятно, из-за которого он не самодостаточен: он сравнивает заявленное с заявленным. Соврала ли декларация о самих данных, контракт не видит.
Фактическая схема из самих данных
А вот это — проверка, которая никому не верит на слово. Берём то, что реально приезжает, и выводим схему из данных: какие типы наблюдаются в каждом поле, какая доля NULL, какие ключи появляются в payload. Дифф сегодняшнего профиля против базового окна — и дрейф виден без всяких деклараций.
Про пороги нужна честность, без неё проверка превращается в генератор шума. Универсальных чисел не бывает, поэтому метод: возьмите окно, в котором вы уверены, что схема не менялась, посмотрите, как в нём дышит каждая метрика, и поставьте тревогу за пределами этого дыхания. Доля NULL в поле, которое исторически не бывает пустым, имеет своё естественное рассеяние; сигнал — выход за него, а не любое отклонение от нуля. Отдельного внимания стоят новые ключи в payload: именно там дрейф прячется лучше всего, потому что ничто не падает. И пересобирайте базовое окно после каждой согласованной миграции — иначе детектор будет вечно спорить с легальными изменениями.
Свойства этого слоя определяют всё остальное. Он единственный не требует ничьего сотрудничества: работает на стороннем API, на вендорской выгрузке, на ответе чужого сервиса — там, где нет ни миграций, ни реестра, ни вообще кого-то, кто вам что-то обещал. И он же ловит класс, мимо которого проходят остальные проверки: расхождение заявленного с фактическим. Декларация говорит «поле обязательное», а в данных оно давно опционально — узнаете только здесь.
Слабых мест два. Профилирование стоит вычислений, поэтому на каждый источник его не поставишь: полностью — то, что меняется часто или больнее бьёт, остальное — сэмплом. И профиль видит форму, но не смысл: поле может опустеть не из-за дрейфа, а потому что наверху остановили кампанию — это ложная тревога, и разбирать её придётся руками. Раньше каталога здесь тоже не выйдет: срабатывание — по факту прибытия данных, а не в момент изменения схемы.
Валидация на входе
Тот же дифф, но с полномочиями: вход пайплайна не замечает расхождение, а решает — пропустить, пометить или завернуть. Из пяти проверок единственная, которая может сделать хуже: строгий режим превращает дрейф в простой приёма, и придётся выбирать, что страшнее — данные с флагом или их отсутствие. Поэтому порядок жёсткий: сначала датчик, потом ворота. Ворота без датчика — это инцидент без объяснения причин.
Тесты на стороне потребителя
Последняя точка — ассерты у того, кто читает: витрина или отчёт проверяет свои колонки на непустоту, тип, наличие строк. Срабатывает позже всех, зато честнее всех: тревога поднимается только на дрейф, который кому-то важен. По сути это слой приоритизации — всё, что выше, орёт на любое изменение, а здесь выясняется, какие изменения несущие. Как единственный детектор он бессмыслен: это снова «чиним отчёт». Как последний слой — превращает шум в сигнал.
Позиция редакции: что строить первым
Мы считаем, что первым нужно строить вывод фактической схемы из данных. Аргумент — из арифметики, а не из вкуса. На источниках, которые вам подчиняются, дрейф в принципе можно запретить дверью миграций. Значит, дрейф, который остаётся ловить, по построению приходит извне — с территорий, на которые у вас нет влияния. А для внешнего дрейфа годится только детектор, работающий без сотрудничества источника: наблюдать за самими данными, а не за обещаниями вокруг них. Дальше по порядку: миграционный дифф на своих источниках; контракт на границе там, где производитель готов играть по правилам; ворота на входе — после того как датчики уже стоят, им нужен источник истины о том, что считать нормой; ассерты — у отчётов, поломка которых стоит дорого.
Наша позиция неверна, если все ваши источники — ваши же и миграция в каждом действительно единственная дверь: одна команда, деплой-гейты, никаких ручных ALTER. Тогда дифф живой схемы с журналом миграций ловит всё раньше и дешевле, а профилирование будет молчать и жечь ресурсы. Если это про вас — стройте дверь миграций и её дифф, а из этого текста вам нужен только раздел про тесты потребителя.
Общий знаменатель простой: дрейф не объявляет себя, его можно только сравнивать. Вопрос не в том, узнаете ли вы, — узнаете. Вопрос — на каком расстоянии от сломанного отчёта.