Аннотация


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

Введение


В ряде задач практически неограниченного масштабирования по объему обрабатываемых транзакций можно достичь, используя распределённые системы, в которых тем или иным способом поток транзакций распределяется на большое количество серверов. Такое масштабирование часто называют “горизонтальным”. Однако, универсального распределенного решения не существует, кроме того, распределённость имеет свою цену. Архитектура системы должна заранее проектироваться как распределённая. Распределенные системы менее гибки, чем монолитные, к тому же они сложнее в эксплуатации и требуют более высокой квалификации персонала. Одни задачи легче поддаются распараллеливанию, другие — сложнее. Поэтому спрос на высокопроизводительные монолитные системы существует, и достижение возможно лучших результатов по производительности в рамках одного сервера было и остается важной задачей. Это часто называют “вертикальным масштабированием”.

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

Для решения таких проблем существуют механизмы управления доступом к ресурсам — использование блокировок, а также пригодные в некоторых случаях неблокирующие (lock-free) подходы. Рост производительности этих механизмов, а также детализация блокировок дает возможность снизить издержки, связанные с одновременным (конкурентным) доступом.

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

Исследования масштабирования PostgreSQL


В 2012 году на конференции PgCon Роберт Хаас из компании EnterpriseDB представил результаты исследований производительности (числа запросов на чтение в секунду) PostgreSQL в зависимости от количества одновременно действующих клиентских сессий (Рис.1). Измерения производились на платформе amd64 с помощью входящего в состав PostgreSQL средства pgbench.

image
Рис.1. График из доклада Р. Хааса на конференции PgCon, 2012 г., случай для 32 ядер

Прежде чем анализировать этот график, сделаем несколько важных замечаний. Архитектура PostgreSQL такова, что один клиент обслуживается одним однопоточным процессом на сервере, а работа с диском ведется через буфер, находящийся в разделяемой между всеми такими процессами оперативной памяти. Таким образом, с одной стороны, возможна одновременная независимая работа некоторого количества клиентских сессий, однако сами данные, находящиеся в разделяемой памяти (shared memory), являются общим ресурсом, к которому осуществляется конкурентный доступ. Общим ресурсом является и дисковая подсистема, но в данном исследовании работа с ней не рассматривалась. Все данные уже находились в буферах, поэтому проведенные измерения показывают максимально достижимую производительность.

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

Рис.1. показывает, что при переходе от версии 9.1 к 9.2 благодаря совершенствованию механизмов блокировки устойчивый рост производительности стал наблюдаться до как минимум 32 одновременно работающих с CPU клиентов (в тесте была использована 32-ядерная машина, поэтому при дальнейшем увеличении количества клиентов производительность больше не росла). Чуть позже Р. Хаасом были проведены аналогичные измерения на 64-х ядрах (Рис.2). Видно, что близкая к линейной вертикальная масштабируемость сохраняется и при этом количестве ядер.

image
Рис.2. График из доклада Р. Хааса на конференции PgCon, 2012 г., случай для 64 ядер

Эти графики и стоящие за ними исследования вдохновили нас на то, чтобы посмотреть, как поведет себя PostgreSQL современных версий на современных многопроцессорных серверах. Этим заинтересовалась компания IBM, поставляющая такие сервера на платформе Power (бизнес, связанный с x86-серверами, IBM уступила Lenovo в 2014 г. вслед за ПК). Вот что из этого вышло.

Наши исследования


В феврале 2015 года наиболее известные российские разработчики PostgreSQL основали компанию “Постгрес Профессиональный” (Postgres Professional), целью которой стало развитие СУБД PostgreSQL и оказание полного спектра связанных с ней услуг. Создание компании позволило в несколько раз увеличить объем работ по разработке PostgreSQL в России, дало возможность проводить прикладные исследования и вступать в партнерские отношения с производителями программного обеспечения и аппаратных средств. Компания Postgres Professional с самого начала проявила интерес к партнерству с IBM и вошла с партнерскую сеть IBM PartnerWorld. В мае 2015 г. Москву посетила Терри Вирниг (Terri Virnig), вице-президент IBM по экосистеме и стратегии Power, которая подтвердила высокую степень заинтересованности IBM в PostgreSQL и его адаптации для достижения максимальной производительности на платформе Power.

В рамках партнерских отношений, в августе 2015 г. в клиентском центре IBM в Пoкипсии (Poughkeepsie, NY) для тестирования PostgreSQL был выделен сервер IBM E880 в конфигурации с 2 узлами, каждый узел содержал по 4 восьмиядерных процессора с технологией SMT (simultaneous multithreading), каждое ядро позволяет получить до 8 потоков. Таким образом, эта конфигурация обеспечивает до 2*4*8*8=512 потоков исполнения (или в терминологии IBM “Logical CPU”, LCPU). Важно заметить, что реализация SMT в IBM Power 8 отличается от ее реализации в процессорах Intel (известной как Hyperthreading), и в отличие от последней дает заметный, до двукратного, прирост производительности на задачах СУБД PostgreSQL. Количеством потоков (LCPU) можно управлять, устанавливая их число равным 1, 2, 4 или 8 на ядро CPU. Техника тестирования была предварительно отработана на менее мощных серверах в Москве. Тесты, как и у Р. Хааса, проводились при помощи входящей в состав PostgreSQL утилиты pgbench.

Сценарий тестирования представлял собой запросы на поиск по B-tree индексу, целиком помещенному в память. Сервер PostgreSQL запускался на большом LPAR, и при помощи встроенных в ядро Linux средств в условиях NUMA-архитектуры закреплялся на определенном количестве numa-нод. Успешность теста заключалась в достижении как можно бо?льшего количества транзакций в секунду (TPS).

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

image
Рис.3. Производительность в зависимости от числа клиентов, для различного числа LCPU

Для того, чтобы понять, почему при добавлении LCPU производительность падала, нужно представить себе устройство сервера IBM E880. В нём есть две материнские платы, на каждой из которых расположено по 4 процессора. Между собой материнские платы связаны гиперконнектом. Более детально про внутреннее устройство серверов на IBM Power 8 можно почитать в цикле статей от IBM. Гиперконнект, хоть и очень быстрый, но оказался предсказуемо медленнее, чем взаимодействие процессоров, находящихся на одной материнской плате. И это будет заметно не только в PostgreSQL, но и в любой другой системе, где процессы взаимодействуют через общую память. Поэтому в дальнейших исследованиях, в узких местах, мы исключали влияние гиперконнекта и использовали один вычислительный узел, т.е. 256 LCPU при SMT = 8.

Вначале был испытан последний на момент тестирования официальный релиз PostgreSQL (версия 9.4.5), который показал линейное масштабирование только до 32 клиентов, затем — текущие версии из веток 9.5 и 9.6, на которых рост, близкий к линейному, продолжался до 64 клиентов.


Рис.4. Производительность в зависимости от числа потоков

В версии 9.5 видно улучшение по сравнению с версией 9.4, которое можно объяснить оптимизацией механизмов LWLock (будет пояснено ниже).

Итак, измерения были проведены. Но мы решили не останавливаться на этом, а продвинуться дальше — попытавшись понять, есть ли возможность, обнаружив узкое место в коде, устранить его и достичь лучшего результата. Для поиска узких мест была использована утилита perf, которая показала, что около 32% процессорного времени занимает выполнение функции s_lock (Рис.5).

32.10% postgres       [.] s_lock
7.77%  postgres       [.] GetSnapshotData
2.64%  postgres       [.] AllocSetAlloc
1.40%  postgres       [.] hash_search_with_hash_value
1.37%  postgres       [.] base_yyparse
1.36%  postgres       [.] SearchCatCache
1.32%  postgres       [.] PinBuffer
1.23%  postgres       [.] LWLockAcquire
1.05%  postgres       [.] palloc
1.01%  postgres       [.] ReadBuffer_common
0.99%  postgres       [.] LWLockRelease
0.94%  libc-2.17.so   [.] __memset_power7

Рис.5. Пример вывода perf top во время тестирования

Чтобы идентифицировать места в программном коде PostgreSQL, ответственные за вызов s_lock, был использован отладчик gdb (производительность однопоточного perf для построение графа зависимостей оказалась недостаточной). Выборочные подключения gdb показали, что в большинстве случаев вызов s_lock происходит из функций PinBuffer и UnpinBuffer, служащих для увеличения и уменьшения счетчика ссылок на буфер, в котором хранится определённый блок данных. Счётчик ссылок защищён спинлоком BufferDesc.buf_hdr_lock, и поэтому для работы с ним нужно предварительно захватить данный спинлок. Чтобы лучше понять это место, давайте рассмотрим, какие виды блокировок есть в PostgreSQL, и зачем они нужны.

Блокировки в PostgreSQL: какие и зачем столько разных?


В PostgreSQL реализовано несколько типов блокировок, каждый из которых решает свои задачи.
  1. Spinlock (спинлок) – самый простой тип блокировок, который имеет только два состояния “занято” и “свободно”. Процедура взятия спинлока состоит из последовательных попыток поменять сменить его состояние со “свободно” на “занято”. Для того, чтобы избежать гонок, спинлоки использую атомарную операцию Test-and-set (TAS). Процессы, желающие получить спинлок не выстраиваются в очередь. Со стороны СУБД не поддерживается ни мониторинг спинлоков, ни автоматическое определение deadlock’ов на спинлоках. Всем этим было пожертвовано ради высокой производительности на очень коротких операциях. PostgreSQL содержит ассемблерные реализации спинлоков под различные аппаратные платформы. В случае когда ассемблерная реализация спинлоков отсутствует, используется эмуляция спинлоков через UNIX семафоры, которые, однако, имеют куда меньшую производительность.
  2. Lightweight lock (LWLock) – более сложный тип блокировок, который имеет два уровня shared и exclusive. Одновременно может быть взято или неограниченное число shared блокировок, или только одна exclusive. Реализация LWLock претерпела существенные изменения между версиями PostgreSQL 9.4 и 9.5. В версиях 9.4 и ниже LWLock содержал в себе набор полей, защищённых спинлоком. Начиная с версии 9.5 большая часть операций с LWLock’ом осуществляется минуя спинлок, с помощью атомарных операций с “переменной состояния” LWLock’а. Процессы, желающие получить LWLock выстраиваются в очередь, благодаря чему при длительном ожидании LWLock’а ресурс шины не расходуется зря. Автоматическое определение deadlock’ов для LWLock’ов не поддерживается, алгоритмы СУБД выверены таким образом, чтобы deadlock’и никогда не возникали. Работу по поддержке мониторинга LWLock’ов ведёт сотрудник Postgres Professional, Ильдус Курбангалиев (aka ildus) в рамках более общей задачи мониторинга событий ожидания.
  3. Heavyweight lock (HWLock) – наиболее “продвинутый” тип блокировок. Имеет много уровней и нетривиальную матрицу конфликтов. На таблицы индексы и другие объекты БД, HWLock’и устанавливаются, как неявно теми SQL-командами, которые их используют, так и явно командой LOCK. Для мониторинга HWLock’ов предназначено системное представление pg_locks. Как следует из названия, HWLock’и – это довольно тяжеловесный механизм, поэтому для их получения DML-командами, предусмотрен специальный механизм fastpath locking. Кроме этого, поскольку порядок получения HWLock’ов зависит от действий пользователя, нельзя гарантировать отсутствие deadlock’ов, поэтому в PostgreSQL реализован механизм автоматического обнаружения deadlock’ов.


Помимо вышеперечисленных блокировок, в PostgreSQL присутствуют row-level locks, predicate locks, advisory locks и другие, которые, в свою очередь, реализованы с использованием перечисленных выше трёх базовых типов блокировок.

Как мы ускорили PostgreSQL


Чтобы понять, получится ли повысить производительность в обнаруженном нами узком месте, мы, совместно с ведущим разработчиком PostgreSQL Андресом Фройндом (Andres Freund), разработали патч, заменяющий спинлок buf_hdr_lock на атомарные операции с “состоянием” буфера. Таким образом, разработанный патч реализует для буферов оптимизацию аналогичную той, что была сделана для LWLock’ов в версии 9.5: функция PinBuffer стала выполнять операцию Compare and Swap (CAS) в цикле, а функция UnpinBuffer – атомарный декремент. Это позволило продлить область роста, близкого к линейному, примерно в 2 раза и достичь производительности более 800 тысяч TPS (Рис.6).


Рис.6. Результаты измерения производительности разработанного патча.

Однако, на этом мы тоже не остановились. Если посмотреть на ассемблерную реализацию операции CAS под power8, то можно увидеть, что она представляет собой цикл (Рис. 7).

# Входные параметры:
#  r3 – старое значение, r4 – новое значение
#  r5 – адрес атомарной переменной
.L1: lwarx 9,0,5
     cmpw 0,9,3
     bne- 0,.L2
     stwcx. 4,0,5
     bne- 0,.L1
.L2: isync

Рис 7. Реализация атомарной операции CAS на ассемблере Power 8.

Таким образом, выполнение операции CAS в цикле – это цикл в цикле. При большой конкуренции за изменение значения, это может быть заметно медленнее, чем выполнение одинарного цикла.

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

# Входные параметры:
#  r3 – инкремент
#  r5 – адрес атомарной переменной
.L1: lwarx 9,0,5
     add 9,9,3
     stwcx. 9,0,5
     bne- 0,.L1
     isync

Рис 8. Реализация атомарной операции атомарного инкремента на ассемблере Power 8.

Очевидно, что между командами lwarx и stwcx могут располагаться и более сложные вычисления.

Исходя их этого родилось две идеи для дальнейших оптимизаций.
  1. Реализовать критичные по производительности функции, таких как PinBuffer и LWLockAttemptLock с помощью ассемблерных вставок. Минусом данного варианта является то, что мы выходим за уровень абстракции в виде атомарных операций, который предоставляет нам компилятор.
  2. Альтернативная идея состоит в том, чтобы использовать атомарный инкремент и реализовать так называемый “оптимистичный подход”. В PinBuffer и LWLockAttemptLock можно делать атомарный инкремент “переменной состояния”, а затем, уже по фактически установленному значению, проверить, имели мы право на такое изменение состояния или нет. В случае, если мы не имели право изменять состояние таким образом, отменить это изменение операции атомарного инкремента. Расчёт здесь делается на то, что отменять изменения придётся лишь в очень небольшой доле случаев, в этом и заключается “оптимизм” подхода. Разумеется, все остальные функции, которые работают с “переменной состояния”, должны также учитывать это.

Обе изложенные идеи были реализованы в виде патчей. При этом версия с атомарным инкрементом была проверена в нескольких вариантах, чтобы можно было оценить отдельно эффект по применения данного подхода к PinBuffer и к LWLockAttemptLock.


Рис.9. Результаты измерения производительности различных патчей.

На графике (Рис. 9) показано сравнение производительности следующих патчей:
  1. pinunpin-cas – PinBuffer выполняется с помощью цикла CAS операций.
  2. pinunpin-increment – PinBuffer выполняется с помощью “оптимистичного” атомарного инкремента.
  3. pinunpin-cas-lwlock-increment – PinBuffer выполняется с помощью цикла CAS операций, LWLockAttemptLock выполняется с помощью “оптимистичного” атомарного инкремента.
  4. pinunpin-lwlock-increment – PinBuffer и LWLockAttemptLock выполняются с помощью “оптимистичного” атомарного инкремента.
  5. pinunpin-lwlock-asm – PinBuffer и LWLockAttemptLock реализованы на ассемблере.

Видно, что эффекта от “оптимистичного” атомарного инкремента в PinBuffer почти нет, зато применение того же подхода к LWLockAttemptLock даёт очень большой эффект. И примерно такой же эффект можно получить с помощью ассемблерный оптимизаций.

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

13.75% postgres       [.] GetSnapshotData
4.88%  postgres       [.] AllocSetAlloc
2.47%  postgres       [.] LWLockAcquire
2.11%  postgres       [.] hash_search_with_hash_value
2.02%  postgres       [.] SearchCatCache
2.00%  postgres       [.] palloc
1.81%  postgres       [.] base_yyparse
1.69%  libc-2.17.so   [.] __memset_power7
1.63%  postgres       [.] LWLockRelease
1.56%  libc-2.17.so   [.] __memcpy_power7
1.33%  postgres       [.] _bt_compare
0.99%  postgres       [.] core_yylex
0.99%  postgres       [.] expression_tree_walker

Рис.10 Пример вывода perf top во время тестирования после применения экспериментального патча pinunpin-lwlock-asm.

Следует заметить, что не только наша команда занимается вертикальной масштабируемостью постгреса. Общее направление в сторону более экономичного использования блокировок существует, об этом, в частности, рассказывалось на октябрьской конференции pgconf.eu в докладе Андреса Фройнда). В том числе, как было уже упомянуто, в версию 9.5 вошли оптимизации LWLock’ов.

Выводы


В экспериментальном режиме была произведена замена системы низкоуровневых блокировок в PostgreSQL, что позволило примерно вдвое увеличить масштабируемость по количеству процессорных ядер и тем самым повысить пригодность PostgreSQL для эффективного использования на очень больших серверах Power 8.

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

OpenSource IBM Advanced Toolchain (которая по сути является версией gcc с оптимизированными под PPC runtime-библиотеками ) показал большую производительность, чем ванильный gcc и рекомендуется для сборки. XLC под linux имеет пока не решенные проблемы с PostgreSQL и не рекомендуется.

PostgreSQL показал уверенную работу в LPAR. Hardware partitioning при помощи PowerVM оказался мощной и удобной системой виртуализации, при помощи которой можно наращивать производительность отдельного LPAR и находящегося внутри него сервера PostgreSQL.

SMT в Power8 может дать более чем двукратный прирост производительности на задачах, аналогичных тем, на которой производилось наше тестирование.

Последний патч на asm позволил достичь суммарной производительности более 2 млн транзакций в секунду на 48 ядер Power8. Достигнуто это было на двух экземплярах PostgreSQL запущенных одновременно на разных вычислительных узлах.

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

Благодарности


Данное исследование было проведено в рамках деятельности российского вендора PostgreSQL — компании Postgres Professional, участника программы IBM Partner World. Авторы благодарят московское отделение IBM и лично Дениса Сосновцева и Александра Лунякова за проявленный интерес к исследованиям Postgres Professional и содействие в получении доступа к технике для тестирования, и Ивана Гончарова за ценные консультации по её использованию, а также вице-президента IBM Терри Вирниг за поддержку и персонал клиентского центра IBM в Пукипси и Кембриджского инновационного центра IBM за организацию тестирования.

Также мне хотелось бы поблагодарить моих соавторов — сотрудников Postgres Professional Дмитрия Васильева (aka vadv), который непосредственно прогонял тесты и написал значительную часть данной статьи, и Юрия Журавлёва (aka stalkerg), который разработал первоначальную версию патча для перевода PinBuffer/UnpinBuffer на атомарные операции.

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


  1. CPro
    17.11.2015 08:04
    +3

    Класс! Это чисто экспериментальные патчи, или в каком-то виде будут внедрены в будущих версиях?


    1. varanio
      17.11.2015 08:44
      -4

      А у вас есть стоядерный сервер?)


      1. Ivan22
        17.11.2015 09:41

        я так понимаю это даст прирост на любой среде где количество процессов больше чем количество ядер. Чем плохо?


        1. vadv
          17.11.2015 09:47

          Этот патч ускорит доступ к часто-используемым страницам в условиях конкрентного доступа. На многоядерных серверах просто увеличивается вероятность данного события.


        1. smagen
          17.11.2015 12:31

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


    1. vadv
      17.11.2015 09:52
      +2

      Сообщество PostgreSQL, по понятным причинам, очень ревностно относится к подобным изменениям. Но обсуждения в pgsql-hackers уже проходят. Пока патчи один за одним улучшают производительность.


      1. smagen
        17.11.2015 10:24
        +1

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


  1. fingoldo
    17.11.2015 09:50

    Спасибо за статью и за проделанную работу по оптимизации )
    Freund должно читаться как «Фройнд».
    «Распределенные системы менее гибки, чем монолитные, к тому же они сложнее в эксплуатации и требуют более высокой квалификации персонала.» — вот с этим высказыванием категорически не согласен, на мой взгляд, всё как раз наоборот — обслуживание мэйнфрейма требует специальных знаний, в отличие от сети дешёвых персоналок.
    По самому продукту, мне кажется, за средства SQL, расширяемости, надёжности PostGres можно поставить 5-ку, но, как бы, в 2015 (2015, Карл!) году когда даже в десктопах торчат по 6-8 ядер, обрабатывать клиентский запрос ОДНИМ потоком… Мягко говоря, вызывает недоумение. В DWH типичный сценарий — один или несколько пользователей с долгими запросами. Почему клиент IBM с 512 ядрами должен ожидать, пока запрос будет выполняться в 1 поток?????
    Далее. Без automatic standby failover это просто несерьёзное решение. Отсутствие партицирования — тоже огромный недостаток. Передайте Андресу: спасибо, конечно, но они там что, в мире одноядерников живут??? :-)


    1. vadv
      17.11.2015 10:03
      +1

      Спасибо за комментарий!

      1. Почему вы заявляете о том, что в PostgreSQL нет партиционирования? Если вам это интересно почитайте про наследование в PostgreSQL.
      2. Решений «automatic standby failover» столько же, сколько предложено HA, встроенные инструменты PostgreSQL позвляют это делать. Да, это требует более высокой квалификации от обслуживающего персонала, но в отличие от других комерческих СУБД PostgreSQL предоставляет тут свободу выбора.


      1. fingoldo
        17.11.2015 10:54

        «1. Почему вы заявляете о том, что в PostgreSQL нет партиционирования? Если вам это интересно почитайте про наследование в PostgreSQL.»
        Потому что в PostgreSQL нет партиционирования. Ну нет его. Схема, на которую вы указываете, это просто головная боль. Сравните с партицированием в Oracle.
        2. хотелось бы multimaster replication with auto failover работающее из коробки, а не набор костылей, не поддерживающий новые версии ядра, не обновляемыей годами, работающий лишь на некоторых ОС и т.п.
        3. Как я понял, с Вашей точки зрения в PostGres с обоими пунктами всё «зашибись»?


        1. galaxy
          17.11.2015 18:47
          +1

          1. В чем головная боль-то? Давайте поконкретнее
          2. А еще чего бы вам хотелось? Все-таки standby failover — это одно и это есть в PG сто лет как, а multimaster replication — совсем другое.
          3. С моей точки зрения (извините, что вмешиваюсь): по 1 — напишите, что вас не устраивает, т.к. для моих нужд там все зашибись, да; 2 — репликация (в общем смысле, не какой-то конкретный ее вид) — не сильная сторона PG, но в последнее время идет хорошее развитие в этом направлении, хотя и имхо с запозданием на x лет


          1. fingoldo
            18.11.2015 10:26

            «1. В чем головная боль-то? Давайте поконкретнее»
            www.postgresql.org/docs/devel/static/ddl-partitioning.html
            потому что админу предлагается создавать все партиции, условия на таблицах, индексы вручную, что, я считаю, тупо. особо радуют «We must redefine the trigger function each month so that it always points to the current partition.», раздельный VAcuum для партиций, да и вообще весь раздел 5.10.6. Caveats.
            Если Вам эти все действия в радость (вместо указания ключа и схемы партицирования при создании/модификации таблицы, как в Оракле) — вопросов не имею.
            2. извиняюсь за неточность, имел в виду полный цикл standby failover, описанный как STONITH тут
            www.postgresql.org/docs/devel/static/warm-standby-failover.html
            3. радует, что подвижки идут, но очень уж мизерные.
            с другой стороны, я, конечно, понимаю, что никто не держит, пиши свой движок или покупай Оракула за $50k /ядро ))


            1. vadv
              18.11.2015 10:52
              +2

              1. Сделать человеческий партишининг есть в планах, но пока и без этого postgresql не хватает более важных вещей: awr, заморозка планов, трасировка…
              Вот буквально вчера был внутренний семинар, где наш разработчик представил рабочее решение по статистике, которая предоставляется для планера и ее миграции в другие инстансы/версии postgresql.
              Если не ошибаюсь, на прошлой неделе Юра Журавлев представил уже рабочее решение по прикреплению планов к конкретным запросам и экспорт на другие машины.

              2. Нормальный фенсинг дает peacemaker, об этом был один из наших докладов на HL: http://www.slideshare.net/AlekseyChizhkoff/dancing-cluster, скоро будет рекомендованый пакеты. К сожалению, а может быть к счастью, встраивать такое решение в поставку с «базой» такое решение сообщество не пойдет. Дополнительно мы проталкиваем патчи такого рода: failover на уровне протокола клиента, read-only на уровне протокола.


            1. foboss
              18.11.2015 11:17

              А pg_partman не?

              pg_partman is an extension to create and manage both time-based and serial-based table partition sets. Sub-partitoning is also supported. Child table & trigger function creation is all managed by the extension itself. Tables with existing data can also have their data partitioned in easily managed smaller batches. Optional retention policy can automatically drop partitions no longer needed. A background worker (BGW) process is included to automatically run partition maintenance without the need of an external scheduler (cron, etc) in most cases.


            1. galaxy
              18.11.2015 13:42

              We must redefine the trigger function each month so that it always points to the current partition

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


            1. vadv
              18.11.2015 14:29
              +3

              >индексы вручную

              кстати можно и так:

              postgres=# create table t (i int);
              CREATE TABLE
              postgres=# create index t_idx_i on t(i);
              CREATE INDEX
              postgres=# create table child_1_t (like t including indexes) inherits(t);
              NOTICE:  merging column "i" with inherited definition
              CREATE TABLE
              postgres=# \d+ child_1_t
                                    Table "public.child_1_t"
               Column |  Type   | Modifiers | Storage | Stats target | Description 
              --------+---------+-----------+---------+--------------+-------------
               i      | integer |           | plain   |              | 
              Indexes:
                  "child_1_t_i_idx" btree (i)
              Inherits: t
              


    1. vadv
      17.11.2015 10:16
      +1

      3. По поводу заявлений о расширяемости попробуйте, из «5-ку, но, как бы, в 2015»:

      1. утилизировать Xeon Phi, отправив туда паралельные вычисления
      2. создать новый тип данных без остановки сервера и описать его на C: индексы, операторы
      3. искать по схема-лесс данным по индексу через свой язык


      1. fingoldo
        17.11.2015 10:58

        1. какие проблемы с Phi? действуйте по образу и подобию
        wiki.postgresql.org/wiki/PGStrom
        2,3. не имеет существенного для меня значения, не возникало такой необходимости
        навскидку, чем бинарное хранение в файловой системе не устраивает для 3?


        1. stalkerg
          17.11.2015 11:18

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


        1. vadv
          17.11.2015 11:55
          +1

          > wiki.postgresql.org/wiki/PGStrom
          я не говорил про конкретный проект. вы можете написать небольшой кусок кода, которую обвяжете в функцию, которую вы можете отправить на Phi.

          >2,3. не имеет существенного для меня значения
          Энтерпрайз до этого еще не дорос.

          >чем бинарное хранение в файловой системе не устраивает для 3?
          что вы имеете ввиду?


        1. phprus
          18.11.2015 10:12
          +1

          > 1. какие проблемы с Phi? действуйте по образу и подобию PGStrom
          Не думаю, что для Phi этот образ будет правильным путем.
          В отличие от GPU Nvidia, Xeon Phi все время шел от ускорителя в сторону центрального процессора и с последней версии Knights Landing стал полноценным CPU, которому не нужен отдельный управляющий Xeon и медленная шина PCIe для обменов данными.

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


          1. stalkerg
            18.11.2015 14:31

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


    1. vadv
      17.11.2015 10:18

      И да, привет от ORA-600 ;)


      1. fingoldo
        17.11.2015 11:01
        -1

        Ага, Оракл ужасно нестабилен, по моему скромному опыту. Вечно ошибки начинают сыпаться, то база не открывается, то восстановление делать надо.
        Тогда как PostGres, Access, MS SQL из разряда «поставил и забыл».


        1. vadv
          17.11.2015 11:47
          +1

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


    1. smagen
      17.11.2015 10:22
      +2

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

      Если говорить про железо и ОС, то я согласен. Но если говорить про кластер БД, который обеспечивает строгую consistency, то один экземпляр БД администрировать намного проще. Но, конечно же, всё зависит от конкретной ситуации.

      По самому продукту, мне кажется, за средства SQL, расширяемости, надёжности PostGres можно поставить 5-ку, но, как бы, в 2015 (2015, Карл!) году когда даже в десктопах торчат по 6-8 ядер, обрабатывать клиентский запрос ОДНИМ потоком… Мягко говоря, вызывает недоумение. В DWH типичный сценарий — один или несколько пользователей с долгими запросами. Почему клиент IBM с 512 ядрами должен ожидать, пока запрос будет выполняться в 1 поток?????

      Работа над инфраструктурой для параллельного выполнения запросов ведётся уже давно. Лиха беда начало, уже есть первые результаты. И что важно, благодаря механизму Custom Nodes, можно будет различные способы распараллеливания добавлять в виде расширений, на pgconf.eu, например, уже демонстрировалось распараллеливание агрегатов.

      Далее. Без automatic standby failover это просто несерьёзное решение.

      В постгресе есть все необходимые средства для того, чтобы сверху можно было обеспечить High Availability(HA). Есть несколько «кластерных обвязок», которые обеспечивают HA на основе этих средств. Мы сейчас делаем свою, которая будет отличаться простотой настройки и администрирования, и будем осуществлять для неё вендорскую поддержку.

      Отсутствие партицирования — тоже огромный недостаток.

      Сейчас в постгресе принято использовать партицирование через наследование, что сопряжено с рядом недостатков. В частности, невозможность hash-партицирования, медленное планирование запросов при большом числе партиций и т.д. Но благодаря механизму Custom Nodes стало возможно обойти эти ограничения не трогая ядро. Мы сейчас работает над своим расширением для партицирования, которое будет лишено этих недостатков.

      Спасибо за содержательный комментарий и разумную критику!


      1. fingoldo
        17.11.2015 10:49
        -1

        Новость про Parallel Seq Scan в, возможно, 9.6 — это круто! Пока приходится запрос разбивать на части вручную, создавая множество соединений с базой.

        «В постгресе есть все необходимые средства для того, чтобы сверху можно было обеспечить High Availability(HA)» Нельзя не согласиться. Вот потому и удивляет, что не находят времени из коробки дописать логику failover-ов и heartbeat-ов и для этого приходится писать своё приложение.

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


        1. vadv
          17.11.2015 11:45
          +2

          >Вот потому и удивляет, что не находят времени из коробки дописать логику failover-ов и heartbeat-ов и для этого приходится писать своё приложение

          а отдавать по 50k $ за ядро вас не удивляет? :)


        1. vadv
          17.11.2015 12:07
          +1

          >всё надо делать вручную
          у вас недостаточный опыт общения с PostgreSQL. откройте для себя экстеншены.


          1. smagen
            17.11.2015 12:28
            +2

            Имеется ввиду pg_partman. Про то, что он имеет свои ограничения, мы знаем, работаем над этим. Про результаты на хабр напишем обязательно.


            1. fingoldo
              17.11.2015 13:56
              +1

              Спасибо, я потестирую pg_partman.


    1. zen
      17.11.2015 15:42
      +3

      Freund должно читаться как «Фройнд»
      Точно, друг означает.
      но, как бы, в 2015 (2015, Карл!) году когда даже в десктопах торчат по 6-8 ядер, обрабатывать клиентский запрос ОДНИМ потоком…

      Уже сейчас можно писать расширения, которые могут пользовать несколько ядер. Hans-Jurgen Schonig скоро выпустит такое расширение (не трогая ядра базы!) для агрегатов, вот его пример:
      agg=# SET agg.hash_workers = 1;
      SET
      Time: 0.269 ms
      agg=# SELECT val1, 
      avg(id),
      sum(id),
      min(id),
      max(id)
      FROM t_agg 
      GROUP BY 1 
      LIMIT 2;
      val1 | avg | sum | min | max 
      ------+----------------------+----------------+-----+---------
      34 | 2500014.000000000000 | 12500070000000 | 34 | 4999994
      25 | 2500005.000000000000 | 12500025000000 | 25 | 4999985
      (2 rows)
      Time: 62983.545 ms
      agg=# SET agg.hash_workers = 10;
      SET
      Time: 0.161 ms
      agg=# SELECT val1, 
      avg(id),
      sum(id),
      min(id),
      max(id)
      FROM t_agg 
      GROUP BY 1 
      LIMIT 2;
      val1 | avg | sum | min | max 
      ------+----------------------+----------------+-----+---------
      34 | 2500014.000000000000 | 12500070000000 | 34 | 4999994
      25 | 2500005.000000000000 | 12500025000000 | 25 | 4999985
      (2 rows)
      Time: 8265.947 ms
      
      Таким образом, многое в ваших руках уже сейчас. Но это конечно только часть того, что планируется.

      Отсутствие партицирования — тоже огромный недостаток.
      Да, схему секционирования надо менять, мы этим тоже занялись и даже есть рабочее название проекта «pg_pathman», которые поможет нам генерить только нужные path-ы и передавать их планеры и избежать гигантского оверхеда перебора кучи ненужных планов. Это тоже часть работ по секционированию, но приятно, что она вполне обозримая и уже даст возможность работать с большим количеством разбиений. Возможно, надо будет объединиться с pg_partman.
      Передайте Андресу
      Передал, более того, Андрес приедет зимой на pgconf.ru с докладом по масштабируемости постгреса, так что вы сможете с ним поговорить лично.


      1. fingoldo
        17.11.2015 16:46
        +1

        круто ) спасибо )


  1. dimv36
    18.11.2015 09:26
    +2

    Спасибо за интереснейшую статью!

    Не могли бы Вы описать подробнее, как вы определяли узкие места?


    1. vadv
      18.11.2015 10:37
      +3

      Это было не сложно, основные инструменты: perf && gdb:

      Про perf вы можете почитать подробнее тут: https://wiki.postgresql.org/wiki/Profiling_with_perf

      GDB использовали потому что perf не мог построить за приемлемое граф вызовов, для этого в gdb необходимо вызвать backtrace:
      gdb --batch --command=file.script --pid $PID_OF_BACKEND


  1. Nastradamus
    18.11.2015 21:40
    +2

    Отличная работа! Спасибо за статью.

    Авторы, не могли бы вы поделиться, пожалуйста, скриптами/командами для наполнения базы для pgbench? И скриптами для тестирования, если такие есть.

    Заранее, большое спасибо!


    1. vadv
      19.11.2015 08:51
      +3

      Держите: https://gist.github.com/vadv/b6dc474ded1261a5b643
      В gist'е находится часть с необходимым ddl и питоновским скриптом, надстройкой над pgbench.
      Результаты тестирования хранятся в базе results.


      1. Nastradamus
        19.11.2015 10:00
        +1

        Огромное спасибо! Вопрос очень актуальный для меня — изучаю методики тестирования Постгреса, так есть основания полагать, что в своих тестах я что-то делал не так. :)


  1. tsafin
    19.11.2015 14:00
    +1

    По поводу предела масштабирования на графике №4 — я бы на вашем месте поигрался бы с affinity (Linux — taskset, AIX — bindprocessor), заставляя планировщик не мигрировать запущенные процессы на другие сокеты. Не уверен что это что-то бы дало, но поиграться стоило.


    1. vadv
      19.11.2015 15:06
      +1

      Мы увеличивали sched_migration_cost в 50 раз. Биндингом на ноды занимался numactl.


      1. tsafin
        19.11.2015 15:24

        Хорошо, то есть какие выводы можно сделать? Архитектура с многопроцессным приложением, работающим через одну разделяемую память плохо работает в NUMA? Надо аллоцировать на каждом NUMА узле свою отдельную память с независимым набором процессов?


        1. vadv
          19.11.2015 15:35

          По нашим оценкам, в условиях этого теста, NUMA (запускаем все процессы сервера на одной numa-ноде, а память на крайней по растоянию) давала деградацию 10-20%.

          И «huge drop in performance when going from 15-cores on1-socket to 30-cores on 2-socket» как описано тут:
          https://events.linuxfoundation.org/sites/events/files/slides/linuxcon-2014-locking-final.pdf мы не наблюдали.

          Большая часть проблемы — не хардваная, а софтовая — это синхронизация экслюзивного доступа к ресурсу — странице памяти. Эту проблему мы и решали.


          1. stalkerg
            19.11.2015 16:00

            Большая часть проблемы — не хардваная, а софтовая — это синхронизация экслюзивного доступа к ресурсу — странице памяти. Эту проблему мы и решали.

            Это ИМХО и есть хардварная проблема, мы просто придумали как меньше синхронизироваться т.е. трогать эту проблему.
            khizmax отличный цикл статей написал, всем советую для понимания.


            1. vadv
              19.11.2015 16:04
              +1

              имел ввиду эффективнее обойти софтово.