◆ ForgeVis
Skip to content

Общая база данных ​

Между платформой и рекордерами стоит одна база PostgreSQL. Владеет ею бекэнд; рекордеры читают две таблицы и пишут в одну.

Знать, кто чем владеет, — не формальность: от этого зависит порядок запуска, а нарушение этого порядка происходит тихо.

Кто к чему обращается ​

ТаблицаБекэндРекордер
camerasвладеет полностьютолько читает
recordingsвладеет схемойпишет и читает сегменты
всё остальноевладеет полностьюне трогает

Рекордер не выполняет никакого DDL — ни таблиц, ни функций, ни триггеров. Вся его работа с базой — несколько запросов к этим двум таблицам, поэтому ему достаточно прав SELECT на cameras и INSERT/SELECT на recordings.

Порядок важен ​

Сначала миграции, потом рекордеры. Это не рекомендация, а требование — причём нарушается оно молча.

Рекордер, не нашедший таблицу cameras, не падает. Он пишет warning, затем error и продолжает стартовать. В результате получается процесс, который отвечает на health-проверки, ничего не пишет и никогда не перечитает камеры — потому что перечитывание висит на триггере уведомлений, который тоже не установился. Снаружи всё выглядит живым.

WARNING

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

Триггер уведомлений ​

Изменения в cameras доходят до работающего рекордера через LISTEN/NOTIFY по каналу cameras_update. Триггер, который их порождает, — часть схемы и приезжает вместе с миграциями, как и всё остальное здесь. Рекордер его не создаёт и не проверяет: он оказывается на месте вместе со схемой.

Камеру поменяли в базе, а нода не отреагировала

Так выглядит отсутствие триггера — и больше это не выглядит никак. Нода при этом здорова: продолжает вести камеры, которые уже знала, отвечает по API и ни на что не жалуется, потому что с её стороны ничего и не сломалось. Уведомление, которого она ждёт, просто не было отправлено.

sql
SELECT tgname FROM pg_trigger WHERE tgname = 'cameras_change';

Пусто — значит схему накатили без него либо его с тех пор удалили. Накатите миграции заново или команды из dump_schema.py ниже.

Перезапуск ноды ничего не изменит: она этот триггер не создавала и не создаст.

Тот же симптом дают ещё две вещи, и их стоит исключить первыми: database.watch в false на ноде — тогда слушатель выключен совсем, — и нода, которая вообще не смогла подняться с базой: смотрите её лог с момента старта.

База без бекэнда ​

Рекордер может работать с базой, за которой не стоит платформа: одна нода, свой PostgreSQL, камеры правятся в таблице напрямую. Alembic там запускать нечем, поэтому с каждым выпуском публикуется та же схема голым SQL:

bash
python scripts/dump_schema.py                     # вся схема, с пустой базы
python scripts/dump_schema.py --from <ревизия>    # только то, чего не хватает
psql "$DSN" -v ON_ERROR_STOP=1 -f schema.sql

Он рендерится из миграций, а не пишется рядом с ними, поэтому второго описания, которое разъедется с первым, не появляется. В конце проставляется alembic_version — созданную так базу бекэнд подхватит и поведёт дальше, если когда-нибудь появится.

Камеры и кластеры ​

У строки камеры нет колонки кластера. Рекордер в кластерном режиме выбирает все строки с enabled = true, а консенсус решает, какая нода какую камеру берёт.

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

Учётные данные ​

Рекордер подключается со своим DSN, заданным на ноде:

yaml
database:
  enabled: true
  url: postgres://user:password@host:5432/dbname
  watch: true

watch: true включает слушателя уведомлений. Без него изменение камеры подхватывается только при перезапуске.

Кластерный режим требует базы: нода с cluster.enabled: true и без базы откажется стартовать, потому что размещение камер считается по списку камер.

Proprietary software.