MAATRIX / Блог / Регламент работы с боевой базой: кто, когда и с чьего разрешения

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

MAATRIX

Рано или поздно в проекте случается момент, когда через приложение поправить данные нельзя, а руки уже тянутся к psql или клиенту MySQL, открытому напрямую в проде. Один неверный WHERE, забытая точка с запятой не в том месте, скрипт, отработавший не на той базе — и вы за десять секунд превращаете рабочий инцидент в катастрофу с восстановлением из бэкапа. Регламент прямого доступа к боевой базе — это не бюрократия ради бюрократии, а единственный способ оставить эту дверь открытой и при этом не потерять данные.

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

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

Классический сценарий: нужно закрыть десяток "зависших" заказов. Вы пишете UPDATE orders SET status = 'closed' WHERE created_at < '2026-01-01', забыв добавить AND status = 'pending'. Запрос отрабатывает мгновенно, коммитится по автокоммиту — и вы закрыли все заказы за два года, включая уже оплаченные и находящиеся в доставке. Приложение ничего не заметило: с точки зрения базы это легитимное изменение.

Второй частый случай — правка не в той среде. Открыты два терминала: один к staging, второй к продакшену, оба подписаны одинаково невзрачным приглашением psql (16.4). Команда для тестовой базы улетает в боевую. Здесь спасает не внимательность (она подводит рано или поздно), а то, что среда физически не даёт так ошибиться: разные цвета приглашения, разные хосты подключения, разный набор прав.

Третий сценарий — рискованная операция без транзакции. DELETE FROM sessions WHERE user_id = 123 кажется безобидным, пока не выясняется, что из-за старого JOIN в подзапросе условие вернуло не то множество строк. Без транзакции откат невозможен физически — данные уже удалены, и восстановление идёт только из бэкапа с потерей всех изменений после точки восстановления.

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

Кто имеет право на прямой доступ к боевой базе

Первый вопрос регламента — не "как делать", а "кому вообще можно". Практика, которая работает в командах любого размера:

  • Именной список, а не роль. Не "все из DevOps", а конкретные имена/логины с указанием, к каким базам и с какими правами. Список хранится в одном месте (вики, access.md в репозитории, тикет-система) и пересматривается не реже раза в квартал — как в ревизии доступов раз в квартал, так и здесь принцип тот же: доступ, который никто не проверяет, со временем становится доступом для уволившихся сотрудников и забытых подрядчиков.
  • Отдельная учётная запись для ручных операций, не совпадающая с сервисным пользователем приложения. У сервисного пользователя — права CRUD на нужные таблицы и ничего больше; у "ручного" пользователя — те права, которые нужны для диагностики и правок, но не DROP, не TRUNCATE, не права DDL без отдельного согласования.
  • Read-only доступ по умолчанию, read-write — по запросу. Большая часть задач "посмотреть, что случилось" решается SELECT. Права на запись выдаются отдельно, часто временно (роль в PostgreSQL с ограничением по времени через внешний скрипт или ручное REVOKE после завершения работ).
  • Обязательное подключение через бастион или VPN, а не напрямую по публичному IP базы. Если у вас ещё не выстроена такая схема — то же самое обсуждается в статье про то, как оформить доступ сотрудников к продакшену: база данных здесь ничем принципиально не отличается от остального продакшн-контура.

Отдельно стоит прописать, что "право на доступ" не равно "право действовать в одиночку" — это уже следующий пункт регламента.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Когда обход приложения оправдан

Регламент должен явно перечислять, для каких задач прямой доступ — нормальная практика, а для каких — нет. Иначе граница размывается, и "быстро поправить в базе" становится привычкой вместо исключения.

Оправданные случаи:

  • Диагностика инцидента, когда через интерфейс приложения не видно причины (зависшие блокировки, аномальные значения, несогласованность после сбоя).
  • Разовое исправление данных, повреждённых багом, который уже исправлен в коде, но старые записи остались в неверном состоянии.
  • Миграция или массовая правка, которую разработчик сознательно не стал реализовывать через приложение, потому что это разовая операция.
  • Восстановление согласованности после ручного вмешательства другого администратора (то есть исправление последствий предыдущего инцидента).

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

  • Регулярные операции, которые проще и безопаснее автоматизировать (например, ежедневная чистка старых записей — для неё пишут cron-задачу с логированием, а не заходят руками).
  • Обход бизнес-валидации "потому что через админку долго" — если валидация мешает работать, чинить нужно валидацию, а не подключение к базе.
  • Любые операции, которые можно сделать через существующий административный интерфейс приложения, даже если это на пару кликов дольше.

Хорошее эмпирическое правило: если один и тот же ручной SQL-запрос вы написали второй раз за месяц — это не разовая правка, а признак того, что нужен инструмент или изменение в коде.

Подтверждение для рискованных операций: принцип второй пары глаз

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

Практическая реализация без лишней бюрократии:

  1. Определите, что считается "рискованной операцией". Обычно это: UPDATE/DELETE без LIMIT, затрагивающий больше N строк (порог зависит от таблицы — для пользователей это 5 строк, для логов — 5000); любой DDL (ALTER TABLE, DROP); изменение прав доступа; операция на таблице с финансовыми данными независимо от масштаба.
  2. Второй человек читает запрос до выполнения, не после. Проще всего это делать в общем канале (Slack/Telegram) или тикете: автор публикует точный текст запроса и ожидаемый результат (SELECT COUNT(*) ... с тем же WHERE, что и в правке), второй инженер подтверждает или указывает на проблему.
  3. Second reviewer подтверждает именно запрос, а не намерение. "Хочу поправить статус заказов" — недостаточно; нужен точный SQL-текст, который будет выполнен.
  4. Для критичных систем — второй человек присутствует онлайн во время выполнения, а не просто ставит "лайк" заранее: между согласованием и запуском база могла измениться.

Это не отменяет права инженера на дежурстве действовать в одиночку при реальном инциденте (когда каждая минута простоя дороже риска ошибки) — но такие случаи регламент должен помечать отдельно, с обязательным пост-фактум ревью в течение суток.

Обязательный бэкап или снапшот перед ручным вмешательством

Даже согласованный и трижды проверенный запрос может пойти не так — из-за триггера, о котором забыли, из-за конкурентной транзакции, из-за банальной опечатки, которую не заметил ни автор, ни ревьюер. Поэтому правило регламента: любое ручное изменение в проде предваряется точкой восстановления, к которой можно откатиться за минуты, а не часы.

Варианты в порядке возрастания надёжности и затрат времени:

  • Точечный дамп затрагиваемых таблиц — быстрее всего:
pg_dump -h prod-db.internal -U readonly_user \
  -t orders -t order_items \
  --data-only -F c -f /backup/pre-change-orders-$(date +%F-%H%M).dump mydb

для MySQL аналогично:

mysqldump -h prod-db.internal -u backup_user -p \
  --single-transaction --no-create-info \
  mydb orders order_items > /backup/pre-change-orders-$(date +%F-%H%M).sql
  • Снапшот на уровне диска или ФС, если правка масштабная — быстрее восстановить целиком, чем собирать выборочный дамп. На ZFS: zfs snapshot pool/pgdata@pre-change-2026-08-27, на LVM: lvcreate --size 5G --snapshot --name pre_change /dev/vg0/pgdata. Помните об ограничении: снапшот на том же носителе, что и база — это точка на диске, а не полноценная резервная копия.
  • Плановый бэкап "по расписанию" не считается точкой восстановления для ручной правки — между последним автоматическим бэкапом и моментом вмешательства могли пройти часы значимых изменений. Регламент должен явно требовать *свежую* точку непосредственно перед операцией, а не полагаться на ночной pg_dump.

После выполнения операции точку восстановления не удаляют сразу — держат минимум 24–48 часов, пока не подтверждено, что побочных эффектов нет (не открылись жалобы пользователей, не разошлась статистика, не сломалась связанная логика в других таблицах).

Транзакция с проверкой вместо прямого UPDATE/DELETE

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

Шаблон для PostgreSQL:

BEGIN;

-- сначала смотрим, что попадёт под изменение
SELECT id, status, created_at FROM orders
WHERE created_at < '2026-01-01' AND status = 'pending';
-- убедитесь, что количество строк и их состав совпадают с ожиданием

UPDATE orders SET status = 'closed'
WHERE created_at < '2026-01-01' AND status = 'pending';

-- проверяем результат ДО коммита
SELECT id, status FROM orders
WHERE created_at < '2026-01-01' AND status = 'closed';

-- если всё верно:
COMMIT;
-- если что-то не так:
-- ROLLBACK;

Для MySQL с InnoDB схема та же, но нужно явно проверить, что автокоммит выключен:

SET autocommit = 0;
START TRANSACTION;
SELECT id, status FROM orders WHERE created_at < '2026-01-01' AND status = 'pending';
UPDATE orders SET status = 'closed' WHERE created_at < '2026-01-01' AND status = 'pending';
SELECT id, status FROM orders WHERE created_at < '2026-01-01' AND status = 'closed';
-- COMMIT; или ROLLBACK;

Несколько практических нюансов:

  • Не держите транзакцию открытой надолго. Незакоммиченная транзакция держит блокировки на затронутых строках, и параллельные запросы приложения встают в очередь. Нужно отвлечься между SELECT и решением — лучше сделать ROLLBACK и начать заново.
  • Ставьте statement_timeout (SET statement_timeout = '30s'; в начале сессии) на случай, если запрос зацепит блокировку и повиснет — это не даёт случайно "заморозить" продакшен на неопределённое время.
  • LIMIT в UPDATE/DELETE не работает "из коробки" в PostgreSQL (в отличие от MySQL) — используйте подзапрос с ctid IN (SELECT ctid FROM ... LIMIT N), если нужно ограничить масштаб операции.
  • Транзакция не заменяет точку восстановления из предыдущего раздела: транзакция ловит ошибку до коммита, бэкап — уже после, если ошибка коммитнулась незамеченной.

Логирование факта прямого вмешательства

Даже безупречно выполненная по всем правилам ручная правка обязана оставить след — иначе через месяц никто не вспомнит, почему в таблице заказов есть три записи с нетипичным статусом, и это снова примут за баг.

Минимальный набор, который должен фиксироваться на уровне процесса (отдельно от логов самой СУБД):

  • Запись в общем журнале изменений — по тому же принципу, что описан в статье как вести журнал изменений сервера: кто выполнил, когда, к какой базе подключался, точный текст запроса, кто подтвердил (если требовалось), какая точка восстановления была создана, каким был результат.

Шаблон записи:

Дата: 2026-08-27 14:32 UTC
Кто: i.petrov (по согласованию с a.sidorova)
База: prod-orders (prod-db.internal)
Причина: закрытие зависших заказов после сбоя очереди (инцидент INC-2318)
Точка восстановления: /backup/pre-change-orders-2026-08-27-1425.dump
Запрос: UPDATE orders SET status='closed' WHERE created_at<'2026-01-01' AND status='pending'
Затронуто строк: 47
Результат: подтверждено, побочных эффектов не выявлено (проверка через 24ч — 2026-08-28)
  • Логирование на уровне СУБД — включите log_statement = 'mod' в PostgreSQL (логирует все изменяющие запросы) или расширение pgAudit для детального аудита; в MySQL — general_log на период работ или audit_log плагин (Percona Server, MySQL Enterprise). Это подстраховка на случай, если ручную запись забыли сделать или сделали неточно.
  • Уведомление в канал в момент выполнения, а не постфактум — вебхук в Telegram/Slack при подключении с правами на запись к боевой базе снимает вопрос "кто сейчас в проде" для всей команды.
  • Отдельная категория — прямой доступ ИИ-агентов и автоматизированных скриптов: это другой уровень риска, потому что нет "второй пары глаз" в моменте. Если в проекте применяются такие агенты, стоит отдельно прочитать про антипаттерн ИИ-агента с полным доступом к продакшену и не давать автоматике те права, которые вы не даёте человеку без подтверждения.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужен ли регламент, если в команде два человека и оба всё видят?

Да, но упрощённый: достаточно правила "перед UPDATE/DELETE в проде — снапшот и подтверждение в чате", даже без формального документа. Проблема возникает не от размера команды, а от того, что правило не зафиксировано и забывается под давлением инцидента.

Что делать, если инцидент критичный, а второго человека нет на связи?

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

Достаточно ли ночного автоматического бэкапа как точки восстановления?

Нет, если между бэкапом и моментом правки прошло значимое время — восстановление отбросит все изменения за этот период. Нужна отдельная свежая точка непосредственно перед операцией.

Как ограничить права read-write пользователя, чтобы он не снёс всю таблицу?

В PostgreSQL создайте роль без прав TRUNCATE/DROP/DDL и выдавайте GRANT UPDATE, DELETE ON конкретные_таблицы TO manual_ops_role только на нужные таблицы — принцип наименьших привилегий надёжнее любого напоминания "будь осторожен".

Нужно ли логировать даже безобидные SELECT-запросы?

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

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →