![](https://habrastorage.org/getpro/habr/upload_files/053/788/4de/0537884de45af925a68b4c72558f3d0c.png)
После ухода западных вендоров в 2022 году российские компании столкнулись с необходимостью массовой миграции с Oracle и MS SQL на PostgreSQL и другие открытые СУБД. Теперь к вопросам миграции приходится относиться ответственнее: самостоятельно оценивать риски и прогнозировать работу систем после переезда.
Как перфоманс-инженер, я часто сталкиваюсь с вопросами типа: справится ли PostgreSQL с текущей нагрузкой? Потребуется ли обновление железа? Какие проблемы могут возникнуть после перехода? К сожалению, готовых инструментов для оценки производительности СУБД на рынке фактически нет. Это заставило нас разработать собственную методологию тестирования, которая позволяет выявить потенциальные проблемы и точно оценить необходимые ресурсы.
В этой статье я поделюсь практическим опытом нагрузочного тестирования баз данных и расскажу об инструментах, которые мы используем для анализа производительности. Наш подход не требует существенных затрат и может быть адаптирован под задачи любой компании, планирующей миграцию на PostgreSQL.
Типичные проблемы при миграции СУБД
Миграция баз данных обычно проходит в четыре этапа:
Аудит текущей системы и выбор новой платформы.
Развертывание кластера и настройка резервного копирования.
Перенос данных и адаптация кода.
Тестирование и финальная оптимизация перед выходом в прод.
Первые этапы многократно опробованы на практике, для них существуют готовые инструменты. Например, ora2pg для миграции с Oracle на PostgreSQL, а вот оценка производительности — это самая непредсказуемая часть процесса.
Сложно заранее определить, какие запросы станут работать быстрее, а какие — медленнее. В одних случаях достаточно создать индекс или подкрутить планы запросов, в других — потребуется пересмотреть сайзинг оборудования. Без тщательного тестирования есть риск столкнуться с проблемами уже в продакшене, когда исправлять их будет намного дороже и больнее.
При миграции баз данных часто возникают три основные сложности.
Первая связана с нехваткой опытных специалистов по Postgres. Число миграций возросло, а рук и светлых голов больше не стало.
Вторая проблема — отсутствие тестового стенда. Качественное тестирование требует копии продакшен-окружения, но далеко не у всех компаний есть ресурсы для его развертывания.
Третья сложность возникает из-за изменения архитектуры при миграции на Postgres:
могут измениться связи между таблицами;
может потребоваться новая логика индексации;
может измениться поведение транзакций.
Раньше при миграции фокус был на сохранении функциональности, а производительность оценивали «на глазок». Теперь же вопрос тестирования производительности стал критически важным: если система после миграции начнет «тормозить», компания потеряет деньги и клиентов.
Предварительный этап тестирования: как подготовиться к оценке производительности СУБД
Перед началом нагрузочного тестирования мне было необходимо тщательно подготовить тестовый стенд и собрать ключевые метрики. На этом этапе я определил:
какой профиль нагрузки наиболее характерен для бизнеса;
какой объем данных и какие типы запросов будут использоваться;
какой инструмент тестирования подойдет для решения наших задач;
как интерпретировать собранные метрики.
Процесс подготовки включает аудит инфраструктуры и СУБД, анализ текущего профиля нагрузки и настройку инструментов тестирования. Без такого предварительного этапа легко пропустить узкие места системы.
Аудит производительности перед тестированием
Сбор статистики и метрик. На первом этапе проанализируем текущую производительность СУБД:
Какие запросы самые тяжелые?
Какова средняя и пиковая нагрузка?
Какие индексы используются, а какие — нет?
Подготовка тестового стенда. Мы приближаем тестовую среду к продакшену. В идеале, чтобы результаты были репрезентативными, они должны быть идентичными. Конечно, так получается не всегда. На практике различия в аппаратной конфигурации требуют дополнительной работы по калибровке тестового окружения. Измерения на разных конфигурациях позволяют определить коэффициенты пропорциональности и скорректировать результаты тестирования. Однако при значительных отличиях достоверность результатов снижается.
Критичные параметры:
количество ядер CPU, объем оперативной памяти, тип и скорость дисков;
конфигурация и версия PostgreSQL;
сетевые задержки.
Определение методики тестирования. На этом этапе мы выбираем наиболее подходящие сценарии тестирования.
Детали отличаются в зависимости от инфраструктуры клиента, но мы всегда проводим стресс-тесты для проверки работы СУБД на пределе возможностей, анализируем латентность запросов для оценки времени отклика на ключевые SQL-запросы и тестируем конкурентный доступ через симуляцию работы нескольких клиентов.
Выбор инструментов тестирования
Мы используем как готовые решения, так и самописные инструменты, позволяющие гибко адаптировать тестирование под реальный сценарий эксплуатации.
Популярные инструменты:
pgbench – синтетический тест, который проверяет работу транзакционного контура PostgreSQL;
sysbench – симуляция нагрузки на CPU, дисковую подсистему, память;
fio – тестирование дисковой производительности;
pg_stat_statements – анализ популярных и медленных запросов в PostgreSQL;
EXPLAIN ANALYZE – разбор планов выполнения запросов;
метрики pg_stat_bgwriter – отслеживание I/O нагрузки и кеширования.
Наши собственные инструменты:
гибкий SQL-генератор на Gatling для имитации реальной нагрузки;
агент сбора статистики, который анализирует поведение системы во время тестов.
Это позволяет нам точно измерить влияние изменений и оптимизировать систему перед релизом.
А вот готовых коробочных инструментов по сути и нет. Проблема в комплексной природе нагрузки на базу данных, которая выходит далеко за рамки простого подсчета транзакций в секунду. Нагрузка формируется из нескольких взаимосвязанных компонентов: типичных пользовательских сценариев с их показателями активности и количеством запросов, особенностей архитектуры решения (будь то монолит или микросервисный подход, распределенная система или единый сервер), различных версий и патчей СУБД (даже разные версии PostgreSQL могут демонстрировать различное поведение при идентичной нагрузке), а также специфики бизнес-логики, где финансовые транзакции существенно отличаются от аналитических запросов, а OLTP – от OLAP.
В реальности это означает, что каждая компания вынуждена адаптировать нагрузочное тестирование под свои конкретные условия. Универсальных инструментов не существует именно потому, что невозможно найти две идентичные системы с одинаковыми нагрузками, данными и пользовательскими паттернами, но есть эффективные методики.
Методики тестирования СУБД: что выбрать?
Подход к тестированию баз данных существенно изменился за последние годы. Если раньше тестирование БД проводилось в основном через пользовательский интерфейс, где нагрузка на базу данных оценивалась косвенно, то сейчас требуется более глубокий анализ производительности.
Комплексное нагрузочное тестирование
Бизнес больше не интересуют сугубо технические показатели вроде количества транзакций, латентности или операций в секунду. Современным компаниям нужны понятные метрики, например, максимальное число пользователей, способных одновременно работать в системе.
На первый взгляд может показаться, что самое простое решение — перенаправить поток данных с продакшена на тестовый стенд. Это кажется логичным: мы фиксируем реальные запросы пользователей, прогоняем их на тестовой среде и смотрим, как ведет себя база данных. Но на практике с таким подходом есть проблемы:
доступ к данным ограничен — авторизация, персональные данные, регуляторные ограничения;
реальная нагрузка может быть сложной для воспроизведения — на практике часто возникают баги, связанные с целостностью данных, последовательностью транзакций и прочими особенностями данных;
сетевой трафик сложно перенаправить, если речь идет о большой инфраструктуре;
подготовка стенда затруднена — необходимо создавать и поддерживать актуальные дампы базы данных для разных временных срезов. Перед каждым тестированием приходится восстанавливать чистое состояние базы из свежего дампа. Далеко не все готовы этим заниматься.
Часто в реальных условиях создать точную копию продакшен-среды оказывается слишком дорого, поэтому востребованы специальные методики нагрузочного тестирования под конкретные задачи.
Сейчас нагрузочное тестирование обычно проводится в идеальных условиях с использованием готовых скриптов со стороны приложения. Этот подход позволяет создать максимально приближенную к боевой нагрузку через генерацию трафика на СУБД со стороны приложения при эмуляции пользователей через пользовательские интерфейсы.
Тестирование имитирует реальные действия пользователей и обеспечивает высокую достоверность результатов. Однако разработка и поддержка таких тестов требует существенных ресурсов из-за необходимости эмулировать сложные пользовательские сценарии и поддерживать их актуальность при изменении интерфейсов.
Наши автоматизированные методы оценки производительности
Итак, стандартный подход через UI не дает полной картины, а классические тесты баз данных не учитывают бизнес-логику. Столкнувшись с задачей оценки производительности PostgreSQL в условиях ограниченных ресурсов, мы со временем выработали собственный гибридный метод тестирования.
Для этого:
использовали существующие инструменты не совсем по прямому назначению. Например, Gatling применили для SQL-запросов (обычно он используется для HTTP-тестов). pg_stat_statements использовали не только для анализа медленных запросов, но и для построения профиля нагрузки;
разработали собственные плагины для генерации нагрузки;
сфокусировались не на «идеальном» тесте, а на поиске конкретных узких мест.
Вместо использования синтетических данных мы протестировали выполнение запросов в реальной среде. Так, применив простые скрипты для эмуляции нагрузки в одном из проектов, мы смогли точно оценить разницу во времени выполнения и потреблении ресурсов между системами.
Обычно для сбора запросов используется логирование на стороне БД. Но в логах есть не вся информация, необходимая для полноценных тестов. Поэтому мы написали свой инструмент, который фиксировал запросы на уровне приложения. Это позволяет тестировать СУБД приближенно к боевым условиям.
Ключевое преимущество нашего подхода заключается в том, что мы не создаем искусственную нагрузку, а переносим реальный трафик на тестовый стенд. Такой метод обеспечивает объективную оценку способности базы данных справляться с нагрузкой на имеющемся железе.
Наша методика тестирования СУБД: минимальные ресурсы, максимальная точность
Наш подход к тестированию отличается высокой эффективностью при относительно небольших затратах. Ключевое преимущество в том, что мы не создаем искусственную нагрузку, а переносим реальный трафик на тестовый стенд. Мы эмулируем реальные бизнес-операции через интерфейсы приложения, при помощи самописных плагинов, для тестирования конкретных метрик, которые нас интересуют.
Этап 1: Сбор статистики
Анализ начинается с детального изучения профилей нагрузки. Мы собираем статистику из работающей PostgreSQL через pg_stat_statements, проводим глубокий анализ планов выполнения запросов с помощью EXPLAIN ANALYZE и формируем комплексный тестовый набор типовых сценариев, охватывающий все основные операции: SELECT, INSERT, UPDATE и DELETE.
![](https://habrastorage.org/getpro/habr/upload_files/e98/9f5/356/e989f5356707e0b6e4e8951f1deef995.png)
Пример анализа производительности запроса
Если Postgres выполняет Seq Scan вместо Index Scan, то это сигнал, что можно ускорить запрос с помощью индекса.
Мы собираем профиль нагрузки. Это набор данных, отражающих реальную работу базы данных. Для корректного тестирования нам нужна следующая информация: полный текст SQL-запроса, длительность выполнения запроса, частота его выполнения и конкретные параметры, с которыми запрос выполнялся (bind variables).
Эти данные мы берем из:
системных представлений — pg_stat_statements в PostgreSQL, V$SQL в Oracle;
логов базы данных — например, логирование log_statement и log_duration в PostgreSQL;
мониторинговых систем — например, OpenTelemetry, Dynatrace, AppDynamics.
Однако есть проблема: стандартные механизмы логирования агрегируют данные, и мы не видим реальной нагрузки в динамике. Например, представления pg_stat_statements показывают среднее время выполнения запроса, но не учитывают пиковые нагрузки, кэширование и колебания в производительности.
Мы написали специализированный парсер логов, который позволяет:
разделять запросы по времени их выполнения, учитывая загруженность системы в разное время дня;
фиксировать реальные параметры каждого запроса, а не работать с усредненными значениями;
сохранять всю информацию в удобную табличку, с которой можно работать через SQL и строить графики.
Этап 2: Генерация тестовой нагрузки
Получив реальный профиль запросов, нужно воспроизвести его на новой СУБД, причем так, чтобы сохранить их характер: исходный порядок и частоту, суточные колебания нагрузки, эффекты кэширования и сетевые задержки.
Для решения этой задачи мы разработали гибридный подход. В его основе лежит использование Gatling как инструмента для генерации запросов в СУБД. Мы разделили поток запросов на временные интервалы для точной имитации реальной нагрузки и использовали фактические параметры SQL-запросов вместо синтетических данных.
Мы не просто запускаем тесты «в лоб», а используем умные эмуляторы нагрузки:
pgbench – для моделирования транзакционной активности;
pg_test_fsync – для тестирования скорости записи на диске;
наш собственный SQL-генератор, который подстраивается под реальный профиль нагрузки.
Так можно эмулировать сложные сценарии: параллельные запросы, случайные выборки и массовые обновления данных.
![](https://habrastorage.org/getpro/habr/upload_files/02a/e8e/efa/02ae8eefa93699922fede753e6e28aa2.png)
Пример нагрузки на обновление данных
Поскольку запросы можно параллелить, мы можем оценить влияние блокировок таблиц на производительность системы.
Наш метод основан на дискретизации потока запросов: мы разбиваем его на небольшие временные блоки продолжительностью около минуты. Внутри каждого блока усредняем частоту, но сохраняем текст, последовательность выполнения и параметры запросов, что обеспечивает точную имитацию поведения реальной системы. Разработанный нами агент автоматически связывает параметры с соответствующими запросами и сохраняет их вместе.
Это позволяет воспроизвести реальную нагрузку без искажений, сравнить время выполнения каждого запроса на старой и новой системе. При этом методика не привязана к конкретной СУБД и может применяться при любой миграции.
Этап 3: Анализ результатов и рекомендации
После выполнения тестов у нас есть фактические показатели производительности. Теперь важно их правильно интерпретировать.
Этот процесс начинается с построения графиков распределения, которые наглядно демонстрируют изменения в скорости выполнения запросов: какие стали работать быстрее, а какие замедлились.
Особое внимание уделяется анализу разброса значений. Мы детально исследуем случаи существенного изменения производительности, например, когда время выполнения запроса увеличивается от 100 до 500 миллисекунд, чтобы выявить причины таких отклонений. При группировке данных приоритет отдается критически важным запросам с высокой частотой выполнения.
На основе проведенного анализа мы формируем конкретные рекомендации по оптимизации системы. Они включают предложения по созданию дополнительных индексов, корректировке параметров PostgreSQL (таких как work_mem и shared_buffers), а также оценку необходимости модернизации аппаратного обеспечения, будь то увеличение вычислительной мощности или повышение производительности дисковой подсистемы.
Разбираем нюансы тестирования: от ошибок к выводам
Во время обкатки и использования этой методики мы сталкивались с рядом кейсов, о которых хочется поговорить отдельно: некоторые запросы выполнялись значительно медленнее ожидаемого, в других случаях система не использовала доступные индексы, что приводило к снижению общей производительности.
Кейс 1: Индекс не используется, хотя он есть
Один из ключевых запросов в новой базе работал в разы медленнее, чем в исходной системе. Анализ плана показал, что вместо Index Only Scan используется полное сканирование таблицы.
Как решали:
проверили наличие индекса – он был на месте;
попробовали пересобрать статистику (ANALYZE) – не помогло;
вспомнили, что Postgres использует карту видимости для Index Only Scan, а она формируется только после очистки таблицы (VACUUM);
запустили VACUUM – и сразу получили ожидаемый план с Index Scan.
Вывод: при развертывании базы из дампа необходимо запускать VACUUM ANALYZE, иначе Postgres может игнорировать индексы.
Кейс 2: Разница во времени выполнения запроса
Один из запросов в новой базе стабильно выполнялся на 30-40% дольше. При этом анализ плана показывал идентичное выполнение. Начали копать дальше.
Что проверяли:
измерения в Gatling – время измеряется корректно;
сняли GC-логи JVM – сборка мусора работает стабильно, без пауз;
проанализировали работу connection pool – задержек нет.
Нас осенило! Мы использовали разные источники данных: для новой системы измеряли время выполнения запросов на стороне клиента через Gatling, а для старой системы брали данные непосредственно из логов базы данных. Это приводило к искажению результатов сравнения. Решение: провели повторный тест, но теперь логировали выполнение запросов прямо в новой базе, а не на клиенте. Разница во времени исчезла!
Вывод: при тестировании важно сравнивать данные одинаковыми методами. Если замеры ведутся с разных точек (на клиенте vs в базе), могут появляться ложные отклонения.
Кейс 3: Разогрев данных или проблема «холодного кэша»
Некоторые запросы в тестах показывали разброс по времени выполнения. В одних случаях они работали быстро, в других – медленно.
Разбирались в причинах:
кэширование данных – часть запросов читала информацию из памяти, часть – с диска;
тестовый стенд не прогрет – при запуске базы кэш пуст, и первые запросы неизбежно работают медленнее.
Как решали:
перед запуском тестов делали разогрев базы, прогоняя ключевые запросы, чтобы заполнить кэш;
отфильтровали из анализа первые минуты теста, чтобы не учитывать холодный старт.
Вывод: При тестировании важно учитывать влияние кэширования, особенно если база разворачивается заново перед тестами.
Проблема с запросами
Мы изначально сфокусировались только на SELECT-запросах, потому что читать можно в любом порядке, а вот с INSERT, UPDATE и DELETE ситуация сложнее. Они требуют соблюдения последовательности операций. Иначе рискуем удалить данные, которые еще не вставили. Учитывая транзакционные зависимости, тестирование таких операций сложнее автоматизировать. А в OLTP-системах без их тестирования не обойтись.
Однако в нашей задаче 90% нагрузки было связано с читающими запросами, поэтому фокус на SELECT-ах был оправдан.
Вывод: если нужно тестировать DML-запросы, придется учитывать порядок их выполнения и состояние данных.
Заключение
Наша методика тестирования не нуждается в дорогих инструментах или лицензиях — она доступна большинству компаний. Для работы потребуются специалисты по администрированию СУБД, которые разбираются в настройке и оптимизации систем, а также разработчики с базовыми знаниями Java, Scala или других языков для анализа логов и баз данных. Ключевое значение имеет понимание методов нагрузочного тестирования и умение правильно интерпретировать полученные результаты. Те, кто уже работал с JMeter или Locust, быстро освоят нашу методологию благодаря сходству основных принципов.
Тестирование — обязательный этап миграции. Работоспособность базы после переноса не гарантирует ее эффективность при реальной нагрузке. Нагрузочное тестирование помогает оценить производительность новой СУБД на текущем оборудовании, предсказать пиковые нагрузки, найти потенциальные узкие места и предотвратить сбои в продакшене.
Для нагрузочного тестирования баз данных рекомендуется создать собственную тестовую среду под конкретный проект, поскольку универсальных решений не существует.