Transparent Huge Pages: механизм, который ускоряет всех и тормозит именно вашу базу
Если вы когда-нибудь ставили MySQL или PostgreSQL на новый сервер и в первой же статье по тюнингу видели строчку «отключите THP» — это не карго-культ. Transparent Huge Pages реально ускоряет большинство процессов, потому что снижает нагрузку на аппаратный кеш трансляции адресов. Но для процессов с непредсказуемым паттерном доступа к памяти — а СУБД устроены именно так — та же оптимизация оборачивается редкими, но заметными паузами. Разберёмся, как это устроено внутри и что с этим делать на практике.
Содержание
- Зачем процессору вообще нужны большие страницы
- Explicit Huge Pages и Transparent Huge Pages — разные механизмы
- khugepaged: кто в фоне сшивает страницы
- Почему это особенно больно именно базам данных
- Почему производители СУБД в один голос советуют выключать THP
- Как проверить и настроить THP в Linux
- Практические следствия: что делать на реальном сервере
Зачем процессору вообще нужны большие страницы
Linux управляет физической памятью постранично, и по умолчанию размер страницы на x86-64 — 4 КБ. Когда процесс обращается к виртуальному адресу, процессор должен превратить его в физический — для этого он ходит по таблице страниц (page table), многоуровневой структуре, которая описывает соответствие виртуальных и физических страниц.
Ходить по таблице страниц на каждое обращение к памяти было бы катастрофически медленно, поэтому в процессоре есть TLB (Translation Lookaside Buffer) — небольшой кеш уже посчитанных трансляций «виртуальный адрес → физический». Записей в TLB мало (десятки-сотни), и каждая покрывает ровно одну страницу. Если страница — 4 КБ, а у процесса резидентно много гигабайт памяти, реальный TLB не может покрыть всё это без промахов, и процессору приходится регулярно откатываться к полному обходу таблицы страниц — а это несколько дополнительных обращений к памяти на каждый такой промах.
Huge Page размером 2 МБ (на x86-64; есть ещё 1 ГБ page для совсем больших объёмов) закрывает эту проблему арифметически: одна запись TLB покрывает не 4 КБ, а 2 МБ — в 512 раз больше. Тот же объём резидентной памяти покрывается на порядки меньшим числом записей TLB, промахи становятся реже, а сами обходы таблицы страниц — короче, потому что у huge page меньше уровней трансляции. Для процессов, которые линейно и предсказуемо работают с большим heap — научные вычисления, JVM с большой кучей, in-memory кеши с последовательным сканированием — выигрыш ощутимый и стабильный.
Explicit Huge Pages и Transparent Huge Pages — разные механизмы
До THP в Linux уже была поддержка больших страниц — hugetlbfs, explicit huge pages. Это ручной механизм: администратор заранее резервирует пул из N страниц по 2 МБ через vm.nr_hugepages в sysctl, эти страницы выделяются из общего пула памяти и становятся недоступны для обычных 4 КБ аллокаций, а приложение должно явно запрашивать память именно из этого пула (через mmap с флагом MAP_HUGETLB). Это надёжно и предсказуемо, но неудобно: нужно менять конфигурацию запуска приложения, заранее знать объём резерва и мириться с тем, что зарезервированный пул нельзя гибко переиспользовать под другие нужды.
Transparent Huge Pages решает ту же задачу прозрачно — без единой строчки в коде приложения. Ядро само, в фоне, следит за анонимной памятью процессов (стек, куча, память после malloc/mmap без файловой подложки) и там, где возможно, объединяет соседние 4 КБ страницы в 2 МБ, если они физически расположены последовательно и это не мешает работе процесса. Приложение ничего не замечает — оно как обращалось к своим виртуальным адресам, так и обращается, просто трансляция адресов под капотом стала эффективнее.
Разница принципиальная: explicit huge pages — это осознанный выбор администратора под конкретную нагрузку. THP — это эвристика ядра, которая пытается угадать, что «объединить страницы — обычно хорошая идея», и работает вслепую, не зная, что именно делает процесс с этой памятью.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверkhugepaged: кто в фоне сшивает страницы
Сама механика объединения страниц реализована в отдельном ядерном потоке — khugepaged. Его можно увидеть в выводе ps aux как процесс с именем [khugepaged] в квадратных скобках — это kernel thread, а не обычный процесс.
khugepaged периодически просыпается (интервал задаётся через /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs) и сканирует анонимную память процессов в поисках подряд идущих 4 КБ страниц, которые в сумме составляют выровненный блок в 2 МБ. Если блок найден и все его страницы годятся для объединения (не выгружены в swap, не защищены особым образом), khugepaged выполняет collapse: копирует содержимое в новый смежный блок памяти размером 2 МБ и переключает таблицу страниц процесса на использование одной huge page вместо пачки мелких.
Проблема в том, что подходящий непрерывный блок физической памяти под 2 МБ страницу далеко не всегда есть в наличии — память фрагментируется естественным образом за время работы сервера. Чтобы найти или создать такой блок, ядру приходится выполнять компакцию памяти (memory compaction) — перемещать занятые страницы, чтобы «сшить» непрерывную область нужного размера из разрозненных кусков. Операция не бесплатная: блокировки, копирование данных, процессорное время — именно она источник «необъяснимых» пауз.
Компакция бывает фоновой (её запускает khugepaged) — она размазана во времени и не блокирует конкретный запрос. А есть синхронная компакция прямо в пути аллокации (direct compaction): процессу здесь и сейчас понадобилась память, свободного блока нет, и ядро решает быстро расчистить место под 2 МБ прежде чем отдать память — вот это уже блокирует запросивший поток и может стоить заметного времени.
Почему это особенно больно именно базам данных
Для процесса с предсказуемым, последовательным доступом к памяти THP почти всегда чистый выигрыш: страницы легко собираются в huge pages, промахи TLB падают, компакция требуется редко, потому что память выделяется и используется предсказуемыми блоками.
СУБД устроены иначе. Ядро базы данных — будь то буферный пул PostgreSQL/MySQL или кеш страниц Redis — работает с рабочим набором, который постоянно меняется частями: одни страницы вытесняются, другие подгружаются, третьи модифицируются точечно под конкретные строки. Доступ к памяти внутри этого набора хаотичен по своей природе — именно так и должна работать кеш-структура, обслуживающая случайные запросы. Из этого вытекает три конкретных механизма боли.
Постоянные split и collapse. Как только запись затрагивает часть страницы, объединённой в huge page, несовместимым с её текущим состоянием способом (например, нужен отдельный copy-on-write только для части региона), ядро вынуждено расщепить huge page обратно на 4 КБ страницы. Затем khugepaged через какое-то время снова попробует их собрать. При активной, постоянно меняющейся памяти базы этот цикл split → collapse идёт непрерывно — лишняя работа ядра сверх той, что была бы при изначально мелких страницах.
Дорогой copy-on-write при fork(). PostgreSQL и Redis используют модель, в которой отдельный процесс форкается от родителя — например, чтобы снять снапшот памяти для бэкапа (Redis BGSAVE) или обслужить клиентское соединение (backend-процессы PostgreSQL). При fork() страницы родителя становятся copy-on-write, и при первой записи ядро должно скопировать затронутую страницу. Обычную 4 КБ копировать дёшево, а huge page на 2 МБ придётся скопировать целиком, даже если изменился один байт — не случайно Redis в своей документации прямо связывает задержки при BGSAVE с включённым THP.
Синхронная компакция в момент, когда критично не тормозить. База данных чувствительна к задержкам иначе, чем batch-обработка: клиенту не важно, что средняя пропускная способность высокая, если конкретно его запрос завис на время, пока ядро искало свободный блок памяти под очередную huge page. Такие паузы редкие, нерегулярные и плохо диагностируются обычным мониторингом — поэтому THP так долго остаётся «невидимым подозреваемым» при поиске причины всплесков latency.
Похожая история — с фрагментацией памяти в целом; у нас есть отдельный разбор про фрагментацию памяти в долгоживущем процессе. А конкретный пример того, как включённый THP на практике удваивал задержки реальной базы, разобран в статье Transparent Huge Pages удвоили задержки базы.
Почему производители СУБД в один голос советуют выключать THP
Если походить по документации и best practices крупных СУБД (реляционных баз, кеширующих хранилищ, документных баз), почти везде в разделе про производительность на Linux встречается одна и та же рекомендация: отключить THP или перевести его в режим madvise. Логика у всех примерно одинаковая:
- База сама управляет своей памятью. У серьёзной СУБД уже есть собственный менеджер буферного кеша, который знает, какие страницы горячие, а какие можно вытеснить. Ей не нужна ещё одна эвристика поверх — та, что ничего не знает про структуру данных внутри базы и принимает решения об объединении страниц вслепую.
- Предсказуемость важнее средней производительности. Для батчевой аналитики небольшой прирост throughput за счёт THP — приятный бонус. Для транзакционной базы редкая, но многомиллисекундная пауза — куда более заметная проблема, чем небольшая просадка средней скорости.
- Своя память, свои huge pages. Многие СУБД поддерживают явную работу с huge pages (hugetlbfs) как контролируемую альтернативу — администратор сам решает, сколько памяти выделить и когда, вместо того чтобы ядро гадало это в фоне. У нас есть отдельная статья о том, почему база без huge pages ощутимо медленнее — это ровно про explicit-подход, который часто выбирают вместо THP.
Это не значит, что THP — плохая технология в целом: она отлично работает для огромного количества нагрузок, не связанных с базами данных. Поэтому современные дистрибутивы Linux не отключают THP глобально, а дают гранулярный контроль — механизм можно оставить включённым для системы и точечно исключить из него конкретный процесс.
Как проверить и настроить THP в Linux
Текущее состояние смотрится через файлы в /sys/kernel/mm/transparent_hugepage/:
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never -- квадратные скобки показывают активный режим
cat /sys/kernel/mm/transparent_hugepage/defrag
# always defer defer+madvise [madvise] never
Три режима enabled: always — ядро автоматически собирает huge pages для всей анонимной памяти всех процессов; madvise — huge pages используются только там, где приложение само попросило об этом вызовом madvise(MADV_HUGEPAGE); never — THP полностью отключён, khugepaged не работает.
Отдельно регулируется defrag — насколько агрессивно ядро готово тратить время на компакцию памяти ради huge page прямо в момент аллокации: always включает синхронную компакцию для любой памяти (самый рискованный по задержкам вариант), defer+madvise/madvise — только для областей, помеченных MADV_HUGEPAGE, never — синхронная компакция ради THP не выполняется вовсе.
Изменить режим временно (до перезагрузки):
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo defer+madvise > /sys/kernel/mm/transparent_hugepage/defrag
Полное отключение (классическая рекомендация для выделенного под СУБД сервера):
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
Эти команды не переживают перезагрузку. Чтобы закрепить настройку постоянно, проще всего создать systemd-юнит, который выполняется на старте:
# /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable Transparent Huge Pages
After=sysinit.target local-fs.target
Before=mysql.service postgresql.service redis.service
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag'
[Install]
WantedBy=basic.target
systemctl daemon-reload
systemctl enable --now disable-thp.service
Строку Before= стоит подстроить под реально запущенные на сервере СУБД — важно, чтобы THP отключался до старта процесса базы, иначе уже выделенная память могла успеть собраться в huge pages.
Наблюдать за тем, что происходит с THP на живой системе, можно через /proc/vmstat:
grep -E 'thp_|compact_stall' /proc/vmstat
Интересны прежде всего thp_collapse_alloc (сколько раз khugepaged собрал huge page), thp_split_page (сколько раз её пришлось расщепить обратно) и compact_stall (сколько раз процесс блокировался на синхронной компакции). Если compact_stall заметно растёт вместе с нагрузкой на базу — прямой сигнал, что THP вносит вклад в задержки именно на вашей системе, а не просто теоретическая возможность из документации.
Практические следствия: что делать на реальном сервере
Универсального «правильного» значения нет — есть контекст сервера:
- Выделенный сервер под одну СУБД. Самый однозначный случай: отключайте THP полностью (
never/never) и, если нагрузка того требует, переходите на explicit huge pages черезvm.nr_hugepages. Конкурирующих процессов, которым THP мог бы помочь, здесь нет, поэтому выключение — чистый плюс без компромиссов. - VPS общего назначения с несколькими сервисами. Если база соседствует с веб-приложением и фоновыми задачами, полное отключение THP может немного снизить эффективность памяти для остальных процессов. Разумный компромисс —
madvise: THP работает только там, где приложение попросило, а буферный пул СУБД, который ничего не запрашивает черезMADV_HUGEPAGE, под раздачу не попадает. - Контейнеризованные базы. Настройка THP — параметр хоста, а не контейнера: изменить его изнутри контейнера обычно нельзя, нужно настраивать на ноде. Это стоит держать в голове при переезде базы в контейнер — унаследованная настройка может незаметно потеряться.
- Не меняйте настройку вслепую. Снимите baseline по
compact_stallиthp_split_pageв/proc/vmstat, отключите THP на тестовом контуре с похожей нагрузкой и сравните задержки под нагрузочным тестом. Точная величина эффекта всегда зависит от конкретной базы и профиля запросов — обещать проценты ускорения без замера на своей системе было бы нечестно. - Мониторьте постоянно. Держите
compact_stallв общей системе мониторинга рядом с метриками самой базы. Если у вас уже настроена связка для баз данных, сверьтесь с подходом из статьи про тюнинг PostgreSQL на VPS — THP стоит рассматривать как пункт общего чек-листа, а не изолированную настройку.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужно ли отключать THP на любом сервере, где просто установлена СУБД?
Не обязательно автоматически, но для серверов, где база — основная нагрузка, это одна из первых рекомендаций в большинстве руководств по продакшн-настройке. Если сервер смешанный, разумнее начать с madvise, а не с полного отключения.
Если я отключу THP, база сразу станет быстрее?
Не обязательно заметно и не обязательно сразу — эффект зависит от того, насколько часто на вашей системе реально происходили split/collapse и синхронная компакция. Прежде чем менять настройку в проде, свериться с метриками /proc/vmstat, как описано выше.
THP и explicit huge pages — взаимоисключающие вещи?
По сути да для одной и той же области памяти: если вы вручную зарезервировали пул через vm.nr_hugepages и настроили СУБД использовать именно его, THP этой памяти уже не касается. THP при этом можно оставить отключённым, чтобы избежать накладных расходов на остальную память процесса.
Как понять, что именно THP, а не что-то другое, вызывает паузы у моей базы?
Сопоставьте по времени рост compact_stall в /proc/vmstat с моментами, когда в логах или трассировке запросов видны аномальные задержки. Если пики совпадают регулярно — сильный аргумент в пользу THP. Если совпадений нет, дело, вероятно, в чём-то другом (диск, блокировки, сеть).
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →