Скупой платит дважды: неделя на пяти серверах клиента после бюджетного переезда

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

Инфраструктура - пять серверов на ISPmanager 6, root по SSH, у каждого свой пароль. Сайты - WordPress/WooCommerce, часть из них - «сетки» из десятков доменов-алиасов на одном движке: один флагманский домен обслуживает несколько десятков городских дублей-поддоменов с одинаковым контентом под разные регионы. Ниже - хроника одной рабочей недели: первый день после переезда, три отдельных рецидива на одном и том же сайте, аудит безопасности всего флота и разбор бага с раздувшейся на 5 миллионов строк таблицей. Идентифицирующие детали изменены.

День 1: первый день после переезда - три независимых пожара

Обход всех серверов

Задача звучала просто: «проверь их все, на некоторых уже перестали работать сайты». Вместо того чтобы гадать - обошёл все домены на всех серверах и сверил реальный ответ с DNS:

# ISPmanager не кладёт vhost'ы в дефолтные места nginx - сначала находим реальную раскладку
find /etc/nginx/vhosts -maxdepth 2 -type d
# затем на каждый домен: реальный статус + текущий A-record
curl -s -o /dev/null -w "%{http_code}" -H "Host: $domain" http://127.0.0.1/
dig +short $domain

Нашёл: два сайта реально сломаны на одном сервере (203.0.113.81), один ложный 403 (антибот-правило в .htaccess резало curl без User-Agent), и несколько доменов, для которых просто ещё не переключён DNS - это уже не проблема сервера.

fence-voronezh.example + fence-hub.example: диск 100%, WP_DEBUG и pm.max_children = 5

Один из городских доменов сети отдавал таймауты, флагманский домен сети - 502. Диагностика:

df -h /                                    # 154G/154G, 100%
du -xh --max-depth=1 /var/www/httpd-logs   # error.log одного из доменов = 63G

Причина роста лога - WP_DEBUG = true в проде: WordPress писал лавину deprecated-предупреждений на каждый запрос, а logrotate крутится раз в сутки и просто не успевал. Обрезал лог (truncate, не rm - процесс продолжает писать в тот же файловый дескриптор), выключил WP_DEBUG. Диск вернулся к 60% - но сайты всё ещё не открылись.

Вторая, более глубокая причина: pm.max_children = 5 у обоих пулов, притом что флагманский домен держит трафик по ~60 доменам-алиасам сразу. Поднял до 20:

sed -i 's/pm.max_children = 5/pm.max_children = 20/' \
  /opt/php84/etc/php-fpm.d/pool.d/fence-hub.example.conf \
  /opt/php84/etc/php-fpm.d/pool.d/fenceum.example.conf
systemctl reload php-fpm84

Load average упал с 7.78 до 4.05, оба сайта - стабильный 200. Этот же паттерн («маленький пул воркеров плюс что-то, что держит воркер дольше обычного») вернётся ещё дважды за неделю.

enclose-city.example: 502 в редакторе Elementor

Разработчик прислал скриншот 502 при открытии редактора Elementor. Причина - не PHP-лимиты (256M/300s, всё в порядке), а nginx:

upstream sent too big header while reading response header from upstream

У Elementor-редактора раздутые HTTP-заголовки (много cookie/nonce), не влезающие в дефолтный fastcgi_buffer_size. На соседнем сайте того же клиента на том же сервере это уже было исправлено раньше - просто забыли перенести настройку при миграции. Добавил в оба блока location @php (HTTP и HTTPS):

fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;

Системная защита от повторного разрастания логов

Раз WP_DEBUG оказался включён на одном сайте, стоило проверить остальные - и тот же баг нашёлся почти на всех сайтах клиента (шаблон при миграции просто не подчищали). На одном из доменов вдобавок WP_DEBUG_DISPLAY = true - ошибки PHP показывались прямо посетителям. Починил на 6 сайтах разом.

Но logrotate раз в сутки - недостаточная страховка сама по себе. Развернул независимый cron-вотчдог на всех 5 серверах:

#!/bin/bash
# diskguard.sh - обрезает любой лог в httpd-logs при превышении порога,
# независимо от logrotate/ISPmanager. Раз в 10 минут.
for f in /var/www/httpd-logs/*.log; do
  size=$(stat -c%s "$f" 2>/dev/null) || continue
  if [ "$size" -gt 1073741824 ]; then   # 1 ГБ
    : > "$f"
    echo "$(date) truncated $f (${size} bytes)" >> /var/log/diskguard.log
  fi
done

Сработал сам в течение того же часа - поймал забытый 3-ГБ лог на pavehub.example (203.0.113.82), который иначе разросся бы так же.

Три сайта с 500 на главной

Три связанных сайта отдавали 500 ещё до всех сегодняшних правок. WordPress глотал текст ошибки - вызвал front-controller напрямую через PHP CLI, чтобы увидеть настоящую причину:

php -r "require '/var/www/.../wp-load.php'; var_dump(error_get_last());"

Настоящей fatal-ошибки не было - значит где-то в коде осознанно вызывается wp_die(). Нашёл: на одном сайте забытый wp_die(); стоял перед рабочим wp_redirect(wp_login_url()), а не вместо него - типичный оставленный отладочный код. На двух других сайтах, работающих на общей кастомной CRM-теме, для одного пути анонимный посетитель корректно редиректился на логин, а для пустого пути (главная) та же функция в конце просто падала в безусловный wp_die(); exit();.

Убрал лишний wp_die() на первом сайте - но вылез вторичный баг: wp_login_url() уходил в цикл редиректов, потому что на сайте отдельно стоит защита wp-login.php через плагин All In One WP Security с секретным URL входа. Поправил редирект на реальный секретный путь.

На двух других сайтах при первой попытке скопировал цель редиректа с уже сломанной соседней ветки того же файла - это была уже моя собственная ошибка, обнаруженная только через час, когда клиент заново проверил ссылку и она всё ещё вела в 404. Разобрался до конца: правильная схема использовалась в 16 местах рабочего кода и в самом зарегистрированном правиле переписывания URL, а неверная - была единичной опечаткой в одном файле. Поправил не только цель редиректа, но и условия-исключения. Итог прозрачно указал в отчёте клиенту - и саму ошибку, и её исправление.

Вечер: перегрузка от краулинга ботов

Отдельно вечером того же дня (load average 15+ на 4 ядрах) один из сайтов перестал открываться. Причина - не FPM и не давешний фикс:

mysqld - 189% CPU, десятки одновременных SQL_CALC_FOUND_ROWS-запросов

Разбор трафика показал: массовый краулинг ссылок вида ?add-to-cart=XXXXX на каждой карточке товара - у WooCommerce на каждый такой URL кэш не срабатывает, и каждый клик бота гоняет тяжёлый запрос по каталогу. Из 20 000 последних запросов половина отдала 499 (клиент не дождался ответа), значительная часть - от краулера, который сканирует ссылки для превью в соцсетях и по документации игнорирует robots.txt, поэтому обычный запрет в файле не помогает.

Развернул fail2ban-джейл под конкретные User-Agent’ы, сначала протестировав фильтр на реальном логе (61% строк совпало - подтверждение масштаба, не ложное срабатывание):

fail2ban-regex /var/www/httpd-logs/pavehub.example.access.log /etc/fail2ban/filter.d/nginx-badbots.conf

За несколько минут забанили 83+ адресов, load average упал с 15-18 до 0.70. Раскатал тот же джейл на весь флот на случай похожего трафика в будущем.

День 2: тот же сайт, но на этот раз не шум, а атака

Тот же флагманский домен сети, симптомы похожие (сайт «виснет»), но причина другая. Пул PHP-FPM, поднятый три дня назад, снова забит полностью. На этот раз это не боты-краулеры, а сканер уязвимостей (42 IP, целится в бэкап-файлы конфигурации) и брутфорс через POST //xmlrpc.php со статичным поддельным User-Agent (12 IP).

awk '{print $12}' access.log | sort | uniq -c | sort -rn | head
grep -c 'POST //xmlrpc.php' access.log

45% всего трафика сайта оказалось этим самым XML-RPC-флудом. При таком масштабе точечные баны по IP - догонялки, атака росла быстрее, чем fail2ban успевал банить, load average дошёл до 16.63. Проверил, что легитимных потребителей xmlrpc.php на сайте нет (плагинов вроде Jetpack нет), и закрыл эндпоинт полностью на уровне nginx:

location = /xmlrpc.php { return 403; }

(добавлено перед location / в обоих блоках - HTTP и HTTPS; nginx схлопывает //xmlrpc.php в /xmlrpc.php, так что второй вариант блокируется тем же правилом). Плюс два новых fail2ban-джейла на всех 5 серверах: сутки бана за повторные POST на xmlrpc и неделя бана за один же запрос к бэкап-файлам - по такому пути легитимный посетитель не пойдёт никогда.

Load average упал с 16-18 до ~2, пул PHP-FPM отпустило.

На пике инцидента спросили: не поднять ли лимит воркеров ещё больше? Ответ был - нет:

На каждый воркер уходит около 192 МБ RAM. 20 воркеров - это уже почти 4 ГБ. Поднять до 40 - около 7.7 ГБ только под этот пул, а на сервере всего 11 ГБ, из них ещё MySQL и два других сайта. Своп уже частично использовался именно во время атаки при текущих 20 воркерах. Упёрся не в лимит, а в реальную атаку, которая теперь заблокирована - поднимать лимит вместо устранения причины означает при следующей атаке уйти не в контролируемые 502, а в своп и OOM-killer.

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

День 3: тот же сайт, третий раз - и снова другая причина

«Сайт снова не работает» - но по логам не то же самое, что в первые два раза. На этот раз - MySQL:

df -h /                          # снова под 100%
du -sh /var/lib/mysql/*/         # binlog-файлы: 54 ГБ, 523 файла

Время хранения бинарных логов было выставлено на 7 дней - при активной записи это накопило полсотни гигабайт логов репликации, которая тут даже не используется. Штатная команда очистки зависла - MySQL не мог завершить операцию, потому что для записи собственного индекса покупки логов ему самому не хватало свободного места на диске (классический deadlock «нужен диск, чтобы освободить диск»). Пришлось руками:

# Ручное удаление binlog-файлов на уровне ФС, оставив последние 20 + активный
cd /var/lib/mysql && rm binlog.000001 ... binlog.000570
mysql -e "PURGE BINARY LOGS TO 'binlog.000571';"   # синхронизирует индекс постфактум

Освободило 51 ГБ (100% → 67%). Снизил retention фактически, а не только по конфигу:

[mysqld]
binlog_expire_logs_seconds = 86400   # было 604800
SET GLOBAL binlog_expire_logs_seconds = 86400;  -- применить без рестарта

Распространил тот же retention на весь флот, кроме серверов, где бинлог вообще выключен.

Даже после этого фикса каталог сайта весил заметно больше ожидаемого. Причина - плагин кэширования: его файловый кэш страниц сам по себе никогда не удаляет физически устаревшие файлы с диска, только перестаёт их использовать по истечении срока. В сочетании с тем, что флагманский домен сети отвечает на любой Host-заголовок (сервер настроен как default_server для всей сети городских доменов), это создало больше 300 000 файлов кэша, часть - многолетней давности. Очистка каталога вручную освободила 48 ГБ; тот же паттерн нашёлся ещё на 4 сайтах на других серверах, в сумме ещё около 57 ГБ.

Развернул постоянный предохранитель - cacheguard.sh, cron раз в сутки на всех 5 серверах, который трёт файлы кэша старше 3 дней в кэш-директории любого сайта на сервере:

MAX_AGE_DAYS=3
for dir in /var/www/*/data/www/*/wp-content/cache; do
  [ -d "$dir" ] || continue
  find "$dir" -type f -mtime +$MAX_AGE_DAYS -delete
  find "$dir" -mindepth 1 -type d -empty -delete
done

День 4: проактивный аудит атак и баг с 5-миллионной таблицей

Поиск того, что защита ещё не покрывает

По собственной инициативе прогнал логи всех 5 серверов на предмет паттернов атак, для которых ещё нет банов. Нашёл и подтвердил действующего атакующего (один и тот же IP на двух разных серверах клиента одновременно): time-based blind SQL-инъекцию через REST API с ротацией User-Agent на каждый запрос, энумерацию пользователей WordPress через штатный REST-эндпоинт, пробы веб-шеллов через обфусцированное кодирование путей, и попытку эксплуатации известной публичной уязвимости через характерный User-Agent (проверил - не сработало, движок не уязвим).

Добавил новые fail2ban-джейлы на весь флот. Отдельно проверил и осознанно не забанил второй подозрительный по объёму запросов IP - по паттерну обращений (правки контента сразу на двух сайтах клиента, реальный fingerprint браузера) это оказался собственный админ клиента, а не атакующий.

Таблица опций на 5,3 миллиона строк

Ранее уже был известен риск («нагрузка на БД, требует отдельного разбора» из отчёта первого дня) - на этот раз докопался до конкретной причины:

SELECT COUNT(*) FROM wp7k2_options;               -- 5 300 000+ (норма - тысячи)
SELECT option_name, COUNT(*) FROM wp7k2_options
  WHERE option_name LIKE '_transient_%'
  GROUP BY option_name ORDER BY 2 DESC LIMIT 5;

Корень - в дочерней теме сайта: ключ кэша строился с использованием полного URI запроса. Поскольку URI запроса уникален почти для каждого визита (особенно с учётом ботового трафика по фильтрованным ссылкам каталога), каждый визит создавал новую, никогда не переиспользуемую и не истекающую запись transient. Что интересно - сам вызов создания transient в этом месте кода уже был закомментирован предыдущим разработчиком, а рядом даже нашёлся его же забытый скрипт-костыль, вручную удаляющий такие записи батчами - то есть проблема была известна раньше, но не вылечена системно.

Безопасная батч-очистка (важно - не блокирующий DELETE сразу по 5 млн строк, а пакетами, с проверкой свободного места на каждом шаге):

while true; do
  QUEUED=$(mysql -N -B "$DB" -e "
    TRUNCATE cleanup_ids;
    INSERT INTO cleanup_ids SELECT option_id FROM wp7k2_options
      WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP()
      LIMIT 20000;
    SELECT COUNT(*) FROM cleanup_ids;")
  [ "$QUEUED" -eq 0 ] && break
  mysql "$DB" -e "
    DELETE v FROM wp7k2_options v
      JOIN wp7k2_options t ON v.option_name = REPLACE(t.option_name,'_transient_timeout_','_transient_')
      JOIN cleanup_ids c ON t.option_id = c.option_id;
    DELETE t FROM wp7k2_options t JOIN cleanup_ids c ON t.option_id = c.option_id;"
  AVAIL=$(df --output=avail -B1 / | tail -1)
  [ "$AVAIL" -lt 104857600 ] && { echo "SAFETY ABORT: <100MB"; break; }
done

Удалило 2 373 262 строки (5,3 млн → 30 620). Но DELETE не сжимает файл таблицы на диске при пофайловом хранении InnoDB - освобождает место только внутри самого файла. Чтобы реально вернуть диск, таблицу пересобрал (дешевле, чем полная оптимизация - не требует двойного места таблицы во временном хранилище):

mysqldump acc48210_pavehub wp7k2_options > wp7k2_options_backup.sql
mysql -e "RENAME TABLE wp7k2_options TO wp7k2_options_old;
          CREATE TABLE wp7k2_options LIKE wp7k2_options_old;"
mysql acc48210_pavehub < wp7k2_options_backup.sql
# проверка: строки на месте, критичные опции (siteurl, active_plugins, rewrite_rules) целы
mysql -e "DROP TABLE wp7k2_options_old;"

Освободило около 90 ГБ.

Отдельно объяснился и сам факт, почему баг возвращался, хотя код давно исправлен: вызов создания transient в проблемном месте уже был закомментирован в коде, но строки продолжали плодиться. Дело оказалось не в коде, а в устаревшем opcache: правка на диске была, но воркеры PHP-FPM, годами живущие в памяти, продолжали исполнять старый скомпилированный байткод. Один systemctl reload php-fpm84 - без единой правки кода - немедленно остановил рост (проверил эмпирически: плоский счётчик строк 90+ секунд после релоада против тысяч новых строк в час до него).

Отдельным вопросом всплыло, почему в отчёте не было сказано про открытую проблему с нагрузкой на БД - это был тот самый пункт из отчёта первого дня, который провисел нерешённым несколько дней. План из двух частей: увеличил буферный пул InnoDB с дефолтных 128 МБ до 2.5 ГБ онлайн, без рестарта:

SET GLOBAL innodb_buffer_pool_size = 2684354560;
[mysqld]
innodb_buffer_pool_size = 2684354560

Плюс включил файловый кэш страниц у уже установленного, но выключенного плагина кэширования - не руками (риск ошибиться в структуре кэша), а через официальный API плагина:

$cache = WPO_Page_Cache::instance();
$cache->config->update(['enable_page_caching' => true]);
$cache->enable(true);   // сам создаёт advanced-cache.php и включает WP_CACHE в wp-config.php

Проверка сразу после показала false у статуса включённости - не баг, просто константа кэша начинает действовать только со следующей загрузки wp-config.php, а не в уже запущенном CLI-процессе. Подтвердил реальным трафиком после reload - файлы кэша начали появляться, load average упал с 1-3 до ~0.9. Отдельно проверил по прямому вопросу клиента - не забьёт ли новый кэш диск так же, как раньше: нет, предохранитель cacheguard.sh уже покрывает и этот путь, маска у него достаточно общая.

Скрытая проблема, найденная по отдельному запросу

Проверка "не было ли проблем с доступностью за последние 30 минут" на ещё одном сайте флота нашла 31 настоящую ошибку 500, не покрытую ни одним существующим джейлом - все по одному паттерну, эксплуатирующему баг ядра WordPress в одном из batch-эндпоинтов REST API. Добавил отдельный джейл на все 5 серверов - легитимного использования такого эндпоинта на этих сайтах нет.

День 5: резонанс двух настроек - и аудит всего флота

Пятиминутный простой

Плановая проверка ("не было ли проблем сегодня") на паре сайтов одного сервера. На одном всё в порядке - только уже известный сканер, забаненный джейлом с Дня 4. На другом - кластер из 10 ошибок 500/504 с одного и того же клиента (легитимный админ, опознанный ранее), но на разных URL - главная, админка, AJAX, форма обратной связи, редактор, даже favicon - в пятиминутном окне.

Разброс URL при одном источнике - верный признак не бага конкретной страницы, а исчерпания общего ресурса. В error-логе - прямое подтверждение:

upstream timed out (110: Connection timed out) ... upstream: "fastcgi://unix:/var/www/php-fpm/4.sock"

Конфиг пула (найден не с первой попытки - на этой инфраструктуре реальные пулы лежат в /opt/phpXX/etc/php-fpm.d/pool.d/, а не в стандартном месте):

pm = ondemand
pm.max_children = 5

и в подключаемом конфиге сайта:

php_value[max_execution_time] = 300

Тот же самый паттерн, что и в самый первый день - маленький пул плюс завышенный лимит времени выполнения. Один подвисший запрос (форма обратной связи или вызов редактора) занял воркер на срок вплоть до max_execution_time (окно инцидента почти ровно 300 секунд), параллельный фоновый трафик редактора (автосохранение, heartbeat) съел оставшиеся воркеры - и любой новый запрос вставал в очередь к nginx, получая 504.

sed -i 's/^pm.max_children = 5/pm.max_children = 12/' \
  /opt/php84/etc/php-fpm.d/pool.d/enclose-city.example.conf
/etc/init.d/php-fpm84 configtest   # php-fpm84 не в PATH - проверка синтаксиса только так
systemctl reload php-fpm84

От разового фикса - к аудиту всего флота

Логичный следующий шаг: та же ли беда на остальных серверах? Прогнал по всем пяти серверам поиск всех конфигов пулов под всеми версиями PHP. Картина: на двух серверах лимит уже подняли раньше в рамках этой же недели, но ещё на трёх сайтах стоял дефолт в 5 воркеров - в том числе на сайте с 120 000+ запросов в день, в 8 с лишним раз больше трафика, чем у сайта, где как раз и случился сегодняшний инцидент. Тот же риск, но с гораздо более высокой вероятностью реализации.

Отдельно проверил сайты, которые вообще не используют PHP-FPM, а идут через связку nginx → Apache/mod_php:

grep -E "proxy_pass|fastcgi_pass" /etc/nginx/vhosts/.../site.conf
# proxy_pass http://127.0.0.1:8082;
grep MaxRequestWorkers /etc/apache2-isp-php84/mods-available/mpm_prefork.conf
# MaxRequestWorkers 150   ← общий дефолт ISPmanager, запас большой, не проблема

Фикс - но с проверкой памяти, в отличие от Дня 2

Прежде чем поднимать лимит фронтально до 20 везде, вспомнил собственное же решение с Дня 2 - там отказал именно из-за нехватки RAM. Проверил доступную память на всех затронутых серверах перед фиксом:

free -h
#            total   used   free  available
# сервер A:  7.8Gi   1.4Gi  1.1Gi   6.3Gi
# сервер B:  7.8Gi   2.4Gi  676Mi   5.3Gi
# сервер C:  7.8Gi   1.7Gi  1.4Gi   6.0Gi

5-6 ГБ свободно на каждом - контекст принципиально другой, чем на перегруженном сервере в День 2, где ещё и активная атака шла одновременно. Поднял до 20 везде, где стоял дефолт 5:

sed -i 's/^pm.max_children = 5/pm.max_children = 20/' "$conf"
systemctl reload php-fpm84

Итог по флоту: минимальный лимит воркеров на любом php-fpm-сайте - 12, у всех остальных - 20.

Итог

За неделю закрыл три независимых инцидента подряд на одном и том же домене, довёл до конца ранее незакрытую проблему с нагрузкой на БД, нашёл и обезвредил реального атакующего (не спутав его с админом клиента), развернул три постоянных предохранителя (защита от разрастания логов, защита от разрастания кэша, набор fail2ban-джейлов) на весь флот из пяти серверов, а не только на тот сайт, где проблема впервые проявилась. Всё, что перенос "бесплатными руками" хостинга оставил не перенесённым или не проверенным, - настройки PHP-FPM, nginx, забытые дебаг-флаги - было найдено и закрыто одно за другим.

Выводы

  1. pm.max_children = 5 (дефолт ISPmanager) - тикающая бомба, а не безопасное значение по умолчанию. За неделю один и тот же паттерн - маленький пул плюс что-то, что удерживает воркер дольше обычного - воспроизвёлся трижды на разных сайтах разных серверов.
  2. Одинаковый симптом не значит одинаковая причина. Три инцидента на одном и том же домене подряд ("сайт не работает") оказались тремя независимыми причинами: нехватка воркеров, XML-RPC-атака, переполнение диска бинлогами MySQL. Диагностику нужно проводить заново каждый раз, не подставляя вчерашний ответ.
  3. Поднимать лимит воркеров - не универсальный фикс. На перегруженном сервере под активной атакой это может увести систему в своп и OOM-killer вместо контролируемых 502. Перед поднятием - обязательно проверять доступную память и то, не является ли текущая перегрузка симптомом атаки, которую надо сначала устранить, а не заливать лимитами.
  4. Кластер ошибок с разными URL от одного источника в узком временном окне - почти всегда признак исчерпания общего ресурса (пул воркеров, соединения к БД), а не бага конкретной страницы.
  5. На ISPmanager с несколькими версиями PHP реальные конфиги php-fpm-пулов лежат не там, где их ищут по умолчанию, а бинарник интерпретатора может не быть в PATH - об этом стоит помнить при аудите лимитов конкурентности, и проверять не только php-fpm, но и Apache MPM для сайтов, идущих через связку nginx → Apache.
  6. Кэш-плагины сами по себе не убирают логически устаревшие файлы с диска - только перестают их использовать. Без внешнего предохранителя это тихо копит десятки гигабайт годами.
  7. DELETE не возвращает место на диск при пофайловом хранении InnoDB - только освобождает пространство внутри файла таблицы. Для реального возврата диска после массовой чистки - пересборка таблицы, дешевле по временному месту, чем полная оптимизация.
  8. Правка кода без перезагрузки PHP-FPM может не сработать вообще - механизм автоматического обнаружения изменений штатно должен подхватывать правки за секунды, но на практике долгоживущие воркеры иногда продолжают исполнять старый байткод. Если фикс "должен был сработать, но не работает" - первое, что стоит попробовать, это перезагрузка PHP-FPM.
  9. Бесплатный перенос от техподдержки хостинга переносит данные, а не конфигурацию. Ни один из описанных выше багов не был новым - все они существовали ещё на старом хостинге в каком-то виде, но всплыли или обострились именно после переезда, потому что никто не сверил лимиты, дебаг-флаги и nginx-конфиги построчно между старой и новой площадкой.

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

Оставьте комментарий