Продолжаю делиться своими размышлениями в поисках оптимального решения разных проблем производительности ИТ-систем в рамках рубрики «Мысли вслух». Напомню, размышления пока сугубо теоретические и практических подтверждений могут как иметь, так и не иметь. Но поскольку исследования проводятся, часть из них, несомненно, войдёт в будущие практические решения, часть так и останется теорией.
Хочу поднять проблему как объективно посчитать размер потребляемой оперативной памяти конкретным запросом в PostgreSQL. И предлагаю использовать для этого автоматическое нагрузочное тестирование. Такая вот сегодня постановка задачи.
В ходе недавних исследований я столкнулся с явной нехваткой стандартных инструментов для мониторинга потребления оперативной памяти запросами. На тестовой среде я моделировал сценарии с избыточным свопированием, при которых данные выгружаются из оперативной памяти на диск для освобождения ресурсов для новых процессов и данных. Это приводило к значительной деградации производительности и даже к полной остановке сервера из-за нехватки дискового пространства для свопа, что в некоторых случаях составляло сотни гигабайт.
При анализе данных мониторинга производительности я не обнаружил параметров, которые бы явно коррелировали с объемом потребляемой памяти. Это побудило меня поднять вопрос о том, как можно улучшить идентификацию "тяжелых" и "опасных" запросов для системы на основе данных мониторинга. В статье я предлагаю одно из возможных решений этой актуальной практической задачи и призываю читателей поделиться своими наблюдениями, идеями и опытом в этой области.
Если актуальная информация по данному вопросу отсутствует, но существует потенциал для решения, я предлагаю рассмотреть возможность моего финансирования исследований, оформленных в виде короткой статьи с примерами, которые можно проверить на практике.
Теперь перейду к подробному описанию ситуации, которая подтолкнула меня к выводу о том, что игнорировать данную проблему недопустимо и необходимо разработать эффективное решение.
Описание идеи
Недавно наша команда провела нагрузочные тесты распределённого вычислительного кластера, и результаты оказались весьма обнадеживающими. Однако, когда я приступил к анализу данных мониторинга производительности, то столкнулся с несколькими, на мой взгляд, критически важными проблемами.
Во время нагрузочных тестов, при увеличении интенсивности генерации запросов за счет роста числа виртуальных пользователей, мы наблюдали активное свопирование, что, в свою очередь, приводило к резкому ухудшению производительности.
![](https://habrastorage.org/getpro/habr/upload_files/9d8/38c/e8a/9d838ce8a1587bcbdafdba8c320b77b7.png)
Как только счетчик Swap Usage начал расти (выделен желтым), сразу видна деградация по остальным счетчикам: CPU, Кол-во запросов в секунду, очереди к диску.
Первоначально мне показалось, что запросы потребляют слишком много оперативной памяти, и я решил более внимательно изучить их. Однако, проанализировав данные мониторинга, включая метрики API от PostgreSQL и Linux, я не обнаружил явной корреляции между запросами и поведением системы. Все сценарии и запросы были известны, и система не была загружена другими процессами. Как человек, знакомый с экспериментальной физикой, я стремился к более четким зависимостям между экспериментом и его результатами. С практической точки зрения, мне хотелось научиться предсказывать поведение системы на основе данных мониторинга, поскольку полноценные нагрузочные тесты в реальных высоконагруженных системах крайне редки, если не сказать невозможны.
При более детальном рассмотрении данных мониторинга я выделил единственного кандидата на роль "виновника" проблем в плане выполнения запроса.
![](https://habrastorage.org/getpro/habr/upload_files/50b/e24/993/50be24993046c54ac68bcfbb8488b436.png)
Как видно, используется хэш-соединение, которое, безусловно, потребляет оперативную память (но есть вероятность, что необходимый план выполнения не попал в мониторинг). Однако значение в 1,4 ГБ не соответствовало моим вычислениям. Можно было бы предположить, что вся память, выделенная под запрос, не освобождается до окончания транзакции, но это не имеет смысла. Часть памяти, возможно, не освобождается, но явно не та большая доля, которая выделяется для ускорения расчетов и хранения хэш-агрегатов. Даже в этом случае общая картина потребления памяти и запуск процессов активного свопирования не складывалась.
В результате всех этих раздумий у меня возник план практического решения этой задачи. Предположим, что мы не можем точно рассчитать объем реально потребляемой памяти и предсказать, когда она закончится, и начнется свопирование. Однако, возможно, мы можем автоматизировать этот процесс и научиться предугадывать его.
Идея заключается в следующем: с помощью мониторинга выявлять потенциально опасные запросы и затем автоматически воспроизводить их на тестовом стенде, где отсутствуют внешние факторы. Это позволит провести точные исследования и, что важно, сделать это полностью автоматизировано, своего рода нагрузочные тесты.
Формулируем более конкретную задачу: необходимо понять потребление памяти запросом. Зачем нам это? Это необходимо, чтобы знать, когда память закончится и начнутся деструктивные процессы деградации (или, в худшем случае, полная остановка системы из-за исчерпания физической памяти). Можно также задать вопрос: сколько одновременно может выполняться "опасных" запросов на сервере, прежде чем начнется свопирование? Это значение можно условно назвать "Счетчиком оценки памяти запроса Сердюка". Хотя это и шутка, но идея в том, что этот счетчик имеет конкретный физический и практический смысл.
Разумеется, есть нюансы. Эксперимент было бы некорректно проводить, если бы в PostgreSQL были механизмы оптимизации, использующие результаты ранее выполненных запросов. Проведем мысленный эксперимент: предположим, мы выполняем тяжелый запрос и получаем набор данных. Если через час мы запускаем тот же запрос, и в БД ничего не изменилось, мы можем моментально вернуть результат. Однако подобные практики оптимизации не имеют ничего общего с реальной жизнью, поэтому мы можем уверенно утверждать, что выполняя один и тот же запрос одновременно, он будет выполняться каждый раз с самого начала до конца. Единственное, что меняется, — это прогревка страниц памяти, и они после первого запроса попадут с диска в память, но это в целом не имеет значения.
Важно также понимать, что тестовый сервер может отличаться от рабочего. Более того, он и должен отличаться, поскольку это дорого и бессмысленно. Например, если на рабочем сервере установлен терабайт оперативной памяти, то в некоторых случаях потребуется запустить много запросов одновременно, чтобы достичь свопирования. Мы можем уменьшить объем оперативной памяти на тестовом стенде и затем аппроксимировать результаты.
Реализация идеи
Теперь перейдем к практической части и рассмотрим, каким образом можно реализовать эту идею. К счастью, у нашей компании уже имеются все наработки. В первую очередь нужен мониторинг для идентификации потенциально опасных запросов по их сигнатуре. Также у нас готова технология прокси, которая, хотя и вызывает небольшую просадку производительности (несколько процентов), может быть включена автоматически и опционально для захвата необходимых запросов, после чего будет отключена.
Прокси не просто фиксирует запросы, но и записывает все временные таблицы, используемые в запросе, что позволяет сохранить все сущности, необходимые для полного воспроизведения. Затем мы восстанавливаем бэкап рабочей базы данных на тестовом стенде на момент выполнения опасного запроса. После этого по заранее установленному графику запускаем N запросов на тестовом сервере (как в транзакциях, так и без них, по выбору). Далее мы постепенно увеличиваем количество запросов: N+x, N+2x и так далее, до тех пор, пока не начнется свопирование.
Мониторинг на тестовом стенде будет непрерывно отслеживать производительность и остановит процесс нагрузочного тестирования на значении N+kx, которое и станет искомым счетчиком предельного потребления памяти (Сердюка). Исходя из объема оперативной памяти на стенде, можно будет приблизительно разделить это значение на показания счетчика и получить оценку того, сколько оперативной памяти потребляет каждый запрос. Однако стоит отметить, что это значение является приблизительным, так как запрос может изменить план оптимизации и перейти в режим вложенных циклов с повышенным потреблением ЦПУ вместо памяти. Поэтому лучше рассматривать это в более широком смысле: при выполнении более чем N+kx запросов одновременно на сервере с объемом памяти Y происходит свопирование.
В результате мы получим список опасных запросов, которые могут привести к деградации производительности, без значительных трудозатрат. Кроме того, аналогичным образом можно оценивать утилизацию ЦПУ. Конечно, возможно провести все исследования вручную, но это требует значительных временных затрат, компетенции и аккуратности в исследовательской работе. В критических ситуациях, когда время идет на часы, быстро решить проблему не удается. Главное, что с помощью этих механизмов мы сможем предсказывать и предотвращать катастрофические проблемы в ИТ-системах.
Данный пример автоматизации представляет собой автоматизированный нагрузочный тест. Однако полной автоматизации в этой области мне не встречалось. Мне интересно мнение читателей: насколько это востребовано? Если это действительно интересно, то, полагаю, в течение полугода мы можем запустить эту инициативу в рамках нашего продукта мониторинга производительности.