MAATRIX / Блог / Перенесли базу и получили тормоза: где потерялись индексы

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

MAATRIX

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

Первая реакция: винят железо и сеть

Сценарий типовой: перенесли PostgreSQL или MySQL на новый сервер — например, с VPS в России на выделенный сервер в Германии или США, — данные проверили, приложение подключилось, всё как будто работает. А потом начинают приходить жалобы: страницы грузятся дольше, отчёты, которые раньше строились за пару секунд, теперь висят по 20-30.

Первым делом смотрят на очевидное:

  • Диск. Проверяют iostat -x 1, смотрят на %util и await. Если новый сервер на NVMe, а старый был на SATA SSD, диск обычно не виноват — он быстрее.
  • Сеть. Меряют пинг до сервера, смотрят задержку между приложением и базой (ping, mtr). Если приложение и база в одном дата-центре или даже на одной машине, сеть тоже не при чём.
  • CPU и память. htop, free -h — часто видят, что ядер стало больше, а памяти минимум столько же, сколько было.

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

Реальная причина ищется не в top, а в плане выполнения самого запроса.

Реальная диагностика: EXPLAIN ANALYZE говорит правду

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

EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM orders 
WHERE customer_id = 48213 AND status = 'pending';

Смотрите на две вещи: тип узла верхнего уровня и на то, совпадает ли оценка планировщика (rows=...) с фактическим числом строк (actual rows=...).

Если на старом сервере план выглядел так:

Index Scan using idx_orders_customer_id on orders
  (cost=0.43..8.45 rows=12 width=124)
  (actual time=0.021..0.034 rows=11 loops=1)

а на новом — вот так:

Seq Scan on orders
  (cost=0.00..184532.00 rows=11 width=124)
  (actual time=0.045..842.113 rows=11 loops=1)

— это прямое доказательство: планировщик перестал использовать индекс и делает полное сканирование таблицы (Seq Scan) вместо точечного поиска по индексу (Index Scan). Разница во времени выполнения (actual time) в сотни раз — это не погрешность и не сеть, это буквально другой алгоритм работы с данными.

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

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

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

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

Причина №1: индексы не пережили перенос

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

Типичные способы переноса, которые калечат индексы:

Метод переносаЧто переноситсяРиск
pg_dump --data-only / mysqldump --no-create-infoтолько строки данныхиндексы и ограничения не создаются вообще
Копирование через COPY в CSV и обратнотолько содержимое таблицструктура (индексы, constraints) теряется полностью
ETL-инструмент "по умолчанию" (некоторые GUI-клиенты)данные, иногда без вторичных индексовзависит от инструмента, легко упустить
pg_dump полный (schema + data)схема, данные, индексы, constraintsбезопасно — но восстанавливаться должно тоже полностью
pg_basebackup / физическая репликациявсё, включая индексы, побайтовобезопасно, но не для миграции между разными версиями

Проверить, что реально приехало на новый сервер, легко: сравните список индексов на старом и новом сервере.

Для PostgreSQL на старом сервере:

SELECT indexname, indexdef 
FROM pg_indexes 
WHERE schemaname = 'public' 
ORDER BY tablename, indexname;

Выполните тот же запрос на новом сервере и сравните вывод (например, через diff двух файлов):

psql -h old-server -U app -d prod -c "\di+" > /tmp/idx_old.txt
psql -h new-server -U app -d prod -c "\di+" > /tmp/idx_new.txt
diff /tmp/idx_old.txt /tmp/idx_new.txt

Если в diff видно, что часть индексов (особенно составных и частичных — WHERE status = 'pending' и подобных) отсутствует на новом сервере, — вот и причина Seq Scan вместо Index Scan. Планировщику физически нечем воспользоваться.

Для MySQL аналогично: SHOW INDEX FROM orders; на обеих базах и сравнение вывода.

Причина №2: планировщик работает вслепую без ANALYZE

Вторая по частоте причина — даже более коварная, потому что индексы на месте, а план всё равно плохой. Дело в статистике планировщика.

PostgreSQL и MySQL принимают решение «использовать индекс или сканировать таблицу целиком» не наугад, а на основе статистики: сколько строк в таблице, какое распределение значений в столбцах, насколько избирательно условие WHERE. Эта статистика не переносится вместе с данными автоматически — она либо собирается заново командой ANALYZE, либо (при полном pg_dump/pg_restore с флагом статистики, что редкость) частично восстанавливается.

Если после pg_restore или ручного импорта дампа никто не выполнил ANALYZE, у PostgreSQL таблица считается пустой или с устаревшей статистикой — и планировщик может решить, что Seq Scan дешевле, чем Index Scan, просто потому что не знает реальный объём и распределение данных.

Проверить, актуальна ли статистика, можно так:

SELECT relname, n_live_tup, last_analyze, last_autoanalyze 
FROM pg_stat_user_tables 
WHERE relname = 'orders';

Если last_analyze и last_autoanalyze пустые (NULL) или относятся к моменту сразу после восстановления дампа (когда таблица была ещё формально пуста для планировщика), а n_live_tup не соответствует реальному числу строк — это прямое указание на проблему.

Быстрая проверка теории: выполните ANALYZE на конкретной таблице и повторите EXPLAIN ANALYZE того же запроса.

ANALYZE orders;

Если план тут же вернулся к Index Scan — вы нашли причину, и дальше нужно просто сделать это системно для всей базы (см. раздел с фиксом ниже).

Для MySQL эквивалент — ANALYZE TABLE orders;, а общая статистика по всем таблицам обновляется через mysqlcheck --analyze --all-databases.

Причина №3: разная конфигурация СУБД между серверами

Третья причина проявляется реже, но встречается — особенно если новый сервер настраивали "с нуля" вместо переноса конфига. Даже с индексами на месте и свежей статистикой планировщик может вести себя иначе из-за разных параметров памяти и стоимости операций.

Ключевые параметры PostgreSQL, которые влияют на выбор плана:

  • work_mem — сколько памяти планировщик готов выделить на одну операцию сортировки/хеширования до ухода на диск. Меньше, чем было — сортировки и джойны на диске, а не в памяти.
  • effective_cache_size — оценка того, сколько данных ОС/СУБД реально держит в кэше страниц. Занижена — планировщик недооценивает выгоду от Index Scan.
  • random_page_cost — во сколько раз случайное чтение страницы дороже последовательного. На NVMe разумно снижать (например, до 1.1), но если параметр остался дефолтным 4.0 (рассчитан на вращающиеся диски), планировщик будет искусственно завышать стоимость Index Scan.
  • shared_buffers — размер общего кэша страниц самой СУБД.

Сравнить текущие значения на старом и новом сервере:

SELECT name, setting, unit 
FROM pg_settings 
WHERE name IN ('work_mem', 'effective_cache_size', 'random_page_cost', 'shared_buffers');

Отдельно стоит проверить версию СУБД: SELECT version(); в PostgreSQL или SELECT VERSION(); в MySQL. Планировщик между мажорными версиями иногда меняет поведение по умолчанию (например, эвристики для параллельных запросов), и старый конфиг, скопированный без ревизии, может конфликтовать с новой версией.

Фикс: пересобираем индексы, статистику и параметры

Когда причина найдена, порядок действий обычно такой — сверху вниз, от самого вероятного к менее вероятному.

1. Восстановить недостающие индексы. Если по diff списка индексов видно, что чего-то не хватает, возьмите определение (indexdef) со старого сервера и выполните на новом:

CREATE INDEX CONCURRENTLY idx_orders_customer_id 
ON orders (customer_id) 
WHERE status = 'pending';

Флаг CONCURRENTLY важен на боевой базе — он не блокирует таблицу на запись во время построения индекса (правда, работает медленнее и не может выполняться внутри транзакции).

2. Обновить статистику планировщика по всей базе.

ANALYZE;

Без указания таблицы это пересоберёт статистику для всех таблиц в текущей базе. Если база большая и вы хотите приоритизировать конкретные горячие таблицы — сначала явно ANALYZE orders; ANALYZE order_items;, а фоновую полную ANALYZE; запустить отдельно, в период низкой нагрузки.

3. Сверить и подтянуть конфигурацию. Если random_page_cost остался дефолтным на NVMe-сервере или work_mem/effective_cache_size занижены относительно доступной памяти — поправьте в postgresql.conf и перезагрузите конфигурацию:

sudo systemctl reload postgresql

(параметры вроде shared_buffers требуют полного рестарта, а не reload).

4. Проверить автовакуум. Если autovacuum был отключён или настроен нетипично на старом сервере и это перенесли как есть, статистика может протухать снова и снова, даже после ручного ANALYZE. Убедитесь, что autovacuum = on и пороги (autovacuum_analyze_scale_factor) разумны для объёма ваших таблиц.

После каждого шага возвращайтесь к EXPLAIN ANALYZE того же проблемного запроса — это единственный надёжный способ убедиться, что план действительно изменился, а не просто "должен был".

Как переносить базу правильно с первого раза

Чтобы не повторять этот разбор при следующей миграции, стоит один раз выстроить процесс переноса так, чтобы индексы и статистика не терялись.

  • Используйте полный дамп схемы и данных, а не только данных. Для PostgreSQL — pg_dump -Fc mydb > mydb.dump (custom-формат, включает схему, данные, индексы, constraints), восстановление — pg_restore -d mydb --jobs=4 mydb.dump. Флаг --data-only оставьте только для узких случаев вроде синхронизации содержимого уже существующей идентичной схемы.
  • Сразу после восстановления — обязательный ANALYZE. Это должно быть последним шагом в вашем чек-листе миграции, не "как-нибудь потом". Если пишете скрипт переноса — добавьте ANALYZE; последней командой после pg_restore.
  • Сверяйте список индексов до и после тем же diff из раздела выше — это занимает минуту, а экономит часы разбора инцидента постфактум.
  • Переносите конфиг осознанно, а не "по умолчанию". Если старый сервер был настроен под конкретный объём памяти и диск, не запускайте новый с дефолтным postgresql.conf — либо перенесите настроенные параметры, либо прогоните конфигурацию заново через pgtune или аналогичный калькулятор под новое железо.
  • Для больших баз рассмотрите pg_basebackup вместо логического дампа — это физическая копия, включающая всё побайтово, без риска потерять индексы. Подходит, если версии PostgreSQL на старом и новом сервере совпадают.
  • Тестируйте на копии перед продакшеном. Разверните дамп на тестовом сервере, прогоните EXPLAIN ANALYZE для 5-10 самых тяжёлых запросов приложения — если планы совпадают со старым сервером, миграция прошла чисто.

Похожий разбор процесса миграции есть в статье про составление плана переноса на новый сервер, а более широкий обзор способов переноса баз между серверами — в материале про миграцию базы данных между серверами. Если хочется системно прогнать восстановление из бэкапа перед боевым переносом — пригодится разбор восстановления базы данных из бэкапа на практике. А если проблема оказалась именно в параметрах памяти планировщика — стоит заглянуть в материал про настройку и тюнинг PostgreSQL на VPS.

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

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

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

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

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

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

Обязательно ли делать ANALYZE вручную, если включён autovacuum?

Да, сразу после восстановления дампа — обязательно. Autovacuum сработает по своим порогам изменений данных, а сразу после pg_restore таблица для него может не выглядеть достаточно "изменившейся", чтобы триггернуть анализ немедленно. Ручной ANALYZE сразу после восстановления убирает это окно неопределённости.

Может ли дело быть одновременно в индексах и в статистике?

Да, это частая комбинация: если использовался --data-only дамп, вы теряете и индексы, и (естественно) их статистику тоже. Сначала восстановите индексы, потом обязательно выполните ANALYZE — одно без другого не даст полной картины планировщику.

Почему на старом сервере с теми же данными всё работало без явного ANALYZE?

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

Нужно ли что-то менять, если переносили через физическую репликацию (pg_basebackup), а не логический дамп?

Обычно нет — физическая копия переносит и индексы, и статистику побайтово в неизменном виде. Если тормоза всё равно есть, вероятнее причина №3 — разная конфигурация СУБД или отличающееся железо (например, другой тип диска).

Как быстро понять, какие именно запросы деградировали, если их много?

Включите логирование медленных запросов (log_min_duration_statement = 500 в postgresql.conf для запросов дольше 500 мс) и соберите список за час-два реальной нагрузки — дальше разбирайте EXPLAIN ANALYZE по каждому из топ-5-10, а не гадайте вслепую.

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

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

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