Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Область применения:
Azure Cosmos DB для PostgreSQL (на базе расширения Citus для PostgreSQL)
Работа распределенной базы данных на полную мощность обеспечивает высокую производительность. Однако для достижения такой производительности могут потребоваться некоторые изменения в коде приложения и моделировании данных. В этой статье рассматриваются некоторые из наиболее распространенных и эффективных методов повышения производительности.
Пул подключений на стороне клиента
Пул подключений содержит открытые подключения к базе данных для повторного использования. Приложение запрашивает подключение из пула при необходимости, и пул возвращает уже установленное, если это возможно, или устанавливает новое подключение. По завершении приложение возвращает подключение в пул, а не закрывает его.
Добавление пула подключений на стороне клиента — это простой способ повысить производительность приложения с минимальными изменениями кода. В наших измерениях выполнение инструкций вставки с одной строкой выполняется примерно на 24x быстрее в кластере с включенным пулом.
Примеры добавления пула в код приложения для конкретного языка см. в руководстве по стекам приложений.
Примечание.
Azure Cosmos DB для PostgreSQL также предоставляет пул подключений на стороне сервера с помощью pgbouncer, но в основном служит для увеличения ограничения подключения клиента. Производительность отдельного приложения больше выигрывает при наличии пула на стороне клиента, а не на стороне сервера. (Хотя обе формы объединения ресурсов можно использовать одновременно без вреда.)
Определение области распределенных запросов
Обновления
При обновлении распределенной таблицы попробуйте фильтровать запросы по столбцу распределения — по крайней мере когда это имеет смысл, если новые фильтры не меняют суть запроса.
В некоторых рабочих нагрузках это сделать легко. Транзакционные и операционные рабочие нагрузки, такие как мультитенантные приложения SaaS или Интернет вещей, распределяют таблицы по клиенту или устройству. Запросы ограничены идентификатором клиента или устройства.
Например, в нашем руководстве по мультитенантным приложениям есть таблица ads
, распределенная по company_id
. Наивный способ обновить объявление — выделить его следующим образом:
-- slow
UPDATE ads
SET impressions_count = impressions_count+1
WHERE id = 42; -- missing filter on distribution column
Хотя запрос однозначно идентифицирует строку и обновляет его, Azure Cosmos DB для PostgreSQL не знает, во время планирования, который сегментирует запрос будет обновляться. Расширение Citus устанавливает ShareUpdateExclusiveLock для всех сегментов для обеспечения безопасности, что блокирует другие запросы, пытающиеся обновить таблицу.
Несмотря на то, что id
достаточно для идентификации строки, можно добавить дополнительный фильтр, чтобы ускорить выполнение запроса:
-- fast
UPDATE ads
SET impressions_count = impressions_count+1
WHERE id = 42
AND company_id = 1; -- the distribution column
Планировщик запросов Azure Cosmos DB для PostgreSQL видит прямой фильтр в столбце распространения и точно знает, какой отдельный сегмент необходимо заблокировать. В наших тестах добавление фильтров для столбца распределения увеличило производительность параллельного обновления в 100 раз.
Объединения и выражения CTE
Мы видели, как инструкции UPDATE должны быть ограничены столбцом распределения, чтобы избежать ненужных блокировок сегментов. Другие запросы также выигрывают от определения области: как правило, это позволяет избежать затрат на ненужную перетасовку данных между рабочими узлами.
-- logically correct, but slow
WITH single_ad AS (
SELECT *
FROM ads
WHERE id=1
)
SELECT *
FROM single_ad s
JOIN campaigns c ON (s.campaign_id=c.id);
Мы можем ускорить выполнение запроса, отфильтровав столбец распределения company_id
в CTE и главной инструкции SELECT.
-- faster, joining on distribution column
WITH single_ad AS (
SELECT *
FROM ads
WHERE id=1 and company_id=1
)
SELECT *
FROM single_ad s
JOIN campaigns c ON (s.campaign_id=c.id)
WHERE s.company_id=1 AND c.company_id = 1;
В общем, при объединении распределенных таблиц старайтесь включать столбец распределения в условия объединения. Однако при соединении между распределенной и ссылочной таблицей это не требуется, поскольку содержимое ссылочной таблицы реплицируется на всех рабочих узлах.
Если добавление дополнительных фильтров ко всем запросам кажется неудобным, помните, что вспомогательные библиотеки для некоторых популярных платформ приложений упрощают работу. Инструкции:
Эффективное ведение журнала базы данных
Постоянное ведение журнала всех инструкций SQL увеличивает расходы. Согласно нашим измерениям, использование более разумного уровня ведения журнала повысило количество транзакций в секунду в 10 раз по сравнению с полным ведением журнала.
Для эффективной повседневной работы можно отключить ведение журнала, за исключением ошибок и аномально долго выполняющихся запросов:
Настройка | значение | причина |
---|---|---|
журнал_статистики_запросов | ВЫКЛ. | Избегайте расходов на профилирование |
длительность_лога | ВЫКЛ. | Нет необходимости знать длительность обычных запросов. |
журнал_заявлений | НИКАКОЙ | Не регистрируйте запросы без конкретной причины |
запись_мин_продолжительность_заявления | Значение, превышающее время, которое, по вашему мнению, должно занимать обычное выполнение запросов | Показывает аномально долго выполняющиеся запросы |
Примечание.
Параметры, относящиеся к журналам в управляемой службе, учитывают приведенные выше рекомендации. Их можно оставить как есть. Тем не менее мы иногда наблюдали, как клиенты изменяют параметры, чтобы сделать ведение журнала агрессивным, что приводило к проблемам с производительностью.
Конкуренция за блокировку
База данных использует блокировки для поддержания согласованности данных в режиме параллельного доступа. Однако для некоторых шаблонов запросов требуется чрезмерное количество блокировок, и существуют более быстрые альтернативы.
Состояние системы и замки
Прежде чем перейти к рассмотрению распространенных неэффективных блокировок, давайте разберемся, как просматривать блокировки и действия во всем кластере базы данных. citus_stat_activity — это подробное представление.
В этом представлении показано, помимо прочего, как запросы блокируются "событиями ожидания", включая блокировки. Группирование по wait_event_type дает представление о работоспособности системы:
-- general system health
SELECT wait_event_type, count(*)
FROM citus_stat_activity
WHERE state != 'idle'
GROUP BY 1
ORDER BY 2 DESC;
Значение NULL для wait_event_type
означает, что запрос ничего не ожидает.
Если в выводе активности статистики отображаются блокировки, вы можете просмотреть определенные заблокированные запросы с помощью citus_lock_waits
.
SELECT * FROM citus_lock_waits;
Например, если один запрос блокируется другим, пытающимся обновить ту же строку, вы увидите, что отображаются заблокированные и блокирующие инструкции:
-[ RECORD 1 ]-------------------------+--------------------------------------
waiting_gpid | 10000011981
blocking_gpid | 10000011979
blocked_statement | UPDATE numbers SET j = 3 WHERE i = 1;
current_statement_in_blocking_process | UPDATE numbers SET j = 2 WHERE i = 1;
waiting_nodeid | 1
blocking_nodeid | 1
Чтобы увидеть не только блокировки, происходящие в данный момент, но и исторические шаблоны, можно зарегистрировать блокировки в журналах PostgreSQL. Дополнительные сведения см. в описании параметра сервера log_lock_waits в документации по PostgreSQL. Еще один отличный ресурс — семь советов по работе с блокировками в блоге Citus Data.
Распространенные проблемы и способы их решения
Команды языка описания данных (DDL)
Команды DDL, такие как truncate
, drop
и create index
, принимают блокировки записи и блокируют операции записи во всей таблице. Минимизация таких операций сокращает проблемы с блокировкой.
Советы
Старайтесь консолидировать операции DDL в периоды обслуживания или использовать их реже.
PostgreSQL поддерживает параллельное создание индексов, чтобы избежать блокировки записи в таблице.
Перед выполнением сложной команды DDL рекомендуется настроить lock_timeout в сеансе SQL. При использовании
lock_timeout
PostgreSQL прервет команду DDL, если команда ожидает блокировки записи слишком долго. Команда DDL, ожидающая блокировки, может привести к тому, что последующие запросы будут становиться в очередь вслед за ней.
Неактивность в транзакционных подключениях
Неактивные (незафиксированные) транзакции иногда без необходимости блокируют другие запросы. Например:
BEGIN;
UPDATE ... ;
-- Suppose the client waits now and doesn't COMMIT right away.
--
-- Other queries that want to update the same rows will be blocked.
COMMIT; -- finally!
Чтобы вручную очистить все долго выполняющиеся неактивные запросы на узле-координаторе, можно выполнить следующую команду:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'citus'
AND pid <> pg_backend_pid()
AND state in ('idle in transaction')
AND state_change < current_timestamp - INTERVAL '15' MINUTE;
В PostgreSQL также есть параметр idle_in_transaction_session_timeout для автоматизации завершения неактивного сеанса.
Взаимоблокировки
Azure Cosmos DB for PostgreSQL обнаруживает распределенные взаимоблокировки и отменяет их запросы, но производительность в этой ситуации ниже, чем при предотвращении взаимоблокировок в первую очередь. Распространенным источником взаимоблокировок является обновление одного и того же набора строк в разном порядке в нескольких транзакциях одновременно.
Например, параллельное выполнение таких транзакций:
Сеанс А:
BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;
Сеанс B:
BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;
-- ERROR: canceling the transaction since it was involved in a distributed deadlock
В сеансе A был обновлён идентификатор 1, затем 2, а в сеансе B - 2, затем 1. Внимательно напишите код SQL для транзакций, чтобы обновить строки в том же порядке. (Порядок обновления иногда называется "иерархией блокировки".)
Согласно нашим измерениям, массовое обновление набора строк с большим количеством транзакций ускорилось в 3 раза при отсутствии взаимоблокировки.
Операции ввода-вывода во время приема данных
Узкие места ввода-вывода обычно менее проблематичны для Azure Cosmos DB под PostgreSQL, чем для однопользовательских PostgreSQL из-за шардирования. Сегменты представляют собой отдельные таблицы меньшего размера, с лучшими показателями попадания в индекс и кэш, что обеспечивает более высокую производительность.
Однако даже с Azure Cosmos DB for PostgreSQL, по мере того как таблицы и индексы увеличиваются, операции ввода-вывода на диске могут стать проблемой для загрузки данных. Обратите внимание на увеличивающееся число записей 'IO' wait_event_type
, появляющихся в citus_stat_activity
:
SELECT wait_event_type, wait_event count(*)
FROM citus_stat_activity
WHERE state='active'
GROUP BY 1,2;
Выполните приведенный выше запрос несколько раз, чтобы получить сведения о событиях ожидания. Обратите внимание, как изменяется количество различных типов событий ожидания.
Также просмотрите метрики на портале Azure, особенно следите за максимальным значением метрики операций ввода-вывода в секунду (IOPS).
Советы
Если данные естественно упорядочены, как, например, во временных рядах, используйте секционирование таблиц PostgreSQL. Чтобы узнать, как секционировать распределенные таблицы, см. это руководство.
Удалите неиспользуемые индексы. Обслуживание индексов приводит к усилению операций ввода-вывода во время приема данных. Чтобы узнать, какие индексы не используются, выполните этот запрос.
По возможности избегайте индексирования случайных данных. Например, некоторые алгоритмы создания UUID не придерживаются никакого порядка. Индексирование такого значения приводит к большим издержкам. Попробуйте вместо этого использовать последовательность bigint или монотонно увеличить количество UUID.
Сводка результатов
В тестах простого приема с операциями INSERT, UPDATE и блокировкой транзакций мы наблюдали следующие показатели ускорения запросов при использовании методов из этой статьи.
Методика | Ускорение выполнения запросов |
---|---|
Определение области запросов | 100x |
Организация пулов соединений | В 24 раза |
Эффективное логирование | 10 раз |
Предотвращение взаимоблокировки | В 3 раза |