Время от времени приходится слышать мнение от некоторых системных администраторов, а также некоторых 1С-разработчиков, что установка, настройка и поддержка PostgreSQL под Linux очень сложна. Что гораздо дешевле покупать лицензии Windows и Microsoft SQL Server, чем нанимать высококвалифицированных администраторов, которые будут администрировать все эти open-source системы.

На наших бизнес-приложениях, использующих в качестве СУБД PostgreSQL, работают 70% крупнейших розничных сетей в Беларуси. Во всех из них одновременно работают от 500 до 1500 пользователей. В приложениях реализованы практически все основные процессы розничных сетей (демо, чтобы оценить сложность). Размер баз данных на данный момент составляет от 2 до 4ТБ. И все они работают практически со стандартными настройками PostgreSQL на одиночных серверах без какой-либо кластеризации. При этом даже в самых загруженных серверах есть еще значительный резерв по ресурсам для дальнейшего увеличения нагрузки без потребности в кластеризации.

Да, конечно же, многое зависит от запросов к СУБД, и несколькими кривыми запросами можно положить весь сервер. Однако, точно также можно положить и Oracle, и MSSQL. Да, платформа lsFusion, на которой написаны наши приложения, делает много различных оптимизаций запросов конкретно под PostgreSQL. Но вручную SQL-запросы можно оптимизировать еще лучше.

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

Установка PostgreSQL

Не буду сильно заострять внимание непосредственно на установке, так как по ней есть миллион инструкций в интернете, но вкратце опишу, чтобы показать насколько это просто.

Сейчас у большинства наших клиентов мы используем в качестве ОС CentOS 7. Просто так исторически сложилось, хотя у некоторых используются и Debian, и Ubuntu, и там тоже все работает нормально. Единственная проблема у нас возникала у двух клиентов только с CentOS 8. Там почему-то производительность была процентов на 30 ниже чем с CentOS 7 за счет возникающего высокого system time при активной работе со временными таблицами. Анализ perf и исходников PostgreSQL не дал быстрых результатов, а из-за ограничения времени и давления со стороны клиентов пришлось просто откатиться на CentOS 7, после чего проблема ушла.

Установка CentOS делается с минимального образа, скачанного с официального сайта. Там есть графический инсталятор, и с установкой прекрасно справлялись даже системные администраторы, которые ранее в глаза не видели Linux. Естественно, установка ОС идет не на голое железо, а на виртуальную машину.

Единственная рекомендация, которую мы стараемся давать при установке ОС - чтобы под базу данных подключали отдельный диск (лучше даже без LVM). Это удобно тем, что потом можно легко при необходимости менять ОС, просто подключая диск с базой данных к другой виртуальной машине.

После того, как ОС установлена, и к ней получен SSH-доступ, делается установка как описано на официальном сайте PostgreSQL. В частности, на Redhat-based Linux, которой и является CentOS, в консоле нужно запустить следующие команды :

# Install the repository RPM:
sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

# Install PostgreSQL:
sudo yum install -y postgresql14-server postgresql14-contrib

Эти команды добавляют в общий список yum-репозиторий, и устанавливают программные файлы PostgreSQL.

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

Перед инициализацией базы данных рекомендуется проверить и установить часовой пояс и регион (так как они запишутся в настройки самой базы из настроек ОС) :

localectl set-locale LANG=ru_RU.utf8
timedatectl set-timezone Europe/Moscow

По умолчанию, база данных будет установлена по пути /var/lib/pgsql/14/data. Но если под базу данных был выделен отдельный диск, то можно перед инициализацией кластера задать путь, куда будет установлена база данных следующим образом :

systemctl edit postgresql-14.service

В появившемся окне редактора указать следующие параметры :

[Service]
Environment=PGDATA=/data/14

Если диск с базой был смонтирован по пути /data, то лучше помещать кластер в подкаталог /data/14, чтобы потом было легче делать pg_upgrade базы данных до 15й и последующих версий.

Наконец, создаем сам кластер БД при помощи следующей команды :

sudo /usr/pgsql-14/bin/postgresql-14-setup initdb

Помимо создания самой базы данных эта команда создает в ОС службу postgresql-14. Ее дальше нужно добавить в автозагрузку при помощи команды :

sudo systemctl enable postgresql-14

Запуск и остановка службы осуществляется соответственно следующим образом :

sudo systemctl start postgresql-14
sudo systemctl stop postgresql-14 

Настройка PostgreSQL

Все основные настройки PostgreSQL находятся в двух файлах : postgresql.conf и pg_hba.conf. В первом хранятся настройки самой базы данных, а в во втором - настройки доступа к ней. Изменение параметров осуществляется путем редактирования этих файлов в любом текстовом редакторе. Лично я чаще всего пользуюсь встроенным редактором Midnight Commander.

После любых изменений параметров требуется уведомить СУБД о том, что требуется перечитать конфигурацию. Лишь маленькая часть параметров требует перезапуска службы PostgreSQL (при помощи команд stop/start указанных чуть выше). Большинство параметров можно изменить на лету несколькими способами. Я чаще всего использую для этого psql. Для этого делается сначала в консоли :

su postgres
psql

А затем уже внутри psql запускается :

SELECT pg_reload_conf();

Основные настройки

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

Первым делом возможно надо увеличить параметр max_connections. По умолчанию, он равен 100, что может быть мало при большом количестве пользователей. Однако слишком большое значение тоже не стоит указывать (так как есть дополнительные расходы). Поскольку lsFusion создает под каждого пользователя по своему выделенному соединению, то мы обычно выставляем :

max_connections = <кол-во пользователей> * 2

PostgreSQL не работает с данными на диске напрямую. Когда ему нужно что-то считать или записать, то он загружает соответствующие страницы с диска в блок памяти, который называется shared buffers. Эта общая память, которая используется одновременно всеми подключениями. Чем выше объем этих буферов, тем меньше будет нагрузка на диск. Для тонкой настройки можно анализировать в динамике, как именно идет ротация этих буферов, но на практике мы обычно выставляем от 30 до 50% всей доступной памяти на сервере :

shared_buffers = 128GB

Помимо этого параметра, обычно мы сразу настраиваем еще три :

temp_buffers = 32MB
work_mem = 32MB

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

Существует один прием, используемый автоматически платформой lsFusion, который позволяет уменьшать потребляемую память. Каждое подключение к PostgreSQL - это отдельный процесс в ОС. По мере выполнения запросов эти процессы не всегда быстро отдают использованную память обратно операционной системе. Для того чтобы бороться с этим, платформа время от времени закрывает активные подключения и открывает их заново. Тем самым процесс, который потребляет много памяти, уничтожается, а на его месте создается новый. Это позволяет значительно уменьшить объем частной памяти потребляемый всеми пользовательскими подключениями.

maintenance_work_mem = 2GB

Этот параметр по умолчанию слишком маленький и лучше его увеличивать, чтобы ускорить разные системные операции.

Дополнительные настройки

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

wal_level = minimal
synchronous_commit = off

Если не планируется настройка асинхронной реплики, то обычно понижаем уровень wal_level. Synchronous_commit также отключаем, так как мы не пишем банковские системы. Если сервер упадет (что бывает крайне редко), то нет принципиальной разницы успело пользователю прийти сообщение об успешном сохранении или нет. Зато все сохранения будут работать чуть-чуть быстрее.

checkpoint_timeout = 20min
max_wal_size = 16GB

Под сильной нагрузкой СУБД некоторых наших клиентов успевают записывать по 1ГБ wal'ов в минуту. При значении max_wal_size равном 1ГБ получается, что чекпоинты будут происходить раз в минуту, что не есть хорошо (особенно при включенном full_page_writes). Поэтому обычно повышаем значение, чтобы чекпоинты происходили раз в 20 минут. Соответственно, немного уменьшается нагрузка на диск. Да, будет дольше восстановлении при падении, но это бывает крайне редко.

seq_page_cost = 0.1                     # measured on an arbitrary scale
random_page_cost = 0.1                  # same scale as above

cpu_tuple_cost = 0.05                   # same scale as above
cpu_index_tuple_cost = 0.05             # same scale as above
cpu_operator_cost = 0.01                # same scale as above

Обычно мы значительно понижаем (по сравнению со стандартными) стоимости диска, и в свою очередь увеличиваем стоимости процессорных операций. Так делается потому, что изначально настройки PostgreSQL делались под медленные HDD-диски. У нас же всегда используются SSD-диски в RAID-массивах, где стоимость чтения значительно ниже, а произвольное запись/чтение от последовательной не сильно отличается.

Единственное, мы стараемся использовать везде идентичные настройки параметров cost, чтобы планы выполнения запросов были одинаковыми. Иначе на тестовом окружении может работать все быстро, а в рабочем будет другой план, который будет значительно медленнее.

Здесь следует отметить, что изменения в параметрах PostgreSQL не всегда приводят к ожидаемому результату в планах запросов. У нас была ситуация, когда простое увеличение параметра work_mem привело к тому, что запрос вместо 20 минут начал выполняться 2 часа. В плане выполнения начал использоваться hash join с предварительным seq scan всей таблицы, которую приходилось читать с диска. Тут кроется одна из основных проблем планирования запросов в PostgreSQL. Планы не учитывают какие данные находятся сейчас в shared buffers, а какие нет. И часто гораздо выгоднее сделать пробег по тем данным, которые в кэше (пусть их и значительно больше), чем читать с диска меньший объем.

Внешний доступ

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

listen_addresses = '*'

После этого потребуется рестарт всей службы PostgreSQL. После этого, нужно добавить в pg_hba.conf IP, с которых принимать подключения (а именно адрес сервера приложений) :

host    all             all             192.168.1.22/32         trust

Вместо trust нужно использовать scram-sha-256, если доступ требуется по паролю.

Дополнительные настройки Linux

Помимо описанных ранее настроек PostgreSQL в серверах с большим количеством памяти мы часто изменяем еще несколько настроек самого CentOS.

Во-первых, в /etc/sysctl.conf устанавливаются следующие параметры :

vm.min_free_kbytes = 4194304
vm.swappiness = 1

Первый параметр устанавливает минимальное количество свободной памяти, которую будет стараться держать ОС. Это нужно, чтобы избавиться фрагментации памяти и высокого system time в определенных случаях (вот тут описана проблема). Swappiness выставляем в 1, так как своп будет очень сильно вредить, а 0, вроде как, не рекомендуется (хотя особой разницы в поведении между 0 и 1 я не замечал).

Далее, в /etc/fstab при подключении диска с базой данных прописываем опции noatime,nodiratime. Мелочь, но хуже не будет. Например :

/dev/sdb /data xfs defaults,noatime,nodiratime 0 0

Также на большом объеме памяти обычно настраиваем использование huge pages. Для этого сначала отключаем THP, а затем добавляем фиксированное количество страниц, которое соответствует размеру shared buffers. В файл /etc/sysctl.conf добавляем :

vm.nr_hugepages = (<shared_buffers> / 2MB) + 3% # например, для 128ГБ = 67502

Ну и наконец, так как мы используем высокопроизводительные SSD диски, то обычно выключаем планировщик ввода/вывода, включая noop или none режим. Есть много способов это сделать, но обычно мы просто настраиваем службу :

[Unit]
Description=Change scheduler

[Service]
Type=simple
ExecStart=/bin/sh -c "echo 'noop' > /sys/block/sdb/queue/scheduler" 

[Install]
WantedBy=multi-user.target

Конфигурация сервера

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

Например, один из наших клиентов использует сервер с двумя процессорами Intel Gold с 24 ядрами в каждом (что дает 96 виртуальных ядер) и 256ГБ памяти. В сервер напрямую через PCI express воткнуты 4 NVME диска по 3ТБ каждый, которые собраны в программный RAID-10 (через LVM) объемом около 5.8ТБ. Сейчас база данных там занимает около 3ТБ, с которой работают около 1000 одновременных пользователей. Рыночная стоимость такого сервера на данный момент составляет около 12K$ (и еще 12К$ стоят диски такого размера).

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

В пиковые моменты времени скорость чтения на таком сервере достигает 1.5ГБайт/секунду без существенного увеличения времени waiting :

Такого запаса производительности сервера будет достаточно при увеличении количества пользователей в 2-3 раза, прежде чем нужно будет начинать использовать кластеризацию.

Заключение

Мы активно используем PostgreSQL в нагруженных приложениях уже более 5 лет. Мы поддерживаем базы данных у нескольких десятков клиентов (как мелких на пару десятков пользователей, так и достаточно крупных). За все это время было много различных нештатных ситуаций, связанных с аварийными отключениями виртуальных машин и серверов. И ни разу у нас не было потерь данных. PostgreSQL всегда запускался, воспроизводил wal с последнего checkpoint и прекрасно продолжал работу. Один раз системный администратор клиента случайно удалил диск, на котором хранился целый tablespace от базы данных. При этом PostgreSQL продолжил работу просто без этих таблиц. Но даже тогда после определенных танцев с бубнами удалось восстановить базу данных в нормальное состояние, а данные пропавших таблиц достать из копии.

PostgreSQL постоянно развивается. Приблизительно каждый год выходит новый релиз с новыми возможностями. Последней значимой для нас была 13я версия (все крупные клиенты уже давно перешли на нее). В ней, в частности, значительно улучшили работу индексов с повторяющимися значениями. В результате размер наших баз данных сократился на 10-15 процентов.

Резюмируя, хочу отметить, что PostgreSQL прекрасно подходит для бизнес-приложений, легка в настройке, и предоставляет отличную альтернативу коммерческим СУБД.

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


  1. ScarferNV
    23.11.2021 12:25

    Странно что вы не рассказали про настройку логирования. А это надо делать в первую очередь перед запуском кластера.


    1. CrushBy Автор
      23.11.2021 12:31
      +1

      Да, это конечно же важно, но я в первую очередь старался описать стандартные настройки, которые можно/нужно делать для человека, который не особо сильно разбирается в PostgreSQL. А если человек не особо разбирается, то и логи он читать вряд ли будет.


  1. vasilisc
    23.11.2021 14:40
    +1

    Много лет внутри ВМ используем nobarrier, но лучше почитать сначала доводы против.

    https://habr.com/ru/post/471906/


    1. CrushBy Автор
      23.11.2021 14:49

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

      Но если есть сервер с асинхронной репликой с такими же характеристиками как и основной (что получается в 2 раза дороже), то я думаю можно для ускорения и nobarrier поставить, и full_page_writes отключить. А если уж совсем рисковать, то вообще вырубить fsync = off.


  1. vodopad
    23.11.2021 17:22

    >Это удобно тем, что потом можно легко при необходимости менять ОС, просто подключая диск с базой данных к другой виртуальной машине.
    А так точно можно делать? Просто в своё время я не смог настроить физическую репликацию между Centos 7 и Alt Linux, была ошибка о том, что операционные системы должны быть идентичными. Это наводит на определённые мысли...


    1. CrushBy Автор
      23.11.2021 17:25

      Много раз так делал - никаких проблем не возникало. Именно так переключались между CentOS 8 и CentOS 7 для тестирования. С физической репликой не доводилось настраивать между совсем разными ОС, но если честно не понимаю почему должны быть проблемы.


    1. derwin
      24.11.2021 04:50

      насколько помню, разница в винде и линуксе. Между линуксами не должно быть проблем. Это изза особенностей реализации сетевого стэка, который не захотели пилить под винду.


  1. vodopad
    23.11.2021 17:38

    Ни в коем случае не в обиду автору, но статья не понравилась. Новичка очень сильно запутает. Тут автор пытался охватить слишком обширный кусок, но всё это уместить в такую маленькую статью нереально.

    Например, установка СУБД рассчитана совсем на новичков. А дальше... Не сказано про pg_settings, откуда можно узнать, можно ли делать просто pg_reload_conf или нужно перезапускать всю СУБД. work_mem -- не написано, что если его будет не хватать, но будет много временных файлов, как это распознать (pgbadger и т.д.) тоже не сказано. shared_buffers -- тоже слабовато описано, вроде как рекомендуется ставить 25%, но если на сервере очень много RAM. maintenance_work_mem -- не написано, почему поставили 2 ГБ. В некоторых случаях это может быть слишком много. Чекпоинты -- ну новичок точно ничего не поймёт. vm.min_free_kbytes -- почему столько, что это вообще такое.

    Новичок не поймёт, а более опытный не узнает ничего нового. Лучше просто дать ссылку на pgtune уж тогда.


    1. CrushBy Автор
      23.11.2021 19:42
      +1

      Цель статьи в том, чтобы показать какие настройки мы устанавливаем для своих приложений. Тем самым я хотел показать, что в настройках PostgreSQL нет ничего сложного. И любой новичок, может установить просто параметры как в статье, глубоко не вникая в них. При этом все будет уже работать достаточно эффективно.

      Если нужно почитать более подробно про любые параметры, то они хорошо описаны в официальной документации (или на русском языке в postgres pro).



  1. Tibor128
    24.11.2021 06:34
    -3

    Коллеги, а не подскажите, где тут теперь хабр?


  1. ggo
    24.11.2021 12:55

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

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

    Synchronous_commit также отключаем, так как мы не пишем банковские системы. Если сервер упадет (что бывает крайне редко), то нет принципиальной разницы успело пользователю прийти сообщение об успешном сохранении или нет. Зато все сохранения будут работать чуть-чуть быстрее.

    Тут важны детали. Очень странный критерий - банковская система.

    А правильный - если при коммите, хотим иметь гарантию, что изменения на диске, то ставим on.

    Если гарантия не нужна, можно отключить.


    1. CrushBy Автор
      24.11.2021 14:06

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

      А можете уточнить, о каком именно кэше идет речь ? Shared buffers общий для всех процессов. И про какие лимиты памяти на сессии идет речь ? Work_mem - это лимит даже не на запрос, а одну операцию.

      Тут важны детали. Очень странный критерий - банковская система.

      Да, это не критерий, конечно же. Тут скорее это критично для операций с распределенными транзакциями. Банковская система - это просто один из наиболее распространенных примеров, когда будет очень плохо, если в одной системе деньги списались, а в другой - нет.


    1. Veidt
      26.11.2021 10:56

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

      Проблема в том, что в Postgres похоже есть явная утечка памяти (во всяком случае при работе с временными таблицами и большом количестве разных сложных запросов). То есть долго существующие соединения при такой схеме постепенно подъедают память, пока не сожрут ее полностью. Это очень хорошо на графиках мониторинга видно (выключаешь перестарт соединений, линия потребления начинает постепенно уходить вниз до нуля, после чего здравствуй своп / oom killer и сервер БД ложится). Видимо поэтому существуют мифы, что Postgres не тянет больше 1к одновременных соединений и вообще без пулинга из очень малого количества соединений не может эффективно работать.

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


  1. puyol_dev2
    06.12.2021 09:50

    Мусорная статья. Конфигурации сервера нет. Какая используется ОС и версии Postgre информации нет. Настройки приведены не понятно для чего