Схемы Что нового

Обслуживание

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

Содержание раздела

Резервные копии

Раздел /admin/settings/backup/ — единственное место, где видно состояние резервного копирования всей установки. Сюда приходят двое: администратор, который настраивает политики и следит, что копии идут, и тот, кто разбирает аварию и ищет свежую копию. Второй приходит в худший день года, поэтому раздел устроен так, чтобы отвечать на три вопроса сразу: есть ли копия, где она лежит и можно ли из неё восстановиться.

Обучающее видео 3:02

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

1920×1080 с озвучкой Скачать
Раздел «Резервные копии»: объём копий, сводка состояния, политики, журнал копий с иконками типа, замком у зашифрованной копии и отметкой загруженной
Сверху — сколько занимают копии и сколько осталось на диске. Ниже политики копирования, под ними — журнал копий с кнопкой «Загрузить копию».

Баннер сверху показывает, сколько места занимают сами копии, а не занятость раздела сервера, — одной строкой: объём копий на сервере, свободное место на диске (значок диска) и объём копий в облаке с числом файлов (значок облака). Подпись к значку видна при наведении и доступна экранному диктору. Занятость всего раздела на экран не выводится — по ней нельзя решать, сколько копий хранить.

Объём в облаке считается по самому хранилищу, а не по записям о копиях (кешируется на 10 минут). Правило «сколько копий хранить» удаляет старые файлы только на сервере — в облаке копии остаются, поэтому по записям объём выходил бы заниженным. Если облако не настроено, этой части строки нет.

Сводка: в порядке ли копирование

Под баннером — одна строка, которая отвечает на главный вопрос раздела, чтобы не сверять даты по карточкам:

Сводка над политиками: состояние, последняя копия, следующая копия, запуски за неделю
Сводка и карточки политик: у каждой — час запуска и когда будет следующая копия.

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

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 до отправки в облако.

Раздел «Шифрование» в окне политики: переключатель включён, пароль, повтор и предупреждение, что пароль не восстановить
Пароль, повтор и предупреждение: пароль не восстановить. Если пароли не совпали, политика не сохранится.
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 ГБ. Файл можно перетащить прямо в окно.

Окно загрузки копии: выбран зашифрованный файл, поле пароля копии
Зашифрованный файл: пароль можно проверить сразу
Загрузка идёт частями: сколько загружено, скорость и сколько осталось
Ход загрузки: объём, скорость, оставшееся время
sequenceDiagram
  participant B as Браузер
  participant S as Сервер
  B->>S: начать: имя и размер
  S-->>B: номер загрузки
  loop части по 8 МБ
    B->>S: часть «с байта N»
    S-->>B: принято байт
  end
  B->>S: завершить, пароль по желанию
  S-->>B: тип, шифрование, запись в журнале

Журнал запусков

Записи о копиях живут ровно столько, сколько живут файлы: старые удаляются по правилу «хранить N копий». Из-за этого неудачный запуск не оставлял следа — на экране был зелёный список политик, а копий не снималось месяц. Теперь каждый запуск пишется отдельно: успех или ошибка, длительность, размер, ушла ли копия в облако. Журнал переживает и удаление файлов, и удаление самой политики.

Оповещения приходят в двух случаях: копия упала (с текстом ошибки) и копия не снимается — прошло больше двух периодов расписания. Второе важнее: молчание системы выглядит как «всё хорошо».

Типы копий:

Настройка политики — одно окно и для новой политики, и для правки. Слева четыре раздела: Что копируем (название и состав копии), Хранение (куда сохранять и сколько копий держать), Шифрование (пароль на файл копии, см. выше) и Расписание (как часто, во сколько, включена ли политика). Списки «Что резервируем», «Куда сохранять» и «Как часто» — с иконкой и цветом у каждого пункта.

Окно новой политики: четыре раздела слева — что копируем, хранение, шифрование, расписание
Новая политика: разделы слева, форма справа.

Час запуска у каждой политики свой. Копия снимается в течение трёх часов после выбранного времени. Пустое значение — «как в общих настройках» (BACKUP_START_HOUR, по умолчанию 3:00), так политики, созданные раньше, ведут себя как прежде. Если сервер стоял и копия просрочена вдвое, она снимается сразу, не дожидаясь окна. У политики «вручную» поле часа неактивно.

Действия с политикой. На карточке на виду только «Запустить», а «Настройки» и «Удалить» — в меню рядом: опасное действие не стоит вплотную к частому. На телефоне то же меню открывается нижней шторкой.

Меню действий политики: «Настройки» и «Удалить» рядом с кнопкой «Запустить»
«Запустить» — на карточке, настройки и удаление — в меню.

Фильтры журнала — политика, тип копии и статус — выпадающие списки с иконкой и цветом у каждого пункта: база данных, файлы, Docker-образ (фирменный знак Docker), «выполнен» и «ошибка» различаются с одного взгляда. Если в журнале есть загруженные копии, в фильтре политики появляется пункт «Загруженные файлом». Выбранный фильтр подсвечивается зелёной рамкой, «Сбросить» снимает все фильтры сразу.

Фильтр журнала по типу копии: пункты с иконками и цветом
Фильтр по типу: у каждого пункта своя иконка и цвет.

Архив. Разовые конфигурации — «перестраховка перед релизом», «копия перед миграцией» — остаются в списке навсегда и топят рабочие политики. Такие уводятся в архив: они не исчезают и не мешают, а открываются кнопкой «Архив» в шапке. Планировщик архивные политики не запускает.

Журнал копий — файл, политика, тип, дата, размер и статус. У типа и политики — те же иконки и цвета, что в фильтрах; копия, загруженная файлом, помечена «Загружена файлом», зашифрованная — замком. Скачать копию — щелчок по имени файла; Восстановить и Удалить — в меню строки. Пока копия снимается, у политики виден индикатор, а страница опрашивает статус каждые 5 секунд.

Защита от «зависших» копий: если Celery-воркер падает или перезапускается во время резервного копирования (например, при переезде сервера), запись могла навсегда остаться в статусе running — на странице крутился вечный спиннер и шёл бесконечный опрос статуса. Теперь функция _reap_stale_running() автоматически помечает любую запись в running старше 30 минут как «Ошибка» с понятным сообщением. Срабатывает при открытии страницы и при каждом опросе статуса (раз в 3 секунды), так что зомби-запись сбрасывается сама. Дополнительно run_backup теперь явно завершается ошибкой при неизвестном типе бекапа или если файл не был создан.

Подтверждения: что именно произойдёт

Все действия раздела спрашивают подтверждение единым компонентом и перечисляют последствия, а не ограничиваются вопросом «вы уверены?».

Подтверждение запуска копии: что резервируем, сколько заняло в прошлый раз, уйдёт ли копия в облако
Запуск: видно, сколько заняло в прошлый раз и уйдёт ли копия в облако
Подтверждение удаления политики: копии останутся, новые сниматься перестанут
Удаление политики: снятые копии остаются, новые не появятся

Восстановление: по шагам на сервере

Раньше окно «Восстановить» предлагало выбрать, что разворачивать, и кнопку запуска, но задачи, которая выполняла бы восстановление, в системе не было — нажатие заканчивалось ошибкой. Замена рабочей базы требует остановить приложение: RADIUS, воркеры и боты держат соединения с базой. Поэтому из панели её не запускают, а окно показывает готовые шаги для сервера — с подставленным именем файла и кнопкой копирования у каждой команды.

Окно восстановления зашифрованной копии базы: шесть шагов с командами для сервера
Для зашифрованной копии первым делом — расшифровка в контейнере celery.

Для копии базы данных — от имени root:

  1. Перейти в каталог копий:
    cd "$(docker volume inspect smit-billing_backup_data -f '{{.Mountpoint}}')"
  2. Если копия зашифрована — расшифровать в контейнере 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'
  3. Снять копию текущей базы на случай отката:
    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
  4. Остановить приложение — база продолжает работать:
    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
  5. Развернуть копию поверх рабочей базы:
    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'
  6. Запустить приложение (те же сервисы, команда 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):

Каждая ячейка показывает размер (Кб/Мб/Гб) + количество записей. Если рядом с цифрой растёт ⚠ — таблица превышает порог опасного роста и требует внимания.

Автоочистка БД

Настройки автоочистки и аудита
Настройки автоочистки и аудита

Параметры retention (период хранения, после которого записи удаляются):

ПараметрSystemSettings ключDefaultЧто удаляется
СообщенияDB_CLEANUP_MSG_DAYS30 днейMsgStack где send_date < now − N
АудитDB_CLEANUP_AUDIT_DAYS365 днейAuditOperations где op_time < now − N
RADIUS-сессииDB_CLEANUP_SESSIONS_DAYS90 днейRadiusSessions где END_TIME IS NOT NULL AND END_TIME < now − N (только закрытые!)
NAS-событияDB_CLEANUP_EVENTS_DAYS30 днейNasStatusLog где event_time < now − N
Размер батчаDB_CLEANUP_BATCH_SIZE5000Сколько строк удалять за один DELETE (чтобы не лочить таблицу надолго)
ВключенаDB_CLEANUP_ENABLED1Тогл всей автоочистки

Расписание: Celery beat-задача db-cleanup-daily запускается раз в сутки по интервалу (86400 с от старта Celery beat) — точное время суток зависит от момента последнего запуска beat, к фиксированному часу не привязано. Для теста есть кнопка «Запустить чистку сейчас».

Что делает задача (billing/tasks/db_cleanup.py::run_db_cleanup):

  1. Создаёт запись в DbCleanupRun с status='running'.
  2. Для каждой из 4 таблиц считает порог cutoff = now - timedelta(days=N).
  3. В цикле: DELETE ... LIMIT batch_size пока есть записи. Между батчами 100мс паузы — снижает нагрузку.
  4. Записывает в результат: msg_deleted, audit_deleted, sessions_deleted, events_deleted, execution_time.
  5. Обновляет DbCleanupRun.status='ok' + finished_at. При ошибке — status='failed' + error.
  6. Если за прогон удалено > 100K записей — отправляет Telegram-уведомление.

Кнопки управления:

История запусков

Таблица последних 50 запусков из DbCleanupRun:

Аудит зомби-сессий на NAS

Аудит зомби-сессий
Аудит зомби-сессий

Зомби-сессия — это запись в RADIUS_SESSIONS с END_TIME IS NULL (то есть «сессия активна»), но при этом NAS не подтверждает её существование. Возникает когда:

Зомби-сессии искажают виджет «Онлайн сейчас» на дашборде (показывает большее число, чем реально), мешают корректному выбору IP из пула при переавторизации, и забивают `RADIUS_SESSIONS` лишними «открытыми» строками.

Beat-задача session-audit-hourly (billing/tasks/session_audit.py):

  1. Запускается раз в час (default; настраивается через SESSION_AUDIT_INTERVAL_MINUTES).
  2. Берёт ровно 4 NAS (default SESSION_AUDIT_NAS_PER_RUN=4) — те, у которых давно не было аудита (sort by last_audit_at ASC). Так за сутки покрывается ~96 NAS — больше, чем у любого реального оператора.
  3. Для каждого NAS берёт батч из 50 активных сессий (default SESSION_AUDIT_BATCH_SIZE=50). Это ~2 минуты времени NAS — не нагружает его.
  4. Для каждой сессии: radclient запрос Status-Server или Accounting Interim-Update к NAS — если NAS не подтверждает наличие session-id → метим как «подозрительную».
  5. Через 5 минут идёт повторная проверка тех же сессий. Если NAS ещё раз не подтверждает — сессия считается зомби, ставится END_TIME=now(), END_REASON='zombie-cleanup'.
  6. Counter подтверждённых zombie добавляется в SessionAuditRun.zombie_killed.
  7. Если zombie_killed > 0 — отправляется Telegram-алерт с разбивкой по NAS.

Параметры аудита:

ПараметрSystemSettings ключDefault
Интервал между запускамиSESSION_AUDIT_INTERVAL_MINUTES60 минут
NAS за один запускSESSION_AUDIT_NAS_PER_RUN4
Сессий за один батчSESSION_AUDIT_BATCH_SIZE50
Задержка повторной проверкиSESSION_AUDIT_RECHECK_DELAY300 сек (5 мин)
Telegram-алертSESSION_AUDIT_TG_ALERT1
ВключёнSESSION_AUDIT_ENABLED1

История аудитов ниже — таблица из SessionAuditRun: время, NAS, проверено сессий, найдено зомби, длительность, статус.

Выбор NAS для аудита

Выбор NAS для аудита
Выбор NAS для аудита

Таблица всех NAS с enabled=True и колонками:

На рабочий сервер сейчас в активном аудите 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 секунды и тормозили работу операторов при одновременной нагрузке.

Что было сделано

Семь пакетов улучшений, выкаченных за один день без даунтайма биллинга:

  1. 8 composite-индексов PostgreSQL на самых частых полях запросов (audit, abonents_block, users_usluga, users.nas_id, radius.logged, finance_operations.op_date). Применены с CREATE INDEX CONCURRENTLY — без блокировки таблиц.
  2. Redis-кеши для главного дашборда (TTL 60с), мониторинга NAS (TTL 60с), карты клиентов (TTL 5 мин), VpnConst-настроек (TTL 60с с автоматической инвалидацией при сохранении через UI).
  3. Денормализация Abonents.last_payment_at — дата последнего платежа теперь хранится прямо у клиента, обновляется автоматически PostgreSQL-триггером на finance_operations.INSERT. Используется в фильтрах должников (period chips: новые/средние/старые/хронические).
  4. Денормализация Abonents.primary_nas_id + primary_pool_id — основной NAS и пул IP-адресов клиента (по самой свежей учётной записи Users). Используется в списке клиентов папки, глобальном поиске и AJAX-подгрузке. Обновляется тремя триггерами на users.INSERT/UPDATE/DELETE.
  5. statement_timeout=30s для пользовательских SQL-отчётов (/admin/reports/AdminCustomReports/) — защита от тяжёлых запросов которые вешали gunicorn-worker на минуты. После таймаута показывается понятное сообщение «Отчёт прерван по таймауту».
  6. Устранение 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).
  7. Убран функциональный 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×
Главный Dashboard600-1500 мс<100 мс5-15×
Мониторинг NAS (auto-refresh)full SQL × 30сwarm cache3-5×

Главный эффект — страница должников (одна из самых частых для операторов отдела взысканий) теперь открывается мгновенно вместо 4 секунд ожидания.

Защитные механизмы

Документы (для разработчиков)