Appearance
Schema Mismatch Detector (draft / на подумать)
Статус: набросок идеи, не имплементировано. Собран по мотивам сессии 6 (2026-04-08), когда на test inner падал sync из-за того, что колонка
queue_numberбылаNOT NULL без default, а на test outer —NULLABLE. Outer присылал delta безqueue_number→ innerprocess-payloadтребовал_submitted: true→ массив[FailedValidationError]сundefinedв feedback.
Проблема
Две базы Directus (inner и outer) живут параллельно. Схемы расходятся по множеству причин:
- ручная правка через админку,
- миграции применились только на одной стороне,
- разные ветки уехали на разные серверы,
- restore из разных бэкапов,
- импорт снапшотов.
Расхождения ломают sync в рантайме, но обнаруживаются только когда пользователь создаёт заявку и получает непонятный feedback. Хочется ловить расхождение до того как кто-то наткнётся на реальный payload.
Что сравнивать
Не всю схему целиком — только коллекции, которые участвуют в синхронизации. Список берётся из sync_settings.collections и раскрывается через expandCollectionsWithRelations (см. extensions/.shared/src/utils/synchronization/get-activities.js) — это даст полный транзитивный набор связанных коллекций.
Для каждой коллекции сравнить для каждого поля:
- имя и тип (
data_type) is_nullablecolumn_defaultUNIQUEconstraint- foreign keys (
referenced_table,referenced_column,on_delete) - record в
directus_fields(interface, options, readonly, hidden — хотя бы как warning)
Источники:
information_schema.columns— PostgreSQL side, настоящее состояние DBdirectus_fields— Directus side, может отличаться от DB schema- Внутренняя
schema.collections[c].fields(изgetSchema()) — то, что Directus видит на runtime (может отличаться от обоих выше — см.process-payload.ts, где валидация строится из обоих источников сразу)
Архитектура — варианты
Вариант A — эндпоинт по запросу
GET /schema-compare/:side — возвращает JSON-отчёт о расхождениях с другой стороной. Использует HTTP-вызов к другому порталу для получения его снапшота (через synchronization endpoint или новый /schema-snapshot).
Плюсы: простой, on-demand. Минусы: нужен сетевой доступ между порталами, разработчик должен помнить запустить.
Вариант B — дашборд в админке
App-extension (module / panel) — вкладка «Расхождения схем» с таблицей: коллекция, поле, тип расхождения, local/remote значение. Опирается на эндпоинт из варианта A.
Плюсы: визуально, удобно контент-менеджеру. Минусы: только by request, не проактивно.
Вариант C — уведомление при логине
Hook auth.login у пользователей с ролью «Разработчик» (или флагом в directus_users) — тихо запускает сравнение и если есть расхождения, создаёт Directus-уведомление (directus_notifications) «Схема расходится: N коллекций, M полей».
Плюсы: проактивно, на глазах, без ручного действия. Минусы: при каждом логине — лишний запрос. Нужен кэш с TTL (например, settings.sync_schema_mismatch_cache_ttl = 1h) — перезапускать только если кэш протух.
Вариант D — триггер на schema change
Hook на directus_fields.* и directus_collections.* (items.create/update/delete) — после любого изменения схемы через админку инвалидирует кэш и запускает сравнение в фоне (post-commit queue!). Результат — уведомление.
Плюсы: ловит момент расхождения. Минусы: не ловит расхождения от прямых SQL-миграций (их правильно ловить через bootstrap hook в entrypoint.sh).
Вариант E — CI-check
Job в .gitlab-ci.yml который после применения миграций на test/stage делает сравнение и falит pipeline при расхождениях. Сырой, но заметный.
Плюсы: блокирует deploy плохого состояния. Минусы: нужен сетевой доступ между CI и обоими порталами.
Рекомендация к обсуждению
Скорее всего сочетание C + D:
- C даёт проактивность и удобство (разработчик увидит при логине)
- D даёт реактивность (ловим момент изменения)
- общий кэш с TTL, общий отчёт в админке
- всегда можно руками дёрнуть через эндпоинт (вариант A) для отладки
Открытые вопросы
Direction — симметричное сравнение или иерархическое (inner — источник истины)? Бизнес-логика очереди говорит, что inner источник правды (queue_number пишется там). Возможно, стоит иметь конфиг: какая сторона для каждой коллекции является primary.
Auto-fix — детектор может предлагать SQL для исправления (
ALTER COLUMN ... DROP NOT NULL). Но это опасно: автоматически не применять. Показывать в интерфейсе с кнопкой «Применить».Работа с
directus_fieldsvsinformation_schema— какое расхождение считать «настоящим»? Например, если в DB колонкаNOT NULL, а вdirectus_fieldsзаписи нет — это расхождение с DB-схемой, но Directus всё равно увидит колонку. Нужен третий источник истины —schema.collections[c].fieldsна runtime.Производительность — N коллекций × M полей × 2 стороны. При ~50 коллекциях по ~30 полей выходит ~3000 сравнений. Можно — надо профилировать.
Как получать snapshot с другой стороны — переиспользовать сервис синка или ввести новый
/schema-snapshotэндпоинт? Если новый — не забыть токен + rate limit.
Когда к этому вернуться
- Когда повторится инцидент типа сессии 6 (sync падает из-за schema mismatch),
- Или когда запустится доп. окружение (UAT / pre-prod) — там больше разносов из-за параллельных миграций,
- Или в рамках общего рефакторинга sync v2 (weeek-528).
Связанное
docs/extensions/post-commit-queue.md— паттерн для отложенной работы, пригодится если детектор будет срабатывать внутри action хука наdirectus_fields.*extensions/.shared/src/utils/synchronization/get-activities.js—expandCollectionsWithRelationsapi/src/permissions/modules/process-payload/process-payload.ts— откуда берётся валидация_submitted/_nnullдля payload'а (именно там ломается sync при mismatch)api/src/services/items.ts— pipeline payload: filter hooks → process-payload → insertpackages/schema/src/dialects/postgres.ts— какcolumn_default = nextval(...)превращается вfield.defaultValue = 'AUTO_INCREMENT'