Как стать автором
Обновить

Комментарии 19

Спасибо за статью, было приятно почитать. Но есть пара узких моментов (как всегда со статьями про бенчмарки).


Создаю одну таблицу, размер которой больше ОЗУ сервера, и индекс к этой таблице, который занимает где-то 10% от размера таблицы

а в самом скрипте:


— Размер таблицы, которую нужно создать. Подбирается методом проб и ошибок так, чтобы таблица имела размер, равный ОЗУ.
\set table_size 75000000

я не придираюсь, но это все-таки важный момент. Таблица помещается в память или же нет? Можно было бы заодно проверить и вариант, когда таблица и индекс полностью помещаются в память.




select count(data) from random;

Меня интересует момент про SeqScan (поиск по таблице без индекса). Запрос select count(*) ... вынуждает постгрес выгружать всю таблицу в память. Мы приходим к ситуации, когда все строки таблицы читаются с диска с одинаковой частотой. Это не типичная ситуация для кешей, и проверять их эффективность в такой ситуации может быть не лучшей идеей. Все-таки кеши рассчитаны на ускорение запросов к горячим данным, а не ко всем вообще.


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


О внутренностях системного кеша знаю еще меньше, так что не буду даже загадывать, есть ли подобные вещи там.




Когда я тестировал два года назад на CentOS 7 и PostgreSQL 10, кеш файловой системы работал примерно с такой же скоростью, что и кеш PostgreSQL без HugePages, а добавление HugePages давало значимый выигрыш над кешем файловой системы. Из чего я могу сделать вывод, что за последние годы Linux научился гораздо эффективнее использовать свой файловый кеш.

Уверен, что ответ будет положительным, но обязан спросить. Вы ведь убедились, что THB отключены? На только что перезагруженной машине, где запущена только база (которая аллоцирует память кусками одного размера) — разницы с большими страницами почти не будет, так как не будет фрагментации. В ситуации, когда таблица почти целиком лежит в кешах, и во время запросов псевдо-большие страницы не аллоцируются, обслуживание TLB может не повлиять на время тестовых запросов.




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


sysctl vm.drop_caches=1

или


echo 1 > /proc/sys/vm/drop_caches



Еще момент. Графики показывают, что использование больших страниц не дает ускорения (а где дает, там ускорение незначительно). Что наводит меня на мысль, что что-то здесь не так. Возможно, дело в том, что в ситуациях, когда вы читаете 16Гб случайных данных (не важно, в страницах 4кб или 2Мб), TLB в любом случае переполняется множество раз, и выйгрыша в больших страницы нет. Но в этом вопросе я тоже не особо разбираюсь, может прокомментирует кто из знатоков.

не придираюсь, но это все-таки важный момент. Таблица помещается в память или же нет? Можно было бы заодно проверить и вариант, когда таблица и индекс полностью помещаются в память.

Смотрите, основная идея была продемонстрировать, что кэш PostgreSQL работает лучше, чем кэш файловой системы. Поэтому я создал немного искусственную ситуацию, которая бы лучше всего служила целям демонстрации некоторых достоинств кэша PostgreSQL по сравнению с кэшем файловой системы. Поэтому для наглядности демонстрации я создал такие условия, что таблица примерно полностью занимает ОЗУ, плюс индекс, таким образом при последовательном чтении индекс должен был бы затираться в кэше в случае кэша файловой системы.


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


Случай когда и таблица и индекс полностью находится в ОЗУ, точнее полностью в одном из кэшей, не замерял. Можете это сделать самостоятельно, если вам это интересно. Все скрипты я приложил.


Меня интересует момент про SeqScan (поиск по таблице без индекса). Запрос select count(*)… вынуждает постгрес выгружать всю таблицу в память.

Нет, постргрес так не делает. Хотя иногда все же делает. :) Подробнее ниже.


Вы учитывали использование постгресом кольцевых буферов для таких запросов? К сожалению, я в постгресе не эксперт, знаю об этом крайне мало.
О внутренностях системного кеша знаю еще меньше, так что не буду даже загадывать, есть ли подобные вещи там.

Собственно основной смысл этой демонстрации был в том, чтобы показать, что PostgreSQL использует кольцевые буферы для sequence scan, а кэш файловой системы нет и поэтому он может быть предпочтительнее. Не только поэтому, там есть и другие оптимизации, эта одна из них. Подробнее об этом вы можете поглядеть в презентации, ссылка была в статье. Но по собственному опыту я хочу уточнить, что кольцевые буферы PostgreSQL использует не всегда, а только для больших, по сравнению с собственным кэшем, таблиц. Если он видит, что таблица занимает мало места по сравнению с размером с собственным кэшем, то тогда он кольцевыми буферами не заморачивается. Я могу с этим ошибаться, но у меня сложилось такое впечатление.


Уверен, что ответ будет положительным, но обязан спросить. Вы ведь убедились, что THB отключены?

TLB? Нет, не убеждался, это был linux CentOS по дефолту, как он есть. :)


На только что перезагруженной машине, где запущена только база (которая аллоцирует память кусками одного размера) — разницы с большими страницами почти не будет, так как не будет фрагментации. В ситуации, когда таблица почти целиком лежит в кешах, и во время запросов псевдо-большие страницы не аллоцируются, обслуживание TLB может не повлиять на время тестовых запросов.

Я думаю что PostgreSQL аллоцирует весь собственный кэш целиком при загрузке. По крайней мере в случае использования HugePages это очевидно. И если и есть выигрыш от использования HugePages, то связан он не с аллоцированием.


По поводу невозможности выделения больших страниц на давно работающей машине. Под большие страницы выделяется только свободная память. Память, уже занятая файловым кешем, не освобождается.

По поводу этого утверждения я прокомментировать не могу, но думаю фрагментирует память не только файловый кэш, но и любые другие процессы, которые тоже запрашивают память. Я же не в single user mode линкс запускал, там были еще какие-то системные демоны и т.д. Поэтому в крайнем случае, когда по моим прикидкам кэш PostgreSQL должен занимать почти всё (по максимуму, но в разумны пределах) ОЗУ, без перезагрузки это не всегда получалось. А с перезагрузкой работало как часы в течении почти месяца. :)


Графики показывают, что использование больших страниц не дает ускорения (а где дает, там ускорение незначительно). Что наводит меня на мысль

Графики показывают, что иногда использование больших страниц дает ускорение, а иногда (в случаях с sequence scan) даже замедляет. :) Большое это ускорение или нет (примерно в полтора раза) дело вкуса. Но важно то, что этот эффект значительно превышает точность с которой собрана статистика и поэтому он достоверен. А вот какие механизмы при этом работают, какая роль TLB и откуда получается такой эффект, тут я теоретизировать не могу.

Уверен, что ответ будет положительным, но обязан спросить. Вы ведь убедились, что THB отключены? На только что перезагруженной машине, где запущена только база (которая аллоцирует память кусками одного размера) — разницы с большими страницами почти не будет, так как не будет фрагментации. В ситуации, когда таблица почти целиком лежит в кешах, и во время запросов псевдо-большие страницы не аллоцируются, обслуживание TLB может не повлиять на время тестовых запросов.

Имел в виду THP (Transparent Huge Pages). Первый раз опечатался в одной букве, еще простительно. А вот TLB вместо THP — это уже на грани фола.

Нет, не проверял, дефолтный линукс. Понапридумывали всякого, я про такое даже не слышал.

Современные ноутбуки используют ssd, nvme. Имеет смысл добавить информацию hdd/ssd.

Не помню что было на том, да и не важно. Чтобы производительность винчестера не сказывалась на картинку специально прогревал БД, чтобы данные были по максимуму в ОЗУ. Производительность работы с ОЗУ и измерялась. Ну а винчестер это что-то заведомо более медленное, чтобы продемонстрировать что работа с БД сильно замедляется если в кэше затирается индекс.

Когда боролся с вымыванием кеша ОС на hot standby со 500 ГБ памяти, первое, что сделал — увеличил shared buffers до объёма 450 ГБ (было 30 ГБ, huge_pages=on, THP заблокированы). Безусловно, спонтанные замедления запросов (на hot standby они только читающие) тут же прекратились. Чуть позже стало понятно, что и 50% под shared buffers достаточно, и даже меньше.
Когда понимаешь, что hot standby лучше унифицировать с primary (master), а для него такой объём не только бесполезен, но и вреден (при определённых обстоятельствах), сразу очевидно, что 50% памяти — это, пожалуй, максимум.

Тут вообще несуразно делать глубокомысленные рассуждения об эффективности кэша в процентах к… ОЗУ на сервере. :) Эффективность кэша определяется исключительно размером БД ну или размером данных в БД которые находятся в активном использовании. Они, очень желательно, должны влазить в кэш целиком. А еще лучше, конечно, БД целиком, хотя это уже не критично. А будет это 10% от ОЗУ сервера, 25%, 50% или 75% совершенно не важно, тут даже рассуждать на эту тему глупо. Хотя, конечно, чем больше памяти на сервере, тем лучше. И нет смысла делать размер кэша для БД больше, чем размер самой БД. Может и есть, но сходу я такой смысл придумать не могу.


А про то что кэш на мастере вреден об этом можете написать по подробнее. :)

Речь не про кеш на мастере, а именно про shared buffers. Если изменений достаточно много, а производительность диска недостаточна, то checkpoint будет периодически очень сильно замедлять весь кластер. Конечно, есть выход, чтобы получить максимум производительности на такой системе — максимально отсрочить checkpoint (например, раз в сутки ночью). Но такая система будет не очень устойчива при сбоях, восстановление может продолжаться до нескольких часов.

Про глубокомысленные рассуждения в процентах мой ответ только в тестах на реальной системе (сделали много, проверили, уменьшили, повторили тесты, ...). И синтетические тесты не всегда адекватно ответят на вопрос, как именно настроить. Ведь в Вашем тесте всегда 100% данных считаются горячими? На бою так не бывает.

shared_buffers это и есть кэш самого PostgreSQL. И? Каким образом уменьшив shared_buffers вы хоть как-то выиграете в производительности на запись? :)


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


Я согласен с тем, что горячие данные это какая-то доля. Согласен с тем что вы правильно сделали, что проводили эксперименты на сервере с реальными данными и нашли оптимальный для вас размер кэша. Вот с чем я категорический несогласен, что этот оптимальный размер кэша вы каким-то образом сумели увязать с 50% от размера ОЗУ и вывести из этого какое-то универсальное правило. Оптимальный размер кэша зависит только от размера БД и структуры (распределения данных, доли горячих данных) в этой БД, но никак не от размера ОЗУ на сервере. ОЗУ на сервере может только либо хватать для оптимального размера кэша, либо не хватать.

1. shared_buffers — не только кеш, но и грязные страницы. Если worker-у не хватает страниц для чтения с диска, он начнёт выгружать грязные страницы прямо в процессе выполнения запроса. В случае выгрузки большого объёма страниц за раз время отклика читающего запроса может существенно увеличиться в самый неподходящий момент.
2. Забирая память у кэша записи ОС мы лишаем себя возможности отложенной записи большого объёма данных. Любой пик записи на диск (кроме checkpoint, разве что) может быть сглажен за счёт грязных страниц менеджером виртуальной памяти. Но как быть, если вы всю память отдали под shared buffers?
3. 50 % — это цифра, оптимальная для моей системы. Она хорошо согласуется с рекомендацией из документации (40%). Никого не призываю слепо верить этим цифрам. Верить тестам можно.
не только кеш, но и грязные страницы
Кэш на запись это и есть "грязные страинцы", нет?

Если worker-у не хватает страниц для чтения с диска, он начнёт выгружать грязные страницы прямо в процессе выполнения запроса.

Мне сложно представить себе БД в которой была бы настолько катастрофическая ситуация с записью. Там скорее я бы подозревал bad design, корявую оптимизацию или неуместное использования SQL сервера для задач, для которых он не предназначен. Более того, тут вы сами является автором проблемы, которую якобы решаете. Уменьшая shared_buffers вы серьезно увеличиваете шанс, что их будет не хватать настолько, что даже грязные страницы во время транзакции придется вытеснять. Идеальное решение — shared_buffers должно хватать для всех активно используемых данных на чтение, не говоря уже и про запись.


Забирая память у кэша записи ОС мы лишаем себя возможности отложенной записи большого объёма данных. Любой пик записи на диск (кроме checkpoint, разве что) может быть сглажен за счёт грязных страниц менеджером виртуальной памяти. Но как быть, если вы всю память отдали под shared buffers?

Ну смотрите. Как пишет БД. Сначала, до завершения транзакции, все изменения на запись находятся в shared_buffers. На этом этапе урезание shared_buffers в угоду кэшу файловой системы никак не поможет, скорее усугубит, потому что уменьшая их вы создаете проблему, что их придется скидывать прямо во время транзакции. Потом, при commit они уходят в журнал транзакций и синкаются. Тут кэш файловой системы тоже никак не поможет, потому что postgresql будет ожидать конца реальной записи на винчестер. Ну и потом, при checkpoint те данные завершенных транзакций, которые уже в журнале транзакций, но еще не были сброшены в файлы таблиц сбрасываются в файлы таблиц. Тут PostgreSQL тоже будет ожидать подтверждение полной записи. Так что и тут файловый кэш никак запись не ускорит.


40% в документации не видел, цитата
https://www.postgresql.org/docs/current/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY


if you have a dedicated database server with 1GB or more of RAM, a reasonable starting value for shared_buffers is 25% of the memory in your system.

Так что ваша цифра ни с чем из документации не согласуется. Не надо ссылаться на документацию, если на самом деле там этого нет. Верить вашим тестам тоже нельзя, если они подробно не расписаны, как тестировались. :) И не перепроверены. Именно поэтому я подробно расписал свои тесты и приложил все скрипты, чтобы их можно было перепроверить любому желающему.


Скажем так, можно сформулировать задачу для тестирования: нужно установить пропорции shared_buffers. и кэш файловой системы таким образом, чтобы оптимизировать запись большого объема данных. Я в этом смысла не вижу, потому что не вижу теоретических предпосылок для того, чтобы уменьшение shared_buffers хоть как-то сказалось на ускорение записи. Если только вы не отрубили ожидание полной записи на диск, но на реальных БД ни один здравомыслящий человек так не сделает. Но я допускаю, что я могу ошибаться. Так что напишите аналогичную моей статье статью на эту тему, подробно распишите скрипты, как вы тестировали, чтобы каждый, например я, мог бы перепроверить ваш результат.

Довольно сложно найти документацию, описывающую алгоритмы работа кеша PostgreSQL. Для желающих изучить этот вопрос более углубленно, привожу ссылку: Inside the PostgreSQL Shared Buffer Cache.

Вот еще классная книга, очень рекомендую: "The Internals of PostgreSQL".
Глава про буферный кэш: http://www.interdb.jp/pg/pgsql08.html

Ага, спасибо

Спасибо за пост.
Что интересно, как раз для индексов — рекомендации о размере shared_buffers в 1/4 ОЗУ вполне себе работают. При поиске по индексу разница во времени выполнения не велика.
Вот для скана проигрыш на рекомендуемом размере очевиден, но, неочевидно, будет ли при реальной нагрузке картина такой же.

Любые рекомендации о размере shared_buffers в качестве пропорций к ОЗУ не работают. :) Это как пальцем в небо. Оптимальный размер shared_buffers зависит от размера БД, размера активных данных в БД, размера индексов в БД и т.д. Но не от размера ОЗУ на сервере. :) ОЗУ на сервере разве что может либо хватать для оптимального размера shared_buffers, либо не хватать. Т.е. правильная последовательность проанализировав БД вычислить нужный размер shared_buffers, а потом докупить нужное количество ОЗУ на сервер, чтобы хватало с запасом и еще оставалось на системные нужды.

Вы же сами выше приводили цитату из документации
if you have a dedicated database server with 1GB or more of RAM, a reasonable starting value for shared_buffers is 25% of the memory in your system.

Даже в вашем эксперименте это starting value вполне неплохо себя показывает. Сделаете таблицу в 4 раза больше и, вероятно, разница на сканах тоже не будет столь заметной.

Конечно было бы здорово иметь столько ОЗУ, чтобы туда влезало всё, что нужно. И, конечно, рекомендации — это всегда что-то усреднённое, подходящее не всем, но они дают хоть какую-то отправную точку.

Отправная точка, если вся БД не влазит в ОЗУ, то чем больше ОЗУ будет приходится на кэш shared_buffers, тем лучше. Разумеется в разумных пределах, надо оставить системе на нужды системы. Потому что со своим кэшем БД работает лучше (в том числе применяет оптимизации), чем с кэшем файловой системы.


Что касается 25% то тут надо копнуть, в каком году эта рекомендация появилась. :) В те времена ОЗУ настолько не хватало, что сервера из свопа не вылазили. С тех пор многое изменилось.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий