0.4.11
23 · Техническая документация

23. Схема базы данных состояния

AstraCode хранит метаданные сессий и аудит-логи в двух SQLite-БД: state_<N>.sqlite (треды, цели, audit) и logs_<N>.sqlite (трассировка). Цифра в имени — версия schema.

Реализация — крейт state/. Миграции — SQL-файлы в state/migrations/.

1. Файлы БД

Файл Версия Назначение
~/.astracode/state_5.sqlite 5 Threads metadata, goals, agent jobs, dynamic tools
~/.astracode/state_5.sqlite-shm Shared memory (WAL вспомогательный)
~/.astracode/state_5.sqlite-wal Write-Ahead Log
~/.astracode/logs_2.sqlite 2 Tracing logs
~/.astracode/logs_2.sqlite-shm
~/.astracode/logs_2.sqlite-wal

Константы — state/src/lib.rs:

rust
pub const STATE_DB_FILENAME: &str = "state";
pub const STATE_DB_VERSION: u32 = 5;
pub const LOGS_DB_FILENAME: &str = "logs";
pub const LOGS_DB_VERSION: u32 = 2;

При повышении мажорной версии создаётся новый файл (state_6.sqlite); старый остаётся на диске нетронутым (для возможности отката).

⚠️ Не удалять -wal/-shm файлы во время работы AstraCode — это сломает БД. Они исчезают сами при корректном shutdown через checkpoint.

2. Настройки SQLite

В state/src/runtime.rs:

Опция Значение Зачем
journal_mode WAL Конкурентные read/write без блокировок
synchronous Normal Не fsync после каждого write, fsync на checkpoint
auto_vacuum Incremental Постепенная очистка освободившихся страниц

Явный pragma в base_sqlite_options() ставит только journal_mode, synchronous, busy_timeout и auto_vacuum. PRAGMA foreign_keys = ON в runtime.rs не выставляется явно (по умолчанию SQLite не форсит foreign keys на соединение), поэтому полагаться на каскадную целостность ссылок здесь не стоит.

WAL+Normal даёт высокую производительность при большом числе сессий, ценой того, что при kill -9 могут потеряться последние ~1 секунды записей.

3. Таблицы state_5.sqlite

threads — основная таблица

Хранит metadata всех сессий. Полное содержимое сессии — в JSONL rollout (см. 21-rollout-format.md); здесь только индекс для быстрого поиска.

Колонка Тип Описание
id TEXT PRIMARY KEY UUID треда (ThreadId)
rollout_path TEXT NOT NULL Путь к .jsonl файлу
created_at INTEGER Unix timestamp в миллисекундах (с миграции 0025)
updated_at INTEGER Unix timestamp ms
source TEXT cli/vscode/external_agent/...
model_provider TEXT id провайдера (openai, anthropic, ...)
cwd TEXT Абсолютная рабочая директория
title TEXT Заголовок (обычно — первое user сообщение, truncated)
sandbox_policy TEXT JSON-сериализованный snapshot политики
approval_mode TEXT JSON-сериализованный approval-режим
tokens_used INTEGER Накопительно по сессии
has_user_event INTEGER (0/1) Есть ли хотя бы одно user message
archived INTEGER (0/1) Заархивирован ли тред
archived_at INTEGER (nullable) Когда заархивирован
git_sha TEXT SHA коммита на момент создания
git_branch TEXT Активная ветка
git_origin_url TEXT URL origin remote

Индексы:

sql
idx_threads_created_at  (created_at DESC, id DESC)
idx_threads_updated_at  (updated_at DESC, id DESC)
idx_threads_archived    (archived)
idx_threads_source      (source)
idx_threads_provider    (model_provider)
idx_threads_cwd         (cwd)               -- миграция 0027

Запросы типа «10 последних сессий в этом репо» используют idx_threads_cwd + idx_threads_updated_at.

Определение — state/migrations/0001_threads.sql.

thread_dynamic_tools — динамические tools на тред

Колонка Тип
thread_id TEXT PRIMARY KEY → threads(id)
namespace TEXT
tools TEXT BLOB (JSON-массив DynamicToolSpec)

Используется для runtime-зарегистрированных tools (от плагинов и пр.) — чтобы при resume сессии restore их definitions.

thread_goals — долгоиграющие цели

С миграции 0029. Соответствует команде /goal (см. 13-goal-and-modes.md).

Колонка Тип Описание
thread_id TEXT PRIMARY KEY → threads(id)
goal_id TEXT UUID цели
objective TEXT Текст цели
status TEXT active / paused / blocked / complete / usage_limited / budget_limited
token_budget INTEGER (nullable) Лимит в токенах
tokens_used INTEGER Накопительно
time_used_seconds INTEGER Накопительно
created_at_ms INTEGER Unix ms
updated_at_ms INTEGER Unix ms

agent_jobs — batch CSV workflows

Tracking batch-вызовов spawn_agents_on_csv (см. 18-tools-catalog.md).

Колонка Тип
job_id TEXT PRIMARY KEY
parent_thread_id TEXT → threads(id)
status TEXT (running/complete/failed/cancelled)
created_at INTEGER
updated_at INTEGER

backfill_state — миграция rollout → DB

Для прогресса фоновой задачи backfill (заполнение threads из старых JSONL-файлов после апгрейда).

Колонка Тип Описание
status TEXT Running / Complete
last_watermark TEXT Точка прогресса в FS
updated_at INTEGER

Использует 15-минутный lease, чтобы две параллельные сессии AstraCode не делали одну и ту же работу.

Другие таблицы

Зависит от миграций (могут добавиться позже): ui_state, approvals_audit, model_catalog_cache, tool_uses_summary. Точный список — grep CREATE TABLE state/migrations/*.sql.

4. Таблицы logs_2.sqlite

logs — append-only трассировка

Логи tracing-крейта (debug / info / warn / error). Отдельная БД — чтобы log-write не блокировал критичные транзакции в state_5.

Колонка Тип Описание
id INTEGER PRIMARY KEY AUTOINCREMENT
ts INTEGER Unix seconds
ts_nanos INTEGER Доп. наносекунды
level TEXT DEBUG/INFO/WARN/ERROR
target TEXT tracing target (модуль)
message TEXT Сообщение
module_path TEXT Полный путь модуля
file TEXT Имя файла
line INTEGER Номер строки
thread_id TEXT (nullable) UUID треда, если применимо
process_uuid TEXT UUID процесса (миграция 0009)

Индексы:

sql
idx_logs_ts                    (ts DESC, ts_nanos DESC, id DESC)
idx_logs_thread_id             (thread_id)              -- миграция 0003
idx_logs_partition_prune       (для очистки)            -- миграция 0011

idx_logs_ts — для типичного «последние 1000 строк лога».

5. Миграции

Используется sqlx::migrate::Migrator. Файлы — state/migrations/0001_*.sql, 0002_*.sql, ... Запускаются последовательно при первом обращении к БД (StateRuntime::init()).

sqlx хранит таблицу _sqlx_migrations с историей применённых миграций.

Ключевые изменения

# Когда Что
0001 начало CREATE TABLE threads
0002 начало CREATE TABLE logs
0003 logs.thread_id + индекс
0009 stage1_outputs + rollout_slug (0009_stage1_outputs_rollout_slug.sql)
0010 logs.process_uuid (0010_logs_process_id.sql)
0011 idx_logs_partition_prune для cleanup
0025 threads.created_at/updated_at → milliseconds (раньше секунды)
0027 idx_threads_cwd
0029 CREATE TABLE thread_goals
0030 новые статусы для goals (usage_limited, budget_limited)

Обратная совместимость

  • Миграции только forward — нет downgrade-скриптов.
  • При downgrade приложения (откат на старую версию) — БД остаётся на новой schema; старый код может не суметь работать с ней (тогда — снести state_<N>.sqlite или откатить вручную).

6. Cleanup и ротация

logs ротация

logs может расти быстро (особенно с RUST_LOG=debug). В state/src/runtime.rs реализован cleanup по партициям:

  • Лимит на партицию (thread_id или process_uuid): 10 MiB.
  • Лимит на партицию: 1000 строк.
  • При превышении — старые записи удаляются автоматически (использует idx_logs_partition_prune).

state cleanup

Автоматического удаления тредов нет. Очистка — вручную:

sql
DELETE FROM threads WHERE updated_at < strftime('%s', 'now', '-90 days') * 1000 AND archived = 1;
VACUUM;

Или просто удалить файл — при следующем старте создастся пустой.

7. Резервное копирование

Что бэкапить:

bash
tar czf astracode-state-backup.tar.gz \
  ~/.astracode/state_5.sqlite \
  ~/.astracode/state_5.sqlite-shm \
  ~/.astracode/state_5.sqlite-wal \
  ~/.astracode/sessions/    # без этого state без полезной нагрузки

Перед backup — корректно завершить AstraCode (Ctrl-C дважды), чтобы WAL был checkpoint'нут в основной файл.

Не бэкапить logs_2.sqlite — это эфемерные данные, переживут потерю без проблем.

8. Чтение БД вручную

bash
# 10 последних сессий в этой директории
sqlite3 ~/.astracode/state_5.sqlite "
  SELECT id, title, datetime(updated_at/1000, 'unixepoch') as updated, tokens_used
  FROM threads
  WHERE cwd = '$(pwd)'
  ORDER BY updated_at DESC
  LIMIT 10;
"

# Активные цели
sqlite3 ~/.astracode/state_5.sqlite "
  SELECT t.title, g.objective, g.status, g.tokens_used, g.token_budget
  FROM thread_goals g
  JOIN threads t ON t.id = g.thread_id
  WHERE g.status IN ('active', 'paused')
  ORDER BY g.updated_at_ms DESC;
"

# Последние ошибки в логах
sqlite3 ~/.astracode/logs_2.sqlite "
  SELECT datetime(ts, 'unixepoch'), level, target, message
  FROM logs
  WHERE level IN ('ERROR', 'WARN')
  ORDER BY ts DESC, ts_nanos DESC
  LIMIT 50;
"

# Активность по дням
sqlite3 ~/.astracode/state_5.sqlite "
  SELECT date(created_at/1000, 'unixepoch') as day, COUNT(*) as threads, SUM(tokens_used) as tokens
  FROM threads
  GROUP BY day
  ORDER BY day DESC
  LIMIT 30;
"

9. Связь с rollout

Каждая строка в threads ссылается через rollout_path на JSONL-файл (см. 21-rollout-format.md). Это две координированные системы:

  • Authoritative source — rollout JSONL: туда пишутся все события сессии в полном объёме.
  • Indexthreads таблица: денормализованный snapshot для быстрого поиска (без чтения JSONL).

При запуске AstraCode проверяет backfill_state — если он Running, запускает фоновую задачу синхронизации (сканирует ~/.astracode/sessions/ и заполняет threads для файлов, которых ещё нет в индексе).

Если threads.rollout_path указывает на отсутствующий файл — сессия не recoverable, но запись в индексе остаётся (можно вручную почистить).

10. Где определены типы

  • Точка входа: state/src/lib.rs.
  • Runtime: state/src/runtime.rs.
  • Миграции: state/migrations/*.sql.
  • Структуры (Threads, ThreadGoals, AgentJobs): state/src/*.rs.

11. Производительность

  • List threads: ~1ms на 10k тредов (индекс по updated_at).
  • Insert thread: ~3-5ms (с fsync на checkpoint).
  • Log write: ~50µs (batch'ится в фоновую транзакцию).
  • Cleanup logs: ~10-50ms при превышении партиций.

Узкое место чаще не БД, а сетевой LLM-запрос — на его фоне SQLite не виден.