Обслуживание
Резервные копии, очистка базы и сессий, диагностика, массовые действия и производительность.
Содержание раздела
- Резервные копии
- Сводка: в порядке ли копирование
- Куда сохраняется копия
- Шифрование копий паролем
- Загрузка копии файлом
- Журнал запусков
- Подтверждения: что именно произойдёт
- Восстановление: по шагам на сервере
- Журнал действий
- На телефоне
- Знакомство с разделом
- Кто видит раздел
- Очистка БД и сессий
- Состояние БД сейчас
- Автоочистка БД
- История запусков
- Аудит зомби-сессий на NAS
- Выбор NAS для аудита
- Решение проблем
- Диагностика
- Массовые действия
- Подраздел «Разблокировать»
- Подраздел «Чистка БД»
- Подраздел «Закрытие периода»
- Производительность и оптимизация
- Что было сделано
- Что это дало
- Защитные механизмы
- Документы (для разработчиков)
Резервные копии
Раздел /admin/settings/backup/ — единственное место, где видно
состояние резервного копирования всей установки. Сюда приходят двое: администратор,
который настраивает политики и следит, что копии идут, и тот, кто разбирает аварию
и ищет свежую копию. Второй приходит в худший день года, поэтому раздел устроен так,
чтобы отвечать на три вопроса сразу: есть ли копия, где она лежит и можно
ли из неё восстановиться.
Что показывает раздел, где хранить копию и зачем шифровать её паролем, как снять копию вручную и загрузить её файлом, как устроено восстановление по шагам на сервере и почему тишина системы опаснее ошибки.
Баннер сверху показывает, сколько места занимают сами копии, а не занятость раздела сервера, — одной строкой: объём копий на сервере, свободное место на диске (значок диска) и объём копий в облаке с числом файлов (значок облака). Подпись к значку видна при наведении и доступна экранному диктору. Занятость всего раздела на экран не выводится — по ней нельзя решать, сколько копий хранить.
Объём в облаке считается по самому хранилищу, а не по записям о копиях (кешируется на 10 минут). Правило «сколько копий хранить» удаляет старые файлы только на сервере — в облаке копии остаются, поэтому по записям объём выходил бы заниженным. Если облако не настроено, этой части строки нет.
Сводка: в порядке ли копирование
Под баннером — одна строка, которая отвечает на главный вопрос раздела, чтобы не сверять даты по карточкам:
- Состояние — «Копии идут», «Есть что проверить» (за неделю ни одна копия не ушла в облако) или «Нужно внимание» (политика просрочена или последний запуск упал — названия политик тут же).
- Последняя копия — когда снята, сколько прошло, есть ли она в облаке.
- Следующая копия — когда и по какой политике.
- За 7 дней — сколько запусков прошло успешно и сколько с ошибкой, по журналу запусков, а не по оставшимся файлам.
Следующая копия считается теми же правилами, что и сам планировщик, — страница прогоняет его такты вперёд, а не угадывает по формуле. Поэтому время в сводке совпадает с тем, когда копия действительно снимется:
flowchart TD T["Такт планировщика
раз в час, в :07"] --> S{"Политика по расписанию
и включена?"} S -->|"нет"| X["Не снимаем"] S -->|"да"| E{"Прошёл период
с последней копии?"} E -->|"нет"| W["Ждём следующего такта"] E -->|"да"| D{"Просрочено вдвое?"} D -->|"да"| NOW["Снимаем сразу"] D -->|"нет"| H{"Идёт окно:
час политики + 3 ч?"} H -->|"да"| RUN["Снимаем"] H -->|"нет"| W
Куда сохраняется копия
У каждой политики есть выбор: Только на этом сервере, Только в облако или Локально и в облако. Копия, лежащая на том же сервере, что и биллинг, исчезает вместе с ним: сервер потерян — потеряны и копии. Поэтому для рабочих политик выбирается «Локально и в облако»: файл остаётся на диске для быстрого восстановления и уходит в облако как страховка.
flowchart LR RUN["Копия по расписанию
или вручную"] --> FILE["Файл на диске сервера"] FILE --> Q{"Куда сохранять"} Q -->|"Только на сервере"| ONLY["Только диск сервера"] Q -->|"Локально и в облако"| BOTH["Диск + облако"] Q -->|"Только в облако"| CLOUD["Облако,
локальный файл убирается"] BOTH --> SAFE["Сервер потерян —
копия остаётся"] CLOUD --> SAFE ONLY --> RISK["Сервер потерян —
копий нет"]
Ссылка на облачный объект хранится в записи копии, поэтому скачать её можно и тогда, когда самого сервера уже нет. Занятое место — и в облаке, и на диске клиента — уходит на сервер лицензий: в карточке инсталляции видно, сколько у клиента копий и есть ли они вообще.
Шифрование копий паролем
Копия — это вся база клиентов, пароли интеграций из .env и код. Пока файл лежит
на сервере, его защищает доступ к серверу; как только файл уходит в облако или скачивается на
компьютер, защищает только шифрование. В окне политики есть раздел Шифрование:
включите переключатель и задайте пароль — каждая новая копия политики будет зашифрована
AES-256 до отправки в облако.
- Пароль — не короче 10 символов, латиница, цифры и знаки. В день аварии его вводят в командной строке чужого компьютера, а консоль Windows отдаёт кириллицу в другой кодировке — копия «не открывается» при верном пароле.
- Пароль не показывается повторно. У заданного пароля видна только дата, поля появляются по кнопке «Сменить пароль». Уже снятые копии остаются зашифрованы прежним паролем.
- В базе хранится не пароль, а токен, запечатанный ключом сервера
(
SECRET_KEY): плановой копии пароль нужен без человека. Файл копии этого ключа не знает — вне сервера его открывает только пароль. Поэтому пароль сохраняют в менеджере паролей: если сервер потерян, пароль в его базе потерян вместе с ним. - Если зашифровать не удалось (например, после смены
SECRET_KEYтокен не читается), копия остаётся на сервере незашифрованной, в облако не отправляется, а в чат тревог приходит оповещение.
flowchart LR
A["Копия снята"] --> B{"У политики
есть пароль?"}
B -->|"нет"| C["Файл как есть"]
B -->|"да"| D["AES-256,
файл .enc"]
D -->|"сбой"| F["Остаётся на сервере,
в облако не уходит,
оповещение"]
D -->|"готово"| E["Исходник
удаляется"]
C --> G["Облако —
по политике"]
E --> G
Зашифрованная копия помечена замком — в журнале перед именем файла; у политики на карточке стоит отметка «Шифрование» с датой, когда задан пароль.
Расшифровать копию можно без биллинга: формат совпадает со стандартным
openssl enc, утилита есть в Linux, macOS и в Git Bash на Windows. openssl
спросит пароль:
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -md sha256 -in копия.dump.enc -out копия.dump
На самом сервере биллинга openssl не установлен — там команду выполняют в контейнере celery (см. восстановление).
Загрузка копии файлом
Кнопка «Загрузить копию» над журналом принимает файл с компьютера —
например, копию, скачанную раньше, или снятую на другом сервере. Подходят дамп базы
.dump и архивы .tar.gz, .tgz, в том числе зашифрованные
(.enc), до 10 ГБ. Файл можно перетащить прямо в окно.
- Файл идёт частями по 8 МБ. Запрос к панели ограничен по размеру и времени, поэтому большой файл отправляется кусками, а сервер принимает кусок только с того места, где закончился предыдущий. Оборвалась связь — загрузка продолжается с последнего принятого байта, повтор ничего не портит.
- Окно можно закрыть — загрузка продолжится, пока открыта страница; ход виден рядом с заголовком журнала.
- Сервер проверяет, что это копия, — по сигнатуре дампа или архива, а не по имени — и сам определяет тип: дамп базы, Docker-пакет, файлы или конфиги.
- Пароль зашифрованной копии — по желанию. С паролем сервер сразу проверяет, подходит ли он, и заглядывает внутрь архива; пароль нигде не сохраняется. Неверный пароль не требует загружать файл заново: исправьте его и нажмите «Загрузить» ещё раз.
- Одна и та же копия дважды не загрузится — сервер сверяет контрольную сумму SHA-256 с журналом.
- Загруженная копия помечается «Загружена файлом» с именем сотрудника; правило «сколько копий хранить» её не удаляет, в сводке «Последняя копия» она не участвует. Брошенные загрузки убираются через сутки.
sequenceDiagram
participant B as Браузер
participant S as Сервер
B->>S: начать: имя и размер
S-->>B: номер загрузки
loop части по 8 МБ
B->>S: часть «с байта N»
S-->>B: принято байт
end
B->>S: завершить, пароль по желанию
S-->>B: тип, шифрование, запись в журнале
Журнал запусков
Записи о копиях живут ровно столько, сколько живут файлы: старые удаляются по правилу «хранить N копий». Из-за этого неудачный запуск не оставлял следа — на экране был зелёный список политик, а копий не снималось месяц. Теперь каждый запуск пишется отдельно: успех или ошибка, длительность, размер, ушла ли копия в облако. Журнал переживает и удаление файлов, и удаление самой политики.
Оповещения приходят в двух случаях: копия упала (с текстом ошибки) и копия не снимается — прошло больше двух периодов расписания. Второе важнее: молчание системы выглядит как «всё хорошо».
Типы копий:
- db — только база данных (pg_dump в формате custom).
- files — медиа-файлы и шаблоны печати (tar.gz).
- both — БД + файлы.
- docker — самодостаточный пакет: БД, код, Dockerfile, docker-compose,
.env, медиа и скрипт
restore.sh. Достаточно для развёртывания на новом сервере. - configs («Конфиги сервера») — лёгкий снимок только
серверной конфигурации:
.env,docker-compose*.yml,Dockerfile,gunicorn.conf.py,requirements.txtи весь каталогdocker/(nginx / freeradius / postgres). Без БД и кода — обычно несколько килобайт. Удобно для быстрого сравнения/отката настроек сервера.
Настройка политики — одно окно и для новой политики, и для правки. Слева четыре раздела: Что копируем (название и состав копии), Хранение (куда сохранять и сколько копий держать), Шифрование (пароль на файл копии, см. выше) и Расписание (как часто, во сколько, включена ли политика). Списки «Что резервируем», «Куда сохранять» и «Как часто» — с иконкой и цветом у каждого пункта.
Час запуска у каждой политики свой. Копия снимается в течение трёх
часов после выбранного времени. Пустое значение — «как в общих настройках»
(BACKUP_START_HOUR, по умолчанию 3:00), так политики, созданные раньше, ведут
себя как прежде. Если сервер стоял и копия просрочена вдвое, она снимается сразу, не
дожидаясь окна. У политики «вручную» поле часа неактивно.
Действия с политикой. На карточке на виду только «Запустить», а «Настройки» и «Удалить» — в меню рядом: опасное действие не стоит вплотную к частому. На телефоне то же меню открывается нижней шторкой.
Фильтры журнала — политика, тип копии и статус — выпадающие списки с иконкой и цветом у каждого пункта: база данных, файлы, Docker-образ (фирменный знак Docker), «выполнен» и «ошибка» различаются с одного взгляда. Если в журнале есть загруженные копии, в фильтре политики появляется пункт «Загруженные файлом». Выбранный фильтр подсвечивается зелёной рамкой, «Сбросить» снимает все фильтры сразу.
Архив. Разовые конфигурации — «перестраховка перед релизом», «копия перед миграцией» — остаются в списке навсегда и топят рабочие политики. Такие уводятся в архив: они не исчезают и не мешают, а открываются кнопкой «Архив» в шапке. Планировщик архивные политики не запускает.
Журнал копий — файл, политика, тип, дата, размер и статус. У типа и политики — те же иконки и цвета, что в фильтрах; копия, загруженная файлом, помечена «Загружена файлом», зашифрованная — замком. Скачать копию — щелчок по имени файла; Восстановить и Удалить — в меню строки. Пока копия снимается, у политики виден индикатор, а страница опрашивает статус каждые 5 секунд.
Защита от «зависших» копий: если Celery-воркер падает или
перезапускается во время резервного копирования (например, при переезде сервера), запись
могла навсегда остаться в статусе running — на странице крутился вечный
спиннер и шёл бесконечный опрос статуса. Теперь функция _reap_stale_running()
автоматически помечает любую запись в running старше 30 минут
как «Ошибка» с понятным сообщением. Срабатывает при открытии страницы и при каждом
опросе статуса (раз в 3 секунды), так что зомби-запись сбрасывается сама. Дополнительно
run_backup теперь явно завершается ошибкой при неизвестном типе бекапа или
если файл не был создан.
Подтверждения: что именно произойдёт
Все действия раздела спрашивают подтверждение единым компонентом и перечисляют последствия, а не ограничиваются вопросом «вы уверены?».
Восстановление: по шагам на сервере
Раньше окно «Восстановить» предлагало выбрать, что разворачивать, и кнопку запуска, но задачи, которая выполняла бы восстановление, в системе не было — нажатие заканчивалось ошибкой. Замена рабочей базы требует остановить приложение: RADIUS, воркеры и боты держат соединения с базой. Поэтому из панели её не запускают, а окно показывает готовые шаги для сервера — с подставленным именем файла и кнопкой копирования у каждой команды.
Для копии базы данных — от имени root:
- Перейти в каталог копий:
cd "$(docker volume inspect smit-billing_backup_data -f '{{.Mountpoint}}')" - Если копия зашифрована — расшифровать в контейнере celery, openssl спросит пароль:
docker exec -it smit-billing-celery-1 openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -md sha256 -in '/var/backups/carbon/копия.dump.enc' -out '/var/backups/carbon/копия.dump' - Снять копию текущей базы на случай отката:
docker exec smit-billing-db-1 sh -c 'pg_dump -U "$POSTGRES_USER" -Fc "$POSTGRES_DB"' > before_restore_$(date +%Y%m%d_%H%M).dump - Остановить приложение — база продолжает работать:
docker compose -f /opt/smit-billing/app/docker-compose.prod.yml --project-name smit-billing stop web celery celery-accounting celery-coa celery-beat freeradius - Развернуть копию поверх рабочей базы:
docker cp 'копия.dump' smit-billing-db-1:/tmp/restore.dump && docker exec smit-billing-db-1 sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner --no-acl /tmp/restore.dump' - Запустить приложение (те же сервисы, команда
start) и убрать временный файл:docker exec smit-billing-db-1 rm /tmp/restore.dump
flowchart TD
A["Копия в журнале"] --> B{"Зашифрована?"}
B -->|"да"| C["Расшифровать
в контейнере celery"]
B -->|"нет"| D["Снять копию
текущей базы"]
C --> D
D --> E["Остановить
приложение"]
E --> F["pg_restore поверх
рабочей базы"]
F --> G["Запустить
приложение"]
Шаги проверены на сервере. Зашифрованная копия, загруженная через окно, расшифрована в контейнере celery байт в байт и развёрнута этими же командами во временную базу — все 600 000 строк на месте. Рабочая база при проверке не затрагивалась, остановка и запуск приложения не выполнялись.
Docker-пакет разворачивается скриптом restore.sh из архива:
распаковать и запустить — скрипт проверит пакет и спросит подтверждение. Архив
файлов и конфиги распаковывают во временный каталог и переносят нужные файлы
вручную.
Журнал действий
В аудит попадает каждое действие: создание политики (с расписанием и числом копий), изменение (с перечнем изменённых полей, в том числе «шифрование включено», «выключено» или «пароль изменён» — сам пароль в журнал не пишется), удаление, ручной запуск, удаление копии, загрузка копии файлом (размер, тип, зашифрована ли и проверен ли пароль) и скачивание копии с отметкой, зашифрована ли она: это вынос базы клиентов за пределы сервера.
flowchart TD A["Действие в разделе"] --> B["Подтверждение
с перечнем последствий"] B --> C["Выполнение"] C --> D["Запись в аудит:
кто, что, когда"] C --> E["Журнал запусков
для копий"] C --> F["Оповещение
при ошибке или тишине"]
На телефоне
Сводка складывается в столбик, политики становятся карточками с меню действий, журнал копий — карточками с подписями полей: горизонтальной прокрутки нет, ничего не прячется. Фильтры журнала — поиск, политика, тип и статус — раньше занимали четыре полные строки над списком; теперь это одна строка с поиском и кнопкой «Фильтры», которая открывает нижнюю шторку.
Знакомство с разделом
При первом заходе раздел показывает короткое знакомство: обучающий ролик и четыре шага — зачем хранить копию в облаке, зачем шифровать её паролем, как загрузить копию файлом и как устроено восстановление по шагам на сервере. Обновлённое знакомство показывается администраторам один раз; повторно открыть его можно кнопкой «Знакомство» в шапке раздела.
Кто видит раздел
Раздел доступен только группе «Администратор» и суперпользователям. В
копии вся база клиентов и секреты сервера, поэтому правило жёсткое: его не расширяет матрица
«Разделы админки» и не снимает выключатель SECTION_RBAC_ENABLED. Сотрудник другой
роли не видит пункт меню и получает отказ по прямой ссылке; скачивание, загрузка и
сохранение политики отвечают отказом и в обход страницы — проверка стоит и в общем фильтре
доступа, и в каждом действии раздела.
Организации. Копия охватывает всю установку целиком: данные всех организаций лежат в одной базе, и разделить их на отдельные копии нельзя. Поэтому раздел общий.
Очистка БД и сессий
URL: /admin/settings/db_cleanup/ (раздел «Обслуживание», только root-доступ).
Раздел централизованного автоматического обслуживания БД и борьбы с «зомби»-сессиями RADIUS. Состоит из 5 блоков (сверху вниз): Состояние БД, Автоочистка, История запусков, Аудит зомби-сессий, Выбор NAS для аудита.
Состояние БД сейчас
Верхний блок — реальный счётчик записей в 4 ключевых таблицах (обновляется AJAX):
MSG_STACK— журнал отправленных сообщений (Email/SMS/Push/Telegram). На рабочий сервер ~100K записей при ежедневной нагрузке 500–2000.AUDIT_OPERATIONS— аудит действий (создание/правка/удаление). На рабочий сервер ~600K записей за всё время.RADIUS_SESSIONS— история RADIUS-сессий (включая закрытые). На рабочий сервер ~5–7M записей при 1000+ онлайн-клиентах.NAS_EVENTS— события NAS (UP/DOWN, аларм). Маленькая таблица, чистится редко.
Каждая ячейка показывает размер (Кб/Мб/Гб) + количество записей. Если рядом с цифрой растёт ⚠ — таблица превышает порог опасного роста и требует внимания.
Автоочистка БД
Параметры retention (период хранения, после которого записи удаляются):
| Параметр | SystemSettings ключ | Default | Что удаляется |
|---|---|---|---|
| Сообщения | DB_CLEANUP_MSG_DAYS | 30 дней | MsgStack где send_date < now − N |
| Аудит | DB_CLEANUP_AUDIT_DAYS | 365 дней | AuditOperations где op_time < now − N |
| RADIUS-сессии | DB_CLEANUP_SESSIONS_DAYS | 90 дней | RadiusSessions где END_TIME IS NOT NULL AND END_TIME < now − N (только закрытые!) |
| NAS-события | DB_CLEANUP_EVENTS_DAYS | 30 дней | NasStatusLog где event_time < now − N |
| Размер батча | DB_CLEANUP_BATCH_SIZE | 5000 | Сколько строк удалять за один DELETE (чтобы не лочить таблицу надолго) |
| Включена | DB_CLEANUP_ENABLED | 1 | Тогл всей автоочистки |
Расписание: Celery beat-задача db-cleanup-daily запускается раз в сутки по интервалу (86400 с от старта Celery beat) — точное время суток зависит от момента последнего запуска beat, к фиксированному часу не привязано. Для теста есть кнопка «Запустить чистку сейчас».
Что делает задача (billing/tasks/db_cleanup.py::run_db_cleanup):
- Создаёт запись в
DbCleanupRunсstatus='running'. - Для каждой из 4 таблиц считает порог
cutoff = now - timedelta(days=N). - В цикле:
DELETE ... LIMIT batch_sizeпока есть записи. Между батчами 100мс паузы — снижает нагрузку. - Записывает в результат:
msg_deleted,audit_deleted,sessions_deleted,events_deleted,execution_time. - Обновляет
DbCleanupRun.status='ok'+finished_at. При ошибке —status='failed'+error. - Если за прогон удалено > 100K записей — отправляет Telegram-уведомление.
Кнопки управления:
- «Применить» — сохранить изменённые retention-параметры в SystemSettings (без перезапуска чистки).
- «Запустить чистку сейчас» — синхронный запуск task'а через Celery
.delay(). UI показывает spinner, через ~30 сек обновляет «Историю запусков». - Тогл «Включено» — глобально отключить автоочистку (полезно при миграциях/бэкапах).
История запусков
Таблица последних 50 запусков из DbCleanupRun:
- Время старта и длительность в секундах.
- Статус: 🟢 ok / 🔴 failed / 🟡 running.
- Удалено: разбивка по 4 таблицам (Msg / Audit / RadSes / Events).
- Запустил:
scheduledдля beat-task или username при ручном запуске. - Ошибка: tooltip с traceback при
status='failed'.
Аудит зомби-сессий на NAS
Зомби-сессия — это запись в RADIUS_SESSIONS с END_TIME IS NULL (то есть «сессия активна»), но при этом NAS не подтверждает её существование. Возникает когда:
- NAS перезагрузился / упал и не прислал Accounting-Stop для последних сессий.
- Сетевой обрыв между NAS и FreeRADIUS — Accounting-Stop потерян.
- Bug в прошивке NAS (типичный пример — MikroTik с переполнением счётчика, см. разбор зомби-сессий).
Зомби-сессии искажают виджет «Онлайн сейчас» на дашборде (показывает большее число, чем реально), мешают корректному выбору IP из пула при переавторизации, и забивают `RADIUS_SESSIONS` лишними «открытыми» строками.
Beat-задача session-audit-hourly (billing/tasks/session_audit.py):
- Запускается раз в час (default; настраивается через
SESSION_AUDIT_INTERVAL_MINUTES). - Берёт ровно 4 NAS (default
SESSION_AUDIT_NAS_PER_RUN=4) — те, у которых давно не было аудита (sort bylast_audit_at ASC). Так за сутки покрывается ~96 NAS — больше, чем у любого реального оператора. - Для каждого NAS берёт батч из 50 активных сессий (default
SESSION_AUDIT_BATCH_SIZE=50). Это ~2 минуты времени NAS — не нагружает его. - Для каждой сессии:
radclientзапрос Status-Server или Accounting Interim-Update к NAS — если NAS не подтверждает наличие session-id → метим как «подозрительную». - Через 5 минут идёт повторная проверка тех же сессий. Если NAS ещё раз не подтверждает — сессия считается зомби, ставится
END_TIME=now(),END_REASON='zombie-cleanup'. - Counter подтверждённых zombie добавляется в
SessionAuditRun.zombie_killed. - Если
zombie_killed > 0— отправляется Telegram-алерт с разбивкой по NAS.
Параметры аудита:
| Параметр | SystemSettings ключ | Default |
|---|---|---|
| Интервал между запусками | SESSION_AUDIT_INTERVAL_MINUTES | 60 минут |
| NAS за один запуск | SESSION_AUDIT_NAS_PER_RUN | 4 |
| Сессий за один батч | SESSION_AUDIT_BATCH_SIZE | 50 |
| Задержка повторной проверки | SESSION_AUDIT_RECHECK_DELAY | 300 сек (5 мин) |
| Telegram-алерт | SESSION_AUDIT_TG_ALERT | 1 |
| Включён | SESSION_AUDIT_ENABLED | 1 |
История аудитов ниже — таблица из SessionAuditRun: время, NAS, проверено сессий, найдено зомби, длительность, статус.
Выбор NAS для аудита
Таблица всех NAS с enabled=True и колонками:
- Имя + IP NAS.
- Чекбокс «Включён в аудит» — индивидуально для каждого NAS (тогл сохраняется в
Nas.session_audit_enabled). Удобно отключить аудит для проблемного NAS на время разбора. - Последний аудит — дата/время последнего
SessionAuditRunдля этого NAS. - Найдено зомби — счётчик из последнего аудита.
- Активных сессий сейчас — текущее количество.
- Кнопка «Запустить» — мгновенный аудит конкретного NAS (вне очереди), полезно для отладки.
На рабочий сервер сейчас в активном аудите 4 NAS: TOY, JOY, FAG, POT. Зомби-сессии редки (1–3 в неделю) — после фиксов (zombie-LOGGED + bigint миграция) это в основном NAS-перезагрузки.
Решение проблем
- Чистка не отрабатывает
- Проверь Celery beat:
docker logs smit-billing-celery-beat-1 | grep db-cleanup. Должна быть запись «Scheduler: Sending due task db-cleanup-daily». Если нет — beat не запущен или не видит задачу (импортconfig/celery.py). - «MSG_STACK слишком большой» — 5М+ записей
- Уменьши
DB_CLEANUP_MSG_DAYSдо 14 или 7 → жми «Запустить чистку сейчас». При первом проходе может удалить миллионы строк (займёт 10–20 минут). Дальше beat-задача держит размер в норме. - RadiusSessions растёт быстрее, чем чистится
- В норме: 1 сессия = 1 строка с
END_TIME IS NOT NULLчерез 5–10 минут. Если строк сEND_TIME IS NULLмного — есть зомби, запусти аудит. Если все закрытые, но всё равно много — увеличьDB_CLEANUP_BATCH_SIZEдо 20000. - Telegram-алерт «zombie cleanup: 50+ sessions» каждый час
- Один из NAS постоянно теряет Accounting-Stop. Проверь логи NAS, прошивку, сетевые потери до FreeRADIUS. Временно отключи аудит для этого NAS (чекбокс «Включён в аудит» = False) пока разбираешься.
- Вернуть удалённые данные
- Если настроено регулярное резервное копирование базы (раздел «Резервные копии») — восстановление таблицы:
pg_restore -t msg_stack <dump-file>.dumpв отдельную БД, потомINSERTнужных строк.
Диагностика
Набор готовых команд для диагностики проблем с конкретным клиентом или NAS. Запускаются прямо из веб-интерфейса, результат показывается в модалке-терминале.
Примеры команд: пинг NAS, проверка живости клиента, тест авторизации radtest,
проверка SSH-доступа к NAS.
Массовые действия
Подменю с тремя инструментами обслуживания, объединёнными по смыслу «одно действие сразу для большой группы записей».
Подраздел «Разблокировать»
Массовая разблокировка клиентов. Открывается, если, например, партнёр-сборщик заплатил оптом за весь дом, и нужно одной кнопкой снять блокировку «долг» с десятков клиентов. Можно задать тип блокировки (b_negbal — долг, b_admin — админская) и фильтр клиентов (папка, тариф, дата блокировки).
Подраздел «Чистка БД»
Удаление тестовых/демо-данных при первичной настройке системы. Удаляет демо-клиентов, тестовые услуги, фейковые финансовые операции — всё, что было в стартовой поставке для демонстрации. Не трогает реальные данные оператора.
Запускать только один раз — после первичной настройки и до загрузки реальных клиентов. Действие необратимо, перед запуском система запрашивает подтверждение.
Подраздел «Закрытие периода»
Финальное действие месяца. Фиксирует балансы клиентов, переводит «корзину» (soft-deleted клиентов) в архив, готовит данные для бухгалтерской выгрузки. Запускается оператором вручную — обычно в первые дни нового месяца.
Производительность и оптимизация
В мае 2026 в биллинг внедрён пакет оптимизаций производительности. Главная цель — устранить «тяжёлые» страницы которые открывались по 3-4 секунды и тормозили работу операторов при одновременной нагрузке.
Что было сделано
Семь пакетов улучшений, выкаченных за один день без даунтайма биллинга:
- 8 composite-индексов PostgreSQL на самых частых полях запросов
(audit, abonents_block, users_usluga, users.nas_id, radius.logged, finance_operations.op_date).
Применены с
CREATE INDEX CONCURRENTLY— без блокировки таблиц. - Redis-кеши для главного дашборда (TTL 60с), мониторинга NAS (TTL 60с), карты клиентов (TTL 5 мин), VpnConst-настроек (TTL 60с с автоматической инвалидацией при сохранении через UI).
- Денормализация Abonents.last_payment_at — дата последнего платежа
теперь хранится прямо у клиента, обновляется автоматически PostgreSQL-триггером
на
finance_operations.INSERT. Используется в фильтрах должников (period chips: новые/средние/старые/хронические). - Денормализация Abonents.primary_nas_id + primary_pool_id — основной
NAS и пул IP-адресов клиента (по самой свежей учётной записи Users). Используется
в списке клиентов папки, глобальном поиске и AJAX-подгрузке. Обновляется тремя
триггерами на
users.INSERT/UPDATE/DELETE. - statement_timeout=30s для пользовательских SQL-отчётов
(
/admin/reports/AdminCustomReports/) — защита от тяжёлых запросов которые вешали gunicorn-worker на минуты. После таймаута показывается понятное сообщение «Отчёт прерван по таймауту». - Устранение N+1-запросов в 5 горячих местах: NAS list (DISTINCT ON для last_event), NAS Monitor (GROUP BY вместо N COUNT), AdminAccounts (3 count → 1 aggregate с filter=Q), folder/search (lookup из Nas/IpPull вместо JOIN Users).
- Убран функциональный cast
op_date::dateв FinanceOperations view — теперь используются B-Tree индексы вместо seq scan.
Что это дало
| Страница | Было (cold cache) | Стало | Ускорение |
|---|---|---|---|
| Должники, фильтр «не платили 90+ дней» | 3 821 мс | 0,34 мс | 11 000× |
| Папка клиентов | 545 мс | 100-300 мс | 2-5× |
| Глобальный поиск | 301 мс | 100-200 мс | 2-3× |
| Карточка клиента | 400 мс | ~100 мс | 4× |
| Главный Dashboard | 600-1500 мс | <100 мс | 5-15× |
| Мониторинг NAS (auto-refresh) | full SQL × 30с | warm cache | 3-5× |
Главный эффект — страница должников (одна из самых частых для операторов отдела взысканий) теперь открывается мгновенно вместо 4 секунд ожидания.
Защитные механизмы
- Backup при деплое: код (tar 254 МБ) + pg_dump (29 МБ) — откат 5-10 минут.
- Celery beat safety-net: раз в неделю запускается
rebuild-last-payment-atиrebuild-primary-nas-pool— полная сверка денормализованных полей с реальной таблицей. Если триггер пропустил какой-то платёж (например, при массовом импорте с отключёнными триггерами) — данные восстанавливаются автоматически. - CONCURRENTLY-индексы: создание индекса не блокирует таблицу для чтения и записи, клиенты ничего не замечают.
Документы (для разработчиков)
/admin/reports/dev/?id=392— общее описание простыми словами/admin/reports/dev/— все технические отчёты по фазам 1-4: план оптимизации, реализация индексов и кешей, денормализация last_payment_at, денормализация primary_nas/pool