Skip to content
On this page

Directus Cloud

Everything you need to start building. Provisioned in 90 seconds. Starting at $15/month.

Get Started

Schema Mismatch Detector (draft / на подумать)

Статус: набросок идеи, не имплементировано. Собран по мотивам сессии 6 (2026-04-08), когда на test inner падал sync из-за того, что колонка queue_number была NOT NULL без default, а на test outer — NULLABLE. Outer присылал delta без queue_number → inner process-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_nullable
  • column_default
  • UNIQUE constraint
  • foreign keys (referenced_table, referenced_column, on_delete)
  • record в directus_fields (interface, options, readonly, hidden — хотя бы как warning)

Источники:

  1. information_schema.columns — PostgreSQL side, настоящее состояние DB
  2. directus_fields — Directus side, может отличаться от DB schema
  3. Внутренняя 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) для отладки

Открытые вопросы

  1. Direction — симметричное сравнение или иерархическое (inner — источник истины)? Бизнес-логика очереди говорит, что inner источник правды (queue_number пишется там). Возможно, стоит иметь конфиг: какая сторона для каждой коллекции является primary.

  2. Auto-fix — детектор может предлагать SQL для исправления (ALTER COLUMN ... DROP NOT NULL). Но это опасно: автоматически не применять. Показывать в интерфейсе с кнопкой «Применить».

  3. Работа с directus_fields vs information_schema — какое расхождение считать «настоящим»? Например, если в DB колонка NOT NULL, а в directus_fields записи нет — это расхождение с DB-схемой, но Directus всё равно увидит колонку. Нужен третий источник истины — schema.collections[c].fields на runtime.

  4. Производительность — N коллекций × M полей × 2 стороны. При ~50 коллекциях по ~30 полей выходит ~3000 сравнений. Можно — надо профилировать.

  5. Как получать 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.jsexpandCollectionsWithRelations
  • api/src/permissions/modules/process-payload/process-payload.ts — откуда берётся валидация _submitted/_nnull для payload'а (именно там ломается sync при mismatch)
  • api/src/services/items.ts — pipeline payload: filter hooks → process-payload → insert
  • packages/schema/src/dialects/postgres.ts — как column_default = nextval(...) превращается в field.defaultValue = 'AUTO_INCREMENT'