Разовый скрипт на боевом - запуск, защита, журнал, откат
Выполняем разовую правку данных на боевом сайте безопасно: запуск из консоли, сухой прогон, блокировка повтора, журнал каждого действия и снимок для отката.
Что нужно знать заранее
Разовый скрипт почти всегда живёт в проекте дольше, чем планировалось. Он остаётся в проекте, его находит другой разработчик и запускает через полгода, поэтому защита от повторного запуска нужна с первого дня.
Скрипт в публичном каталоге сайта доступен снаружи по прямой ссылке. Служебные скрипты кладут вне корня сайта либо закрывают проверкой, которую нельзя пройти из браузера.
Правка данных без сохранённого снимка почти всегда необратима. Резервная копия базы помогает, но разворачивать её ради одной таблицы дороже, чем сохранить изменяемые записи в файл заранее.
Шаги
- Положить скрипт вне публичного каталога сайта и запускать его только из консоли.
- Сделать сухой прогон, который печатает будущие изменения, но ничего не записывает.
- Сохранить снимок изменяемых данных в отдельный файл до первой настоящей записи.
- Защитить скрипт от повторного и параллельного запуска обычной блокировкой.
- Вести журнал каждого действия скрипта и хранить его дольше самой правки.
Решение
Подключаем ядро для запуска из консоли:
// /home/bitrix/tools/fix-articles.php - вне публичного каталога сайта$_SERVER['DOCUMENT_ROOT'] = '/home/bitrix/www';define('NO_KEEP_STATISTIC', true);define('NOT_CHECK_PERMISSIONS', true);require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';if (PHP_SAPI !== 'cli') { die("только из консоли\n"); } // из браузера не запуститьПроверка способа запуска - одна строка, которая закрывает целый класс проблем. Скрипт, найденный поисковым роботом и запущенный из браузера, правит данные без всякого предупреждения.
Делаем сухой прогон по умолчанию:
$apply = in_array('--apply', $argv, true); // без этого ключа только показываемforeach ($items as $item) { printf("%d: %s -> %s\n", $item['ID'], $item['OLD'], $item['NEW']); if ($apply) { updateItem($item); }}printf("%s: %d записей\n", $apply ? 'изменено' : 'будет изменено', count($items));Сухой прогон по умолчанию спасает от опечаток в фильтре. Список из двадцати тысяч записей вместо ожидаемых двадцати виден сразу, и это лучший момент, чтобы остановиться.
Сохраняем снимок для отката:
file_put_contents('/home/bitrix/backup/snapshot-' . date('Ymd-His') . '.json', json_encode($before, JSON_UNESCAPED_UNICODE));// снимок делают до первой записи и хранят рядом с журналом прогонаБлокируем повторный запуск:
$lock = \Bitrix\Main\Application::getConnection()->lock('fix_articles', 5);if (!$lock) { die("скрипт уже выполняется\n"); }// блокировка снимается сама при завершении процесса, включая аварийноеВедём журнал прогона:
php /home/bitrix/tools/fix-articles.php --apply 2>&1 | tee -a /home/bitrix/logs/fix-articles.logtail -5 /home/bitrix/logs/fix-articles.log# журнал хранят дольше правки: вопросы о ней приходят через неделиТипичные проблемы
Скрипт запустили из браузера и данные изменились.
Файл лежит в публичном каталоге и доступен по прямой ссылке любому. Служебные скрипты кладут вне корня сайта и проверяют способ запуска первой строкой.
Правка задела в тысячу раз больше записей, чем ожидалось.
Ошибка в фильтре выборки, а скрипт сразу записывал изменения без всякого показа. Сухой прогон по умолчанию показывает объём до того, как что-то изменится.
Скрипт запустили дважды, и правка применилась повторно.
Нет ни блокировки, ни признака уже выполненной операции у изменённых записей. Блокировку ставят на время работы, а факт правки помечают в самих данных.
Откатить изменения нечем.
Снимок изменяемых данных не сохранялся, а резервная копия базы старше суток. Снимок делают до первой записи и хранят рядом с журналом прогона.
Через полгода никто не помнит, что делал этот скрипт.
Скрипт остался в проекте без описания задачи, журнала и даты последнего запуска. В начало файла кладут комментарий с задачей, а прогоны пишут в журнал.
Частые вопросы
Где хранить служебные скрипты?
Вне публичного каталога сайта, рядом с проектом, но недоступно из браузера. В репозитории они лежат вместе с кодом и проходят обычное код-ревью.
Обязателен ли сухой прогон?
На боевом сайте - да, и лучше сделать его поведением по умолчанию. Настоящая правка включается отдельным ключом запуска.
Как не запустить скрипт дважды?
Блокировкой на время работы и признаком обработки у самих записей. Первое спасает от параллельного запуска, второе - от повторного через неделю.
Что делать с длинными правками?
Резать на порции с сохранением места остановки, как обычную фоновую операцию. Тогда обрыв соединения не начинает работу заново.
Нужно ли согласовывать такие правки?
Массовые изменения данных - да, вместе с окном и планом отката. Заказчик должен знать, что и когда меняется в его данных.
Смежное
- Агенты и cron - оглавление подтемы
- Долгая операция шагами: порции, состояние, прогресс - как резать длинную правку
- Свой агент и задание по расписанию: создание, шаг, защита - регулярные задачи вместо разовых
- Массовая правка товаров: обновление свойств скриптом - типовой пример такой правки
- Тестовые данные для стенда: генерация, пометка, чистка - где такие скрипты обкатывают
- Отдача файла с проверкой прав: закрытые каталоги, заголовки, ссылки - почему публичный каталог опасен
- Резервное копирование: расписание, состав, хранение и проверка - страховка перед правкой
- Сервер и поиск - устройство площадки целиком