Skip to content

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

v3 0 stars 0 forks 0 watchers 1 branch 0 runs Public
Люди в кадре и перевод под каждой цитатой — как в примерах жанраv3

Odoo-модуль auto_database_backup, сентябрь 2025 — сентябрь 2026.

Cybrosys — индийская компания, партнёр Odoo. В их открытом репозитории CybroAddons лежат сотни модулей. Один из них снимает копию базы по расписанию и кладёт её куда скажут — на диск, в Google Drive, в Dropbox, в S3. Ставится в один клик, и стоит у людей на боевых серверах.

Это история про то, как один человек снаружи нашёл в нём две дыры, и что было дальше.

19 сентября 2025 — модуль рождается.

Sep 1 : [ADD] Initial Commit 'auto_database_backup'

Внутри — поле, которое станет главным героем:

generated_exception = fields.Char(...)

Сюда пишется текст ошибки, если бэкап не удался. По этому полю потом смотрят, жив ли бэкап.

18 июня 2026 — приходит Джанфранко.

Проходит девять месяцев. Gianfranco Niquín, инженер из компании NIQERA, открывает issue #421.

Он не пишет «у меня не работает». Он приносит разбор на две страницы, с двумя багами, замерами с боевого сервера и готовым PR. Заголовок задачи — уже диагноз:

backups silently fail — generated_exception never reset + temp files leak until disk fills

бэкапы падают молча — generated_exception никогда не сбрасывается, плюс временные файлы текут, пока не забьют диск

Первый баг, его словами:

once a backup fails, the configuration record stays flagged in error forever, even after many subsequent successful backups. Any monitoring based on that field is permanently wrong.

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

Второй баг — про мусор, остающийся после убитого процесса:

Once the temp dir is full, every backup then fails with [Errno 28] No space left on device, which masks the original problem.

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

И числа, ради которых стоит читать целиком:

On a production instance (Amazon S3 destination) backups failed silently for ~17 days; ~90 GB of orphan temp files had accumulated in the container's /tmp while the host disk still had plenty of free space.

на боевом сервере (копии уходили в Amazon S3) бэкапы молча не снимались около 17 дней; в /tmp контейнера накопилось порядка 90 ГБ осиротевших временных файлов, при том что на диске хоста места было полно.

Семнадцать дней. Всё это время в интерфейсе не мигало ничего. Копий не было — и это выглядело точно так же, как если бы они были.

Подпись под отчётом: Reported and fixed by NIQERA — он не просто пожаловался, он принёс правку.

15 июля 2026 — отвечает Ризвана.

Через двадцать семь дней приезжает Jul 15: [FIX] Bug Fixed 'auto_database_backup' за авторством Risvana (RisvanaCybrosys).

Три файла, +123/-123 в том самом db_backup_configure.py. Очень похоже на ответ Джанфранко.

В release notes написано, что именно починили:

Fixed the issue while generating Nextcloud backup

Исправлена проблема при создании копии в Nextcloud

Nextcloud. Не то.

20 июля 2026 — Аджмал, тридцать пять файлов.

Ещё через пять дней приходит Jul 20 [IMP] Structural Change, Code Refactoring, New Features, автор Ajmal (AjmalCybro).

Тридцать пять файлов. И где-то внутри появляется строка:

values['generated_exception'] = False

Первый баг Джанфранко закрыт. В том же коммите заводится и тест на него.

В задаче об этом не сообщает никто.

1 сентября 2026 — что осталось.

Issue #421 открыт. Спустя два с половиной месяца. Комментариев ноль.

По текущему файлу в ветке 19.0:

grep -c "orphan\|cleanup\|sweep" → 0

Второй баг — тот, из-за которого натекли девяносто гигабайт, — живёт.

Починилась ровно та половина, из-за которой врал мониторинг. Не починилась та, из-за которой забивается диск.

Про людей, а не про халатность.

Соблазн прочитать это как «в Cybrosys плохо работают» надо погасить сразу.

В репозитории сотни модулей. Ризвана и Аджмал закрывают поток задач по всему репозиторию — и за пять дней сделали и точечную правку, и рефакторинг на тридцать пять файлов. Отчёт от постороннего человека попадает в этот поток наравне со всем остальным.

Ошибка здесь не в людях, а в положении: между тем, кто нашёл, и тем, кто чинит, нет разговора. Джанфранко не знает, что его первый баг закрыт. Аджмал, вероятно, не знает, что закрыл именно его. А второй баг не закрыл никто, потому что оба считают вопрос как-то решённым.

Что это значит для нас

1
Убирать мусор в начале прогона, а не в конце

Сметать брошенные временные файлы первым действием, до всего остального.

Why: Процесс, убитый сигналом, до своей уборки не доходит никогда — менеджеры контекста при SIGKILL не работают. Следующий прогон — единственный, кто может убрать за предыдущим.
Check
  • Уборка стоит ДО снятия, а не после
  • Проверено запуском: состаренный временный файл действительно удаляется
  • Обычный отказ вдобавок убирает за собой сразу
2
Сделать так, чтобы успех гасил прошлую ошибку

Найти в своём коде поля состояния, которые только ставятся и нигде не снимаются.

Why: Состояние, которое умеет только портиться, — это не состояние, а счётчик обид. Любое наблюдение поверх него врёт после первого же сбоя и навсегда.
Check
  • Найдено каждое место, где поле выставляется
  • Есть хотя бы одно место, где оно снимается при успехе
  • Проверено: отказ, затем успех — состояние стало чистым
3
Завести положительный сигнал, а не только тревогу

При УСПЕХЕ стучаться наружу, а наблюдение настроить на ОТСУТСТВИЕ стука.

Why: Семнадцать дней тишины случились не потому, что тревога не сработала, а потому что послать её было некому. Молчание мёртвого сторожа неотличимо от молчания исправного.
Check
  • Есть внешний монитор, который ждёт стук и ругается на его отсутствие
  • Период монитора больше периода бэкапа
  • Канал связи в образе ДЕЙСТВИТЕЛЬНО есть — проверено, а не предположено
4
Проверять снятое, а не факт снятия

Читать архив после создания (для Postgres — pg_restore --list) и требовать разумного числа объектов. Готовое имя — только проверенному файлу.

Why: Успешный дамп пустой базы — самый убедительный из фальшивых бэкапов. Код возврата ноль означает лишь, что инструмент не упал.
Check
  • Архив читается после снятия, а не только проверяется код возврата
  • Есть порог, отсекающий пустой дамп
  • Хотя бы один раз копия РАЗВЁРНУТА в пустую базу и сверена с оригиналом

Есть в этой истории одна деталь, которую трудно не заметить.

Человек потратил вечер, нашёл два бага, замерил ущерб, написал отчёт на две страницы и приложил готовую правку. Одну его находку молча починили внутри рефакторинга из тридцати пяти файлов. Вторую не починили. Ему не ответили ни разу.

Модуль, о котором шла речь, ломался тем, что ничего никому не сообщал.

Пятеро разработчиков ERPNext по очереди чинили одну трещину, и никто из них не ошибался. Пока один не спросил: а почему это вообще работает? Оказалось — по случайному свойству чужой базы данных.

v1Public 0 0
updated Sep 1, 2026

graphile-worker: очередь на Postgres, замеры в комментариях и настройка, не пережившая полутора лет

v2Public 0 0
updated Sep 1, 2026

Как освободить диск от образов, томов и кэша — и не снести при этом то, без чего сервис потом не поднимется. С разбором реального случая.

v1Public 0 0
updated Aug 4, 2026

Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода

v10Public 0 0
updated Sep 1, 2026

Люди и проекты из разборов — по поступкам, датам и ссылкам. Как определитель птиц: не оценивает, а помогает узнать, кого встретил

v5Public 0 0
updated Sep 1, 2026

Карточка вида: чем живёт, как принимает чужаков, кто в нём работает. Наблюдения по коду, переписке и тому, что проект пишет о себе сам

v5Public 0 0
updated Sep 1, 2026