| 2026-10-10 |
fix: разовый VACUUM — после разряжения и по состоянию файла
...
incremental_vacuum отдаёт по одной странице за вызов (замерено: 50 вызовов —
50 страниц, 200 КБ), так что распухший файл он не сжимает, а разовый VACUUM
срабатывал при старте ДО первого прохода разряжения — когда удалять ещё
нечего, — и на следующем старте уже не запускался: режим auto_vacuum к тому
моменту включён. Теперь первый проход разряжения делает lifespan, VACUUM идёт
после него, а признак — состояние самого файла: режим не включён либо
свободные страницы занимают больше четверти. Ошибка прохода откатывает
транзакцию и не мешает старту панели; нехватка места под VACUUM — тоже не
ошибка старта, только запись причины в лог.
Замер на синтетике «как прод» (14 суток, интервал 30 с, ~6.4 КБ JSON на точку,
330.7 МБ, auto_vacuum=0): старт удаляет 33 247 строк, схлопывает 4 202 бакета и
сжимает файл 332.9 → 58.5 МБ; повторный старт не делает ничего.
Eugene Sukhodolskiy
committed
7 hours ago
|
fix: битый ts не роняет разряжение и ответ истории; индекс для чистки журнала
...
Одна строка с неразбираемым ts роняла весь проход разряжения (ValueError из
_bucket и _bucket_bounds) и заодно весь ответ истории. Теперь такие строки
пропускаются: бакет с непонятным ключом не трогаем совсем (иначе границы,
склеенные из чужого формата, не совпали бы со строками БД), точку в ответ не
отдаём — поставить её на ось всё равно нечем. ts пишет только ingest, и только
через _iso, так что взяться таким строкам неоткуда — но одна строка не должна
останавливать обслуживание.
И индекс events(ts): чистка журнала по возрасту идёт раз в 15 с, без индекса
каждый прогон сканировал всю таблицу (на 100 тыс. строк ~11 мс, то есть ~64 с
CPU в сутки). План стал SEARCH events USING INDEX idx_events_ts.
Eugene Sukhodolskiy
committed
7 hours ago
|

feat: метрики — дайджест бакета при разряжении (крайности и простои не теряются)
...
Прореживание оставляло одну реальную точку на бакет, поэтому всплеск CPU под
98% две минуты или падение, пришедшееся ровно на удалённую точку, исчезали с
графика навсегда. Теперь вместе с выжившей точкой пишется дайджест интервала
в metrics.agg_json: n/exp (пришло замеров против ожидаемых — n < exp значит
простой), min/max по cpu/ram/swap/load/сеть и по каждому маунту диска.
Пишется только там, где бакет реально схлопывается (n > 1), поэтому сырые
сутки остаются сырыми, а повторный прогон ничего не перезаписывает.
- metrics_retention: _Bucket копит крайности; границы бакета для DELETE
собираются через datetime (корректный перенос через час/сутки), удаление —
по индексу, без списка id в памяти.
- схема + ALTER: metrics.agg_json.
- API /servers/{id}/metrics и MCP server_history отдают точку с полем agg;
прореживание внутри тула сливает дайджесты выкинутых точек, а не
выбрасывает их вместе с крайностями.
Eugene Sukhodolskiy
committed
8 hours ago
|

perf: метрики — тяжёлые JSON в server_state, разряжение истории по возрасту
...
Строка метрик худеет с ~5 КБ JSON до ~0.1 КБ: net_json (сырые счётчики),
docker_json, processes_json и extra нужны только от ПОСЛЕДНЕГО пакета, в
истории они были мёртвым грузом (~89% объёма). Теперь они живут в одной
строке server_state на сервер:
- ingest читает предыдущий пакет из server_state (fallback на последнюю
строку метрик — плавный апгрейд на живом деплое), пишет состояние UPSERT;
- GET /servers/{id} берёт процессы/docker/extra из состояния через
LEFT JOIN — контракт API для фронта не менялся;
- в history остаются числа и disks_json (нужны графикам на всю глубину).
Разряжение (app/metrics_retention.py) по возрасту точки: 0–24 ч — всё,
24 ч–3 сут — 1/мин, 3–7 сут — 1/5 мин, 7–365 сут — 1/час, старше года —
удаление. В бакете остаётся реальная последняя точка (бакеты считаются
срезом строки ISO-8601, без парсинга дат). Петля — при старте и каждые
15 минут, затем PRAGMA incremental_vacuum.
Чтобы удалённое возвращалось файлу: auto_vacuum=INCREMENTAL, а на БД,
созданных до этого — разовый VACUUM при старте панели.
Eugene Sukhodolskiy
committed
8 hours ago
|