После прошлой статьи про Huge Pages мы получили несколько вопросов, в которых особо пытливые читатели возмущались отсутствием в статье реальных замеров «С HP» и «Без HP» и сравнением результатов. Материалов по этой теме в интернете крайне мало, а подобных замеров и подавно. Те же, что есть, все какие‑то синтетические (в основном переводы зарубежных источников), оторванные от реальности, от работы информационных систем и, тем более, от 1С систем. Поэтому мы, как дотошные исследователи, решили закрыть этот гештальт и проверить всё самим.

Проведем лабораторную работу.

Давайте еще раз про Huge Pages — что такое и зачем они нужны. Это важно для исследования.

Процессор работает с виртуальной памятью, а не с физической. Чтобы программа могла обратиться к данным, специальный блок внутри процессора TLB (Translation Lookaside Buffer) хранит соответствие виртуальных адресов физическим. Обычно память разбита на страницы по 4 килобайта. Когда страниц становится очень много (а при больших объёмах оперативки их миллионы), TLB перестаёт вмещать все записи. Процессор вынужден при каждом промахе обходить дерево таблиц страниц (Page Tables) в оперативной памяти. И это сразу долго. А в условиях вложенной виртуализации (Two‑Dimensional Paging), когда СУБД работает на виртуальной машине, нагрузка на процессор усиливается еще больше.

Huge Pages — это механизм, который позволяет использовать страницы по 2 мегабайта (или даже 1 гигабайт). Одна такая страница заменяет 512 обычных. Количество записей в TLB резко падает, промахов становится меньше, процессор тратит меньше тактов на поиск адресов. В теории это должно ускорять работу с памятью. Но насколько — вопрос.

Как мы тестировали

Если вы ожидаете найти в этом исследовании ответ на вопрос типа «А вот как ускорится/замедлится после включения HP расчет какой‑нибудь себестоимости в УПП», то нет, вам не сюда. Huge Pages использует в PG только раздел памяти shared buffers и больше ничего. Поэтому наша методика предполагает использование сугубо синтетических тестов с запросами SQL, использующих только shared buffers. А реальная 1С‑ка будет использовать все куски «пирога памяти» и вдобавок кучу временных таблиц и логики 1С на стороне сервера приложения. Поэтому синтетика, но с возможностью позиционирования в первом приближении на реальность 1С.

Стенд

  1. Физический хост: Гипервизор Proxmox VE, процессор Intel Xeon E5-2697 v2 (архитектура Ivy Bridge‑EP, 12 ядер / 24 потока, 30 МБ кэш L3).

  2. Виртуальная машина (ВМ): ОС Debian 13, выделено 8 ядер (тип процессора в Proxmox выставлен как host для сквозного проброса инструкций), 100 ГБ RAM.

  3. СУБД: PostgreSQL 17 (ванильная). Параметр shared_buffers = 40 ГБ, work_mem = 1GB, max_parallel_workers_per_gather = 0.

    Опыт был повторен также и для одной из вендорских СУБД с точно таким же результатом.

  4. Тестовые данные: Синтетическая таблица [test_hp_table] из 10 колонок с типом bigint объемом ~20 ГБ + уникальный B‑Tree индекс объемом ~3,7 ГБ (суммарно ~24 ГБ, что гарантирует полное размещение данных в shared_buffers и исключает влияние дисковой подсистемы I/O на результаты тестов).

Методика тестирования

Таблицу test_hp_table заполнили 180 млн. записей.

Заполнение таблицы [test_hp_table], создание индекса и пересчет статистики
-- 1. Выделяем 4 ГБ памяти на быструю сборку индекса (только на сессию)
SET maintenance_work_mem = '4GB';

-- 2. Создаем нелогируемую таблицу
CREATE UNLOGGED TABLE test_hp_table (
    id bigint,
    col1 bigint,
    col2 bigint,
    col3 bigint,
    col4 bigint,
    col5 bigint,
    col6 bigint,
    col7 bigint,
    col8 bigint,
    col9 bigint
);

ALTER TABLE test_hp_table SET (autovacuum_enabled = false);

-- 3. Быстро заливаем 180 млн строк (без индексов)
INSERT INTO test_hp_table (id, col1, col2, col3, col4, col5, col6, col7, col8, col9)
SELECT 
    g,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint,
    (random() * 9223372036854775807)::bigint
FROM generate_series(1, 180000000) AS g;

-- 4. Строим индекс в один проход
CREATE UNIQUE INDEX idx_test_hp_id ON test_hp_table(id);

-- 5. Актуализируем статистику для планировщика
ANALYZE test_hp_table;

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

CREATE EXTENSION pg_prewarm;

SELECT pg_prewarm('test_hp_table');
SELECT pg_prewarm('idx_test_hp_id');

Для генерации транзакционной нагрузки использовалась утилита pgbench в режиме конкурентного случайного поиска по индексу (Select‑only). С целью минимизации накладных расходов на переключение контекста процессора (Context Switches), конфигурация потоков бенчмарка была жестко приведена к архитектуре ВМ в соотношении 1:1 (8 клиентов, 8 потоков):

sudo -u postgres pgbench -f /var/lib/postgresql/test_query.sql -c 8 -j 8 -T 30

Запросы, которые использовались в утилите pgbench для создания нагрузки:

Сценарий 1. Простой SELECT по индексу (почти чистое чтение блоков из shared buffers)

set id random(1, 180000000)
SELECT * FROM test_hp_table WHERE id = :id;

Сценарий 2. SELECT с JOIN, сортировкой и агрегацией (смешанная операция чтения блоков и другой математики)

set id random(1, 179999900)

SELECT 
    c.category_name,
    COUNT(t.id) as total_count,
    AVG(t.col1) as avg_col1,
    SUM(t.col2) as sum_col2,
    MAX(t.col3) - MIN(t.col4) as spread
FROM test_hp_table t
JOIN test_hp_categories c ON c.cat_id = (t.col5 % 100000) + 1
WHERE t.id BETWEEN :id AND :id + 50
GROUP BY c.category_name
ORDER BY spread DESC, c.category_name ASC;

Для снятия метрик использовали утилиту ядра Linux perf в режиме глобального мониторинга (ключ ‑a) всей системы (system‑wide), запущенной в отдельной сессии. Анализируемые метрики:

  1. page‑faults — ошибки страниц

    Количество исключений (исключительных ситуаций) процессора, возникающих, когда поток обращается к странице памяти в своем виртуальном адресном пространстве, которая в данный момент не отображена в физическую память (RAM) или защищена. В контексте работающего под нагрузкой PostgreSQL данная метрика чаще всего отражает Minor Page Faults (мягкие ошибки) — ситуации, когда память уже выделена в буферном пуле, но для конкретного процесса postgres еще не построена запись в его локальной таблице страниц.

  2. dTLB‑loads — обращения к TLB

    Общее количество операций чтения данных из памяти, которые привели к обращению в Data Translation Lookaside Buffer (dTLB). В контексте СУБД показывает общую интенсивность работы СУБД с кэшем трансляции адресов. Это базовый показатель активности подсистемы памяти.

  3. dTLB‑load‑misses — промахи TLB

    Количество запросов к памяти, адресов которых не оказалось в кэше dTLB. В этот момент процессор вынужден приостановить выполнение инструкций программы и запустить аппаратный или программный обработчик Page Walk (обход дерева таблиц страниц), который ищет адрес в медленной оперативной памяти (RAM), тратя на это драгоценные такты процессора.

  4. cycles:k — такты процессора в ядре

    Количество циклов (тактов) процессора, затраченных на выполнение инструкций в привилегированном режиме (кольцо защиты 0 / Kernel Space).
    В идеальной конфигурации СУБД ядро должно лишь обеспечивать сетевой стек и редкий дисковый ввод‑вывод, а этот показатель должен стремиться к минимуму. Однако при отключенных Huge Pages счетчик cycles:k раздувается за счет того, что процессор тратит миллиарды тактов на логику ядра Linux по управлению памятью: обработку page‑faults, синхронизацию таблиц страниц между ядрами (TLB Shootdowns) и обход деревьев страниц в памяти.

  5. cycles:u  — такты в пользовательском пространстве

    Количество циклов процессора, затраченных на выполнение инструкций в пространстве пользователя (кольцо защиты 3 / User Space).
    Это полезная вычислительная мощность вашего сервера. Сюда входят парсинг SQL‑запросов, планирование, выполнение соединений (JOIN), сортировки, логика MVCC и работа самого демона PostgreSQL и процессов клиентов.

Все замеры выполнялись для двух конфигураций: обычные страницы 4 КБ и Huge Pages 2 МБ.

Результаты измерений

Сценарий 1. Простой Select

Метрика

Без Huge Pages (4 КБ)

Huge Pages 2 МБ

Влияние оптимизаций

Пропускная способность (TPS)

54 263 транз/сек

58 564 транз/сек

+7,93%

Средняя задержка (Latency)

0,147 мс

0,137 мс

-6,80% времени ответа

page faults (за 25 сек)

1 397 551

1 044

Снижение в 1338 раз

dTLB‑loads (за 25 сек)

67,08 млрд

69,36 млрд

+2,28 млрд обращений к памяти

Процент промахов dTLB

2,73%

2,49%

Снижение на 0,24%

cycles:k (такты ядра)

180,46 млрд

163,42 млрд

Сэкономлено 17,04 млрд (10%) тактов ядра

cycles:u (такты пользователя)

407,13 млрд

422,89 млрд

+15,76 млрд (3,7%) тактов в работу СУБД

Цифры получились наглядные, поэтому разберём их по частям.

Наглядно измеримые улучшения

  1. Ликвидация накладных расходов ядра (page‑faults)

    В реальном прогоне на страницах 4 КБ за 25 секунд возникло 1 861 651 ошибок page fault. Ядро при каждом вынуждено прерывать выполнение, выявлять и регистрировать малейшие изменения страниц, а также управлять сложной структурой страниц физической памяти. При использовании страниц 2 МБ число ошибок страниц сократилось до 1 044. То есть почти исчезло.

  2. Перераспределение ресурсов процессора

    Анализ метрик показал, что страницы 4 КБ и страницы 2 МБ дают разный баланс тактов. На тесте с Select‑only ядро тратит на 10,4% меньше тактов (cycles:k: 96,4 млрд → 86,42 млрд), а пользовательские процессы получают на 5,6% больше процессорного времени (cycles:u: 407,3 млрд → 422,89 млрд). Это чистое преимущество, которое напрямую конвертируется в рост TPS.

Особенности архитектуры процессора (почему Huge Pages выбран 2 МБ, а не 1 ГБ)

Тестирование гигабайтных страниц (1 ГБ) в рамках данного исследования показало меньшую эффективность относительно страниц 2 МБ (57,6k TPS против 58,5k TPS). На поколениях процессоров Intel Ivy Bridge аппаратный кэш TLB 2-го уровня (STLB) оптимизирован под сквозное кэширование страниц размером 4 КБ и 2 МБ. Поддержка страниц 1 ГБ ограничена малым количеством регистров в L1 dTLB, что при полностью случайной выборке данных вызывает повышенную плотность промахов на уровне регистров CPU. Для данного поколения «железа» размер 2 МБ является оптимальным вариантом.

Сценарий 2. Select с JOIN, сортировкой и группировкой

Метрика

Без Huge Pages
(4 КБ)

Huge Pages 2 МБ (Оптимизация)

Чистый эффект от HP 2МБ

Пропускная способность (TPS)

10 323

10 795

+4,57% скорости

Средняя задержка (Latency)

0.775 мс

0.741 мс

-4,38% времени ответа

page‑faults (за 25 сек)

473 575

4 146

Снижение нагрузки в 114 раз!

dTLB‑loads (за 25 сек)

204,86 млрд

213,41 млрд

+8,55 млрд операций с памятью

Процент промахов dTLB

0,45%

0,33%

Снижение на 0,12%

cycles:k (Такты Ядра Linux)

51,15 млрд

42,99 млрд

Сэкономлено 8,16 млрд тактов ядра

cycles:u (Такты СУБД / Кода)

533,56 млрд

543,65 млрд

+10,09 млрд тактов ушло в вычисления

Почему в Сценарии 2 прирост уменьшился с 8% до 4,5%?

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

  1. Сдвиг фокуса процессора. В прошлом легком тесте (SELECT * WHERE id =:id) процессор выполнял запрос мгновенно. Трансляция памяти (поиск адресов 4 КБ) занимала огромную долю от общего времени работы. Настроив Huge Pages, мы убрали эту долю, получив рывок в ~8%. В новом тяжелом тесте процессор тратит колоссальное количество времени на чисто вычислительную работу в пространстве пользователя (cycles:u вырос с 422 до 543 миллиардов тактов!). Ему нужно не просто достать данные, а прогнать алгоритм Hash Join, посчитать AVG(), SUM(), MAX(), MIN() и отсортировать пачку строк.

  2. Падение доли промахов TLB. Процент промахов dTLB в выводе perf stat упал с 2,49% (в легком тесте) до 0,33% — 0,45% (в тяжелом). Почему? Потому что теперь процессор, обратившись к одной странице памяти, «застревает» в ней надолго, выполняя вычисления над строками, вместо того чтобы хаотично прыгать по всей RAM. Относительная нагрузка на TLB снизилась сама по себе.

Основной вывод по второму тесту: абсолютная эффективность неизменна!

Несмотря на то, что в процентах прирост TPS стал скромнее (4.57%), абсолютные показатели доказывают, что Huge Pages работают точно так же эффективно:

  • Ядро Linux сэкономило 8,16 млрд тактов (cycles:k). Оно снова полностью избавилось от рутины обслуживания сотен тысяч мелких страниц (page‑faults упал с 473к до 4к).

  • Пространство пользователя получило дополнительные 10,09 млрд тактов (cycles:u).

Процессор сэкономил фиксированный объем «энергии» на трансляции адресов и ровно этот объем отдал PostgreSQL на математику. Из‑за этого база успела провернуть на 8,5 миллиардов больше операций чтения данных (dTLB‑loads) за те же 25 секунд.

В качестве дополнительной проверки мы увеличили huge page, shared buffers и объем таблицы в запросе в 3 раза и проверили исследование по первому сценарию. Результат получили практически такой же.

Выводы

Huge Pages определенно полезны, но не панацея! Максимальный эффект, который нам удалось получить на тестовом стенде — это 8% на простом чтении по индексу. В тяжёлом тесте с JOIN, агрегацией и сортировкой прирост упал до 4,5%. Нужно понимать, что в реальной работе 1С такие простые запросы почти не встречаются. Обычно там есть многоэтажные соединения, вычисления, работа с временными таблицами. Поэтому на практике ускорение будет ещё скромнее.

Настройка HP оправдана, если у вас большой объём оперативной памяти. Мы бы сказали, от 50 ГБ и выше. Если меньше, эффект можно не заметить. И обязательно нужен кто‑то, кто будет присматривать за этим хозяйством: правильно выставить vm.nr_hugepages, прописать huge_pages = on в postgresql.conf, убедиться, что память выделилась.

Но главное — не ждите чудес. Оптимизация запросов, правильные индексы, грамотная схема БД дают выигрыш в разы и на порядки больше. Huge Pages — это тонкая настройка, которая в лучшем случае улучшит общую производительность на несколько процентов.


Ссылки на остальные части Записок оптимизатора 1С:

  1. Записки оптимизатора 1С (ч.1). Странное поведение MS SQL Server 2019: длительные операции TRUNCATE

  2. Записки оптимизатора 1С (ч.2). Полнотекстовый индекс или как быстро искать по подстроке

  3. Записки оптимизатора 1С (ч.3). Распределенные взаимоблокировки в 1С системах

  4. Записки оптимизатора 1С (ч.4). Параллелизм в 1С, настройки, ожидания CXPACKET

  5. Записки оптимизатора 1С (ч.5). Ускорение RLS‑запросов в 1С системах

  6. Записки оптимизатора 1С (ч.6). Логические блокировки MS SQL Server в 1С: Предприятие

  7. Записки оптимизатора 1С (ч.7). «Нелогичные» блокировки MS SQL для систем 1С предприятия

  8. Записки оптимизатора 1С (ч.8). Нагрузка на диски сервера БД при работе с 1С. Пора ли делать апгрейд?

  9. Записки оптимизатора 1С (ч.9). Влияние сетевых интерфейсов на производительность высоконагруженных ИТ‑систем

  10. Записки оптимизатора 1С (ч.10): Как понять, что процессор — основная боль на вашем сервере MS SQL Server?

  11. Записки оптимизатора 1С (ч.11). Не всегда очевидные проблемы производительности на серверах 1С

  12. Записки оптимизатора 1С (ч.12). СрезПоследних в 1C:Предприятие на PostgreSQL. Почему же так долго?

  13. Записки оптимизатора 1С (ч.13). Что не так в журнале регистрации 1С в формате SQLitе?

  14. Записки оптимизатора 1С (ч.14.1). Любите свою базу данных и не забывайте обслуживать

  15. Записки оптимизатора 1С (ч.14.2). Пересчет индексов на SSD‑дисках. Делаем или игнорируем?

  16. Записки оптимизатора 1С (ч.14.3). Отличия в обслуживании статистик в MS SQL и в PostgreSQL

  17. Записки оптимизатора 1С (ч.15). Параллелизм запросов 1С в PostgreSQL

  18. Записки оптимизатора 1С (ч.16). Риски падения Postgres: потребление и высвобождение памяти процессами postgres

  19. Записки оптимизатора 1С(ч.17). Как избежать падения Postgres при большом потреблении памяти запросами

  20. Записки оптимизатора 1С (ч.18.1). Ошибки 1С в части производительности и стабильности ИС

  21. Записки оптимизатора 1С (ч.18.2). Ошибки 1С из‑за конфликта блокировок

  22. Записки оптимизатора 1С (ч.19). Как настройка Huge Pages в Linux влияет на устойчивость работы Postgres

  23. Записки оптимизатора 1С (ч.20). На сколько реально настройки Huge Pages для Postgres могут ускорить запросы 1С

Комментарии (0)