Appearance
Общая база данных
Между платформой и рекордерами стоит одна база 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: truewatch: true включает слушателя уведомлений. Без него изменение камеры подхватывается только при перезапуске.
Кластерный режим требует базы: нода с cluster.enabled: true и без базы откажется стартовать, потому что размещение камер считается по списку камер.