Память системы и воспроизведение событий
Что хранить, чтобы объяснить вчерашнее действие?
Быстрая запись полезна только тогда, когда понятно, что система может потерять при сбое.
Одного текущего состояния недостаточно. Чтобы понять, почему включился свет, нужны наблюдения, версия модели, действовавшие ограничения, причина команды и её результат. Но хранить каждый звук и каждый отсчёт навсегда тоже не нужно.
Разделим быстрый снимок в памяти, долговечный журнал управления и крупные исследовательские записи. У каждого свой срок хранения и способ восстановления. Повторное проигрывание истории будет отдельным режимом, который не имеет права незаметно посылать живые команды.
outbox, сохранённая очередь ещё не завершённой отправки
В первом Koobique хранятся четыре разных класса данных: конфигурация и ключевые привязки; журнал принятых команд; текущие наблюдения; длительная история. У них разные требования к сохранности. Не нужно делать каждое измерение температуры таким же дорогим, как смену владельца, но нельзя терять принятую команду и продолжать показывать пользователю успех.
SQLite как исходный выбор#
SQLite подходит для одной станции, если архитектура учитывает одного писателя и локальный накопитель. WAL позволяет чтению и записи сосуществовать, но требует управления checkpoint и длительными читателями. Активная база не размещается на SMB/NFS только ради удобной резервной копии. В 2026 году исправлена ошибка WAL-reset; сборка должна содержать исправленную версию или подтверждённый backport. [51]
Это сознательный выбор зрелого SQL-хранилища, а не отказ от Rust. redb остаётся альтернативой для транзакционного key-value слоя, если его модель лучше подходит данным и прошла такие же проверки. Сравнение включает запросы, резервирование, миграции, память и поведение при отключении питания, а не только язык реализации. [53]
Транзакция принятия и outbox#
В одной транзакции сохраняются command_id, источник, неизменяемые параметры, условия исполнения, минимальные основания и outbox. Только после успешного commit выдаётся ACCEPTED. В живой загрузке Core допускает повтор того же тела и ID до исходного срока. Перед первой записью пакета в сокет долговечно фиксируется DeliveryAttempt. После reboot/resume отсутствие такой отметки позволяет назвать запрос неотправленным/EXPIRED; отметка без результата означает возможно исполненную команду и требует UNKNOWN/сверки. Старый outbox не становится новым намерением. Приёмка включает сбои до и после commit отметки, до и после write и между GPIO и ACK.
Если сеть получила пакет, но станция упала до записи результата, при восстановлении проводится сверка. Физический эффект нельзя откатить транзакцией SQLite. Поэтому важны идемпотентность, история результатов на устройстве и состояние UNKNOWN.
Режим синхронизации#
Для критичного журнала требуется режим, соответствующий обещанной сохранности при потере питания. В WAL выбор FULL/NORMAL меняет момент синхронизации и допустимую потерю последних транзакций. Эти настройки задаются осознанно для конкретного соединения/хранилища и проверяются; нельзя просто скопировать «быстрые PRAGMA» из случайного примера. [93]
Телеметрию можно записывать пакетами. Если принято решение допускать потерю нескольких последних секунд истории, оно отображается в требованиях. Критический журнал и история могут жить в разных базах, но тогда нет общей атомарности транзакций между ними: это учитывается в проекте.
Объём данных#
Условные 100 событий в секунду по 200 байт дают 1,728 ГБ в сутки до индексов и служебных расходов. Для кабинета такая частота обычно избыточна, но расчёт показывает опасность бесконтрольного логирования. 10 событий в секунду уменьшают исходный объём в десять раз, однако политика хранения всё равно нужна.
Raw-история сохраняется ограниченный срок, затем строятся агрегаты по минутам/часам. Агрегат содержит минимум, максимум, среднее, число валидных образцов и долю пропусков. Только среднее скрывает короткие, но важные изменения.
Аудио хранится отдельно и по умолчанию не архивируется постоянно. Модели, резервные копии и диагностические пакеты имеют собственные каталоги и квоты. Заполненный диск должен сначала отключить необязательный сбор данных, а не сломать конфигурацию и обновление.
Резервная копия#
Для работающей SQLite применяется согласованный Backup API или эквивалентная процедура, а не копирование одного файла без WAL. Копия включает описание версии, конфигурацию, ключи в защищённом виде и необходимые данные адаптеров. Проверка чтения архива не равна проверке восстановления всей инсталляции. [52]
Ключи восстановления должны быть доступны владельцу отдельно от единственного хаба. Но резервная копия не предназначена для одновременного запуска двух управляющих станций. После переноса меняется controller_fence, а старая копия не получает автоматически действующие полномочия.
Миграции#
Новая версия сначала добавляет совместимые поля, затем переводит данные и только позднее удаляет старые структуры. Необратимая миграция должна быть явно согласована с механизмом отката ОС. Смена формата всей истории ради одного поля чаще всего не оправдана.
Испытание, без которого БД не принята#
На отдельном тестовом накопителе выполнить серию остановок процесса и контролируемых отключений питания в разные моменты записи. Проверить целостность, незавершённые команды, время восстановления и отсутствие повторного опасного действия. Не проводить этот опыт на единственной копии ценных данных.
Текущий снимок, журнал управления и подробная история требуют разных способов записи и хранения. Для первой версии это логическое разделение можно реализовать в одном локальном хранилище.
Три режима данных#
Актуальный снимок нужен для быстрых решений. Он находится в памяти, содержит свежесть и ссылки на исходные записи. После перезапуска восстановленное значение не объявляется свежим без проверки.
Журнал решений и конфигурации сохраняет права, связи объектов, версии, команды и их результаты. Для обещания «надёжно принято» нужна соответствующая транзакционная запись. Нельзя считать ответ успешным до шага, сохранность которого обещана пользователю.
История измерений допускает агрегирование и ограниченные потери согласно профилю. Аудио, видео и большие диагностические потоки имеют отдельную политику и по умолчанию не записываются постоянно.
Выбор хранения для первого экземпляра#
Для конфигурации и журнала исходным кандидатом остаётся SQLite с ограниченным писателем и явной политикой синхронизации. WAL поддерживает параллельное чтение и запись, но не отменяет требования к файловой системе и согласованному резервированию. [51] Альтернативу на Rust, например выбранное встроенное key-value хранилище, сравниваем по реальным транзакциям и восстановлению, а не по языку реализации.
Большие записи можно вынести в последовательные чанки с индексом. MCAP задаёт контейнер для каналов и сообщений с временными метками; он полезен как кандидат формата записи/повтора. [108] Arrow подходит для столбцового обмена пакетами данных в аналитике. [109] Ни тот ни другой не обязателен для каждого маленького KQIP-сообщения.
Для первой комнаты достаточно одного локального хранилища и простого экспортируемого журнала. Добавление отдельного аналитического формата оправдано измеренным объёмом или удобством исследования.
Сколько стоит «сохранять всё»#
Четыре аудиоканала по 16 000 отсчётов/с и 16 бит дают байт/с — около 11,06 ГБ за сутки без служебных расходов. Сто событий в секунду по 200 байт — 1,728 ГБ за сутки. Это расчётные объёмы в десятичных единицах, не показатели компрессии.
Храним краткое кольцевое окно только при необходимости, задаём сроки удаления и резерв под критический журнал. Заполненный диск не должен приводить к бесконечному повтору команды. У пользователя есть отдельные переключатели для хранения аудио, диагностических данных и участия в обучении.
Совместимость KQIP#
Новые логические сущности не требуют ломать транспорт. Предлагаем согласование поддерживаемых дескрипторов наблюдений и результатов. Канал может объявить единицу, шкалу времени, параметры измерения и ограничения; отдельные сообщения ссылаются на него компактно.
Неизвестный тип не превращается в ближайший знакомый. Неизвестное критическое поле отклоняет действие; необязательную диагностику можно пропустить только по явному правилу. Логическая запись и бинарное кодирование версионируются раздельно: расширенный паспорт наблюдения не меняет молча профиль KQIP 0.2; малый вектор 0.1 оставлен историческим примером. Новая редакция кодека требует согласованных тестовых векторов.
Дубли, порядки и перезапуск#
Ключ наблюдения включает канал, эпоху перезапуска и последовательность. Дубликат не создаёт второе физическое событие. При пересборке производных данных версия преобразования добавляет новую сущность, а не изменяет идентичность исходной записи.
После перезапуска ядро восстанавливает журнал принятых операций, узнаёт фактические состояния устройств и только затем возобновляет разрешённые процессы. Два восстановленных сервера не получают одновременно право на один выход: у установки остаётся идентичность и процедура передачи полномочий.
Быстрое состояние, долговечный журнал и большой архив имеют разные требования. Их разделение — логическое; оно не обязывает покупать сервер СУБД или разворачивать микросервисы.
Нет места на диске — нет обещания ACCEPTED#
Бюджет диска включает критическую БД, WAL и checkpoint, экспорт, временное обновление, две версии моделей и резервную копию. Начальный резерв для E0 выбирается как большее из 1 ГиБ и удвоенного максимального текущего служебного файла; это порог остановки тяжёлых задач, не универсальная гарантия. Перед commit всё равно обрабатываются ENOSPC, I/O и fsync-ошибки. Неподтверждённая запись не выдаёт ACCEPTED.
При нехватке места сначала останавливаются raw-запись, обучение, экспорт и OTA. Новые сетевые команды, которым нельзя обеспечить обещанный журнал, отклоняются явно. Локальная кнопка с отдельным питанием остаётся доступной. Вариант временного сетевого управления без долговечности потребовал бы нового явного договора и не включён в E0.
Минимальные основания решения сохраняются в той же критической транзакции: использованные значения и интервалы, статусы, версии, активные ограничения и ссылки на полный raw. Удаление телеметрии тогда не уничтожает смысл команды. Исчезнувшая по retention подробная запись отмечается «истёк срок хранения», а не «повреждение». Записи журнала неизменяемы; версия схемы и upcaster дают новое представление, не переписывая прошлое.
Ресурс накопителя считаем по записи, а не по JSON#
Если ради 200 байт отдельно записать страницу 4 КиБ в WAL, а затем ещё раз при checkpoint, теоретический пример даёт примерно 40-кратное отношение. Это не измеренный коэффициент SQLite на нашей схеме и не полный NAND write amplification. Индексы, batch, checkpoint, файловая система и контроллер меняют результат. Проверяем host writes, объём WAL, реальную частоту commit и доступную телеметрию SSD; сравниваем с ресурсом конкретного накопителя. [51]
Непрерывное аудио по умолчанию не сохраняется. Для диагностики — ограниченный RAM-буфер и явное согласие на эпизод. Транскрипты и производные признаки имеют отдельные сроки хранения. Лицензия весов, голосов TTS и runtime записывается в манифест; язык реализации сам по себе не решает эти ограничения.