The databasePart 4 of 6

Даём агенту кнопку «отменить» для ваших данных

AI-агент, который собирает ваше приложение, заодно прогоняет миграции, массовые правки и сидинг — деструктивные операции, которые git отменить не может, потому что git версионирует только код. Поэтому мы дали агенту отмену данных, которой он управляет сам: он делает снимок перед рискованной операцией и откатывается, если операция ломает данные. Разбираем, как это работает на Postgres с копированием при записи, почему восстановление сохраняет ту же строку подключения и почему целый флот таких приложений ничего не стоит, пока простаивает.

Даём агенту кнопку «отменить» для ваших данных

AI-агент, который собирает ваше приложение, не просто пишет код — он прогоняет миграции, наполняет таблицы и выполняет массовые UPDATE. У кода есть отличная отмена: она называется git, и каждая генерация — это коммит. У данных её нет. Агент, который прогнал миграцию, удалившую не тот столбец, или массовый пересчёт цен со сдвигом на единицу в WHERE, сделал нечто, до чего git не дотянется. Для быстрого, автономного агента именно данные — это незащищённая поверхность.

Мы уже дали билд-агенту платформы привязанную к коммиту отмену кода и данных (путешествие во времени для кода и данных). Этот пост — про активную половину: мы вручаем агенту снимок/восстановление, которыми он управляет как инструментом, чтобы он мог защищать себя по ходу выполнения — сделать снимок перед рискованной операцией, откатиться, если операция ломает данные, — и чтобы вы могли простым языком попросить отменить то, что произошло. Приём, который делает это доступным повсюду, — тот же самый, что позволяет этим приложениям простаивать бесплатно.


Снимки — это маркеры, а не копии

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

Adorable работает на собственном self-hosted Neon: хранилище (pageserver) отделено от compute (stateless Postgres), а история — это таймлайн: база, реконструированная из версионированных страниц + WAL вплоть до некоторого LSN. Ветвление таймлайна — это копирование при записи и O(1), независимо от размера базы. Поэтому снимок — это не копия ваших данных; это маркер из двух полей — (timeline_id, lsn) — который не стоит практически ничего, пока вы реально не восстановитесь.

Когда снимок — это строка, а не копия, дефолт можно перевернуть: делать снимок перед каждой рискованной операцией, рефлекторно. Именно это агент теперь и делает.

Агент сам обрамляет свои рискованные операции

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

  • Автоматически, на самой явной границе. apply_migrations делает страховочный снимок перед запуском. Миграцию, которая удаляет столбец или неверно заполняет данные, нельзя отменить правкой кода — только восстановлением данных, — поэтому точка восстановления фиксируется, и агенту не приходится об этом помнить.
  • Осознанно, по суждению. Инструмент snapshot_data позволяет агенту сделать снимок перед чем угодно ещё, что он считает рискованным, — массовой перезаписью, большим сидингом, шагом с перелопачиванием данных. Достаточно дёшево, чтобы «сомневаешься — делай снимок» было правильной привычкой.

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

1 — snapshot_data()

2 — the risky migration / bulk write

3 — verify: data broke?

4 — restore_data() if broken

fork@lsn + repoint, in place

Producer

neon-conn-proxy

Postgres / one timeline

Один защитный механизм держит всё это в безопасности: снимок и восстановление не симметричны. Снимок дёшев и не деструктивен, поэтому агент делает его без колебаний. Восстановление откатывает данные и перезапускает compute, поэтому агент использует его только чтобы отменить собственное повреждение в рамках прогона — откат ваших исторических данных это решение, которое принимаете вы, а не агент за вас (подробнее ниже).

Восстановление на месте: строка подключения никуда не двигается

Чтобы это было пригодно по ходу прогона — и по ходу разговора, — восстановление должно быть стабильным. Если бы оно возвращало новый endpoint базы, каждое восстановление означало бы перенастройку работающего приложения. Поэтому восстановление происходит на месте: форкнуть таймлайн на LSN снимка, затем перенаправить compute приложения на форк — то же имя базы, тот же DATABASE_URL. Приложение не переподключается к новому месту; данные под ним откатываются. Это переиспользует уже существующий на платформе путь «форк-на-LSN + перенаправление, которым владеет прокси» — тот же примитив, на котором держатся деплой и откат.

Отмена, о которой можно попросить

Сначала выходит самозащита агента — это уже работает сегодня. Те же самые снимки задуманы и для вас, и направление дальше именно такое: поскольку восстановление стабильно и охватывает весь проект, оно всплывает там, где вы уже есть, — в разговоре. Скажите «этот импорт всё угробил, откати его назад», и чат-агент предлагает восстановление («Я могу откатить ваши данные к снимку на 14:14 — сделать?»), а вы подтверждаете; он никогда не перенаправляет вашу базу сам. Панель в дашборде перечисляет точки восстановления, и именно там происходит это подтверждение. Разрушительное направление всегда остаётся за человеческим «да».

И оно спит бесплатно

Почему этому место в каждом приложении, а не только в нагруженных: база данных приложения Adorable масштабируется до нуля. Простаивает → compute масштабируется до нуля реплик и не стоит ничего; следующий запрос будит её за секунды (см. масштабирование Postgres до нуля). Снимки — это маркеры, поэтому приложение с 200 снимками и без трафика всё равно остаётся практически бесплатным. Инструмент для сбора RSVP на свадьбу, сезонный магазин, внутренний инструмент, которым пользуются дважды в неделю, — каждый может нести полный набор снимков/отмены и вносить ~$0, пока простаивает. Именно это свойство вообще делает флоты AI-приложений жизнеспособными (app.build, QwikBuild, Atoms гоняют тысячи масштабируемых до нуля баз ровно по этой причине); здесь мы тратим этот запас на страховочную сетку, а не только на экономию на простое.

Одна точка восстановления на всё приложение

Восстановление откатывает базу данных всего проекта — и это правильный масштаб, а не ограничение. В Adorable проект — это одно приложение, а его apps — это микросервисы, которые намеренно делят одну базу и ссылаются на данные друг друга. Откатить данные одного сервиса без остальных сломало бы эти межсервисные ссылки, поэтому консистентная отмена должна покрывать всё приложение разом — по той же причине, по которой вы никогда не стали бы делать point-in-time восстановление половины системы. Более мелкий масштаб — независимая отмена для каждого конечного пользователя мультитенантного приложения, которое вы собрали, — это отдельная форма (ветка на тенанта), и она отложена.

Почему это важно

Отмена данных превращает деструктивную автоматизацию из угрозы в способность. Агент может прогнать страшную миграцию, потому что в основании лежит восстановление одним вызовом инструмента до состояния, которое действительно существовало; а вы можете отменить плохой день данных, просто попросив. Git дал коду это свойство давным-давно. Базе данных всего-то и нужен был тот же примитив — ветка на LSN — и агент, который знает, что к нему стоит потянуться, прежде чем он сделает что-то, о чём может пожалеть.

Создавай на той самой платформе, о которой эти посты.

Опиши своё приложение простыми словами — Adorable напишет код, настроит базу данных и выкатит его в онлайн.

Начать бесплатно