На прошлой неделе я удалил свою производственную базу данных. Мой рабочий экземпляр [Neotoma](https://neotoma.io) увеличился с 6174 наблюдений и 3862 объектов до 84 наблюдений и 67 объектов в одной команде. Месяцы контактов, задач, разговоров, заметок об отзывах, транзакций и постоянных правил: прошли.

Я получил его обратно. Окончательная база данных содержит 6296 наблюдений и 3951 объект за пять недель активности. Восстановление заняло около часа. Этот пост о том, как это произошло, почему восстановление стало возможным и что показал этот опыт в создании системы памяти, которой можно действительно доверять.

## Что случилось

Я работал над Neotoma CLI, тестируя рабочий процесс разработки. Я выполнил последовательность команд, которые сбрасывали состояние базы данных и повторно инициализировали ее. Целью было очистить тестовые данные из среды разработки. Целью была производственная база данных.

Ошибка была банальной. Я запустил «neotoma reset», когда «NEOTOMA_ENV» был настроен на производство. Я думал, что нацелен на разработчиков. К тому времени, когда я это заметил, в активной базе данных было 84 свежих наблюдения процесса повторной инициализации и ничего больше.

## Поиск резервных копий

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

| Исходный файл | Наблюдения | Сущности | Последняя активность |
|---|---|---|---|
| `neotoma.prod.db.db` | 6,174 | 3862 | 9 марта |
| `neotoma.prod 2.db` | 4,406 | 3,073 | 10 марта |
| Живая цель (после очистки) | 84 | 67 | 11 марта |
| `neotoma.prod.db.recovered-*` | 4,381 | 3,059 | 3 марта |
| `резервные копии данных/копия данных/` | 4,158 | 2,955 | 2 марта |
| Различные старые копии | с 3100 до 3931 | с 2558 до 2806 | с 17 по 27 февраля |

Резервные копии существовали, потому что я вручную копировал файл базы данных через нерегулярные промежутки времени. Не формальная система резервного копирования, просто случайные команды `cp`, когда я вспоминал или нервничал. Одна из этих копий, neotoma.prod.db.db, хранила почти все до 9 марта. Вторая копия, neotoma.prod 2.db, содержала данные до 10 марта, которых не хватало первой копии.

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

## Как работало слияние

Neotoma имеет встроенную команду merge-db для объединения баз данных SQLite. Процесс:

1. Создайте резервную копию каждого задействованного файла (как исходного, так и целевого) в каталоге с отметкой времени. Никакая попытка восстановления не должна подвергать риску оригиналы.
2. Остановите работающий сервер Neotoma, чтобы предотвратить одновременную запись.
3. Пробный запуск слияния, чтобы увидеть существующие конфликты.
4. Выполните слияние с помощью `--mode Keep-target`, который вставляет строки из источника, которых нет в целевой базе данных, и сохраняет целевую версию любой строки, совместно используемой обеими базами данных.
5. Повторите то же самое для второго источника.
6. Проверьте наблюдения и количество объектов.
7. Перезагрузите сервер.

В результате первичного слияния из крупнейшей резервной копии было получено 6174 наблюдения. Вторичное слияние добавило еще примерно 100 компаний по сравнению с окном 10 марта. Окончательный подсчет: 6296 наблюдений, 3951 объект, активность с 9 февраля по 11 марта.

После перезапуска я проверил объекты через Neotoma MCP, чтобы убедиться, что все доступно. Контакты, задачи, разговоры, записи отзывов: все присутствует и правильно структурировано.

## Почему это восстановление стало возможным

Восстановление сработало из-за трех свойств архитектуры Neotoma.

**Наблюдения — источник истины.** Neotoma не сохраняет объекты, перезаписывая строку при каких-либо изменениях. Every fact enters the system as an immutable observation: "Alice's email is alice@example.com, observed on March 3 from Gmail." Состояние сущности вычисляется на основе полного набора наблюдений. Журнал наблюдения доступен только для добавления.

Это означает, что резервная копия базы данных представляет собой полный снимок всех фактов, которые когда-либо видела система, а не только последнее состояние. Когда я объединял резервную копию с действующей базой данных, я не восстанавливал «последнее известное состояние каждого объекта». Я переигрывал всю историю.

**Снимки объектов являются производными, а не первичными.** После объединения наблюдений Neotoma повторно вычисляет снимки объектов из журнала наблюдений. Снимок для каждой сущности является детерминированным: учитывая одни и те же наблюдения, вы всегда получаете одно и то же состояние сущности. Вот почему команда слияния включает этап пересчета снимка. Как только наблюдения будут на месте, сущности правильно перестроятся.

**Слияние по первичному ключу с обнаружением конфликтов.** Команда `merge-db` просматривает каждую таблицу, вставляет строки, которые существуют в источнике, но не в цели, и обрабатывает конфликты по первичному ключу. В режиме «держать цель» версия цели выигрывает при любом столкновении. Режим пробного запуска позволяет точно просмотреть, что будет вставлено и что будет конфликтовать, прежде чем вы зафиксируете изменения. Я провел пробные прогоны обоих слияний и просмотрел отчеты о конфликтах перед выполнением.

Вместе эти три свойства обеспечивают самовосстановление базы данных, в отличие от традиционных резервных копий на уровне строк. Вам не нужно беспокоиться о том, какая резервная копия содержит «правильную версию» объекта. Вы объединяете наблюдения, пересчитываете, и выпадает правильное состояние.

## Что я узнал

Этот опыт укрепил несколько вещей.

**Неофициальное резервное копирование лучше, чем отсутствие резервного копирования.** Моя привычка время от времени копировать файл базы данных сэкономила месяцы работы. Но периодическое копирование вручную не является системой. Они оставляют пробелы. Если бы я удалил базу данных 7 марта, а не 11 марта, я бы потерял данные с 28 февраля по 7 марта, потому что ни одна копия не закрывала это окно полностью. Сейчас я настраиваю автоматическое ежедневное резервное копирование с помощью Time Machine на своем Mac.

**Ошибка с флагом env — классика.** Любая система, работающая в средах разработки и производства, несет в себе этот риск. Средством смягчения являются запросы на подтверждение деструктивных операций, запросы терминала с цветовой кодировкой или отдельные учетные данные для каждой среды. После этого инцидента я добавил принудительное подтверждение «перезагрузки neotoma» всякий раз, когда он обнаруживает производственную среду. Флаг `-y` игнорируется для prod. Прежде чем что-либо произойдет, вы увидите «Сброс Neotoma (ПРОИЗВОДСТВО)» и предупреждение.

**Архитектура, основанная на событиях, окупается при восстановлении.** Если Neotoma хранит объекты путем перезаписи строк на месте, очистка базы данных будет означать потерю данных без возможности чистого восстановления. Поскольку наблюдения неизменяемы, а состояние объекта является производным, восстановление представляет собой операцию слияния и повторного вычисления. Журнал наблюдений — это основная истина. Все остальное из него можно восстановить.

**Я тестировал инструменты, которые создавал.** Несколько месяцев назад я написал команду merge-db для другого варианта использования: объединения данных от пользователей, работающих с несколькими экземплярами Neotoma. Я никогда не ожидал, что буду использовать его для своих собственных производственных данных. Но поскольку инструмент существовал и позволял разрешать конфликты и пересчитывать снимки, восстановление было скорее механическим, а не напряженным.

## Ваши данные должны пережить ваши ошибки

Этот инцидент выявил бреши, которые Neotoma должна закрыть, чтобы пользователям никогда не приходилось делать то, что я делал вручную.

**Автоматические снимки.** Neotoma должна делать снимки базы данных по расписанию и перед любой деструктивной операцией. Постоянно меняющийся набор копий с отметками времени, хранящийся в течение 30 дней. Если вы по ошибке запустите сброс на проде, снимок перед сбросом будет тут же. Восстановление не должно зависеть от того, не забыли ли вы запустить `cp` на этой неделе.

**Обнаружение аномалий.** Внезапное падение числа наблюдений с тысячи наблюдений до почти нуля не является нормальным. Neotoma может обнаружить эту закономерность и подтвердить ее перед совершением действия. Простая эвристика: «Эта операция удалит более 90% наблюдений, подтверждаете?» полностью предотвратил бы мой вайп.

**Восстановление с помощью агентов.** Поскольку агенты являются основным UX для Neotoma, восстановление также должно осуществляться с помощью агентов. Вы говорите своему агенту: «Моя база данных выглядит неправильно, мне кажется, я потерял данные». Агент проверяет количество наблюдений, находит доступные снимки, сравнивает диапазоны дат и помогает вам выполнить слияние через MCP. Никаких исследований CLI не требуется.

**Удаленная синхронизация.** Локальные резервные копии защищают от случайной перезаписи, но не от сбоя диска. Neotoma должна поддерживать синхронизацию журнала наблюдений с удаленным местом: облачным хранилищем, вторым компьютером или автономным сервером. Поскольку наблюдения доступны только для добавления, модель синхронизации проста. Отправьте новые наблюдения на пульт. Восстановите состояние объекта на обоих концах.

Та же архитектура, которая сделала возможным это восстановление, упрощает создание этих функций. Наблюдения, допускающие только добавление, производное состояние и детерминированный повторный расчет — это не просто свойства восстановления. Они являются основой для резервного копирования, синхронизации и самовосстановления и являются первоклассными гарантиями.

## Числа

| Метрическая | Перед стиранием | После очистки | После выздоровления |
|---|---|---|---|
| Наблюдения | 6,174 | 84 | 6,296 |
| Сущности | 3862 | 67 | 3951 |
| Диапазон дат | с 9 февраля по 9 марта | с 10 по 11 марта | с 9 февраля по 11 марта |

Итоговое количество выше, чем количество до очистки, поскольку объединение объединило наблюдения из всех трех источников: двух файлов резервных копий и сохранившихся данных после очистки. Некоторые наблюдения, существовавшие только в резервной копии от 10 марта или только в действующей базе данных, отсутствовали в исходной самой большой резервной копии.

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

Neotoma — это [с открытым исходным кодом на GitHub](https://github.com/neotoma-app/neotoma). Если вам нужен уровень памяти, в котором ваши данные смогут пережить ваши худшие ошибки, [попробуйте](https://neotoma.io/install).