F.55. pg_throttle#
F.55. pg_throttle #
pg_throttle
- это расширение Tantor SE, которое добавляет
функцию ограничения запросов.
F.55.1. Функциональность #
Расширение позволяет установить верхний предел количества кортежей, отправляемых от
узлов сканирования к верхним узлам. Например, узел SeqScan
может передавать не более 100 кортежей в секунду к верхним узлам.
Ограничение устанавливается как количество кортежей, возвращаемых узлом сканирования в секунду. Поддерживаемые ScanNodes:
SeqScan
IndexScan
/IndexOnlyScan
BitmapHeapScan
/BitmapIndexScan
CustomScan
ForeignScan
ResultScan
/ValuesScan
FunctionScan
Также поддерживаются другие операторы DML, которые могут изменять
таблицу. Например, INSERT
, UPDATE
,
DELETE
, но только если
указано предложение RETURNING
.
Существуют 2 режима работы для ограничения скорости:
per-node - ограничение устанавливается для каждого узла независимо
большая скорость (по сравнению с режимом запроса)
более тонкая степень управления
query - состояние ограничения скорости разделяется между всеми узлами
более строгое ограничение выполнения запросов
Скорость ограничения измеряется как количество кортежей, возвращаемых соответствующим узлом за 1 секунду. Расширение отслеживает только количество кортежей, и если оно превышает лимит (менее чем за 1 секунду), оно просто спит оставшееся время. Например, время ограничения составляет 100 (100 кортежей в секунду):
0.1s - 35 кортежей возвращено
0.2s - возвращено 35 кортежей
0.3s - возвращено 30 кортежей
0.4-1s - сон
F.55.2. Настройка #
Расширение должно быть загружено с использованием
session_preload_libraries
. Во время загрузки оно
обращается к своей таблице конфигурации (см. ниже) и устанавливает хуки, если
необходимо. Добавьте расширение в postgresql.conf
# postgresql.conf session_preload_libraries = 'pg_throttle'
Наконец, запустите БД и создайте расширение:
CREATE EXTENSION pg_throttle;
Примечание
Для расширения может быть указана пользовательская схема
F.55.3. Начало работы #
Установите расширение, как обсуждалось в разделе настройки.
После этого вы можете захотеть ограничить себя, чтобы протестировать расширение. Выполните это:
CREATE TABLE tbl( value int ); INSERT INTO tbl SELECT * FROM generate_series(1, 200); SELECT throttle_user_per_node_all(current_user, 100);
Теперь этот пользователь (которого вы используете для входа) будет ограничен. Выйдите, войдите снова и выполните этот запрос:
SELECT * FROM tbl;
Продолжительность запроса должна составлять около 2 секунд: 200 кортежей / 100 кортежей в секунду = 2 секунды задержки.
F.55.4. API & примеры #
FUNCTION throttle_user_per_node(username text, seq_scan_max int idx_scan_max int, idx_only_scan_max int bitmap_heap_scan_max int, bitmap_index_scan_max int custom_scan_max int, foreign_scan_max int tid_range_scan_max int, modify_scan_max int, values_scan_max int, result_scan_max int, func_scan_max int);
Это основная функция для установки ограничения пользователя. Однако, обычно вы будете использовать другие сокращенные функции ниже.
username
- имя пользователя, для которого должно быть
ограничение. Если указанный пользователь не существует, будет выброшено
исключение.
XXX_max
- другие аргументы указывают ограничение
для соответствующего узла:
seq_scan_max
- SeqScanidx_scan_max
- Индексное сканированиеidx_only_scan_max
- Только индексное сканированиеbitmap_heap_scan_max
- Битовая куча сканированияbitmap_index_scan_max
- Битовый индексный сканcustom_scan_max
- Пользовательское сканированиеforeign_scan_max
- Иностранное сканированиеtid_range_scan_max
- Диапазонное сканирование TIDmodify_scan_max
- операторы модификации DML (INSERT, UPDATE и т.д.), только дляRETURNING
values_scan_max
- Значения Сканированияresult_scan_max
- Результатное сканированиеfunc_scan_max
- Функция Сканирования
Для аргументов XXX_max
допустимое значение:
NULL
- узел не будет ограничен. Дляquery_max = NULL
- включает режим на узел0 < XXX_max
- устанавливаетXXX_max
кортежей в секунду обработки в секунду для соответствующего узла
Примечание
Отрицательное или нулевое значение не допускается. Если вы хотите отключить
ограничение - установите его NULL
Пример - ограничение пользователя ‘analytics’.
SELECT throttle_user_per_node('analytics', 100, -- SeqScan 1000, 1000, -- Index/IndexOnly Scan 1000, 1000, -- Bitmap Heap/Index Scan NULL, -- Custom Scan (disable throttling) 10, -- Foreign Scan 100, -- TID Range Scan NULL, -- DML (disable throttling) NULL, NULL, -- Values/Result Scan (disable throttling) 1000); -- Function Scan
FUNCTION throttle_user_per_node_all(username text, max int);
Это сокращенная функция для
throttle_user_per_node
, которая устанавливает все
XXX_max
в max
.
Пример:
SELECT throttle_user_per_node_all('analytics', 100);
FUNCTION throttle_user_query(username text, max int);
Эта функция ограничивает все узлы Scan, используя общее состояние. Это означает, что все узлы имеют один и тот же счетчик ограничения. Счетчик применяется ко всем поддерживаемым узлам - вы не можете установить конкретные узлы
Пример:
SELECT throttle_user_query('analytics', 100);
FUNCTION unthrottle_user(username text);
Эта функция удаляет ограничение пропускной способности для указанного пользователя.
Пример:
SELECT unthrottle_user('analytics');
TABLE throttlings( username text, query_throttle int, XXX_throttle int );
Это основная таблица конфигурации расширения - хранит конфигурацию ограничения для пользователей:
username
- имя пользователя с ограничением скоростиquery_throttle
- ограничение для режима запроса (NULL
, если режим на узел)XXX_throttle
- это значение ограничения для соответствующего узла
Эта таблица читается расширением при запуске бэкенда, во время инициализации расширения. Поэтому пользователь должен иметь к ней доступ, но это создает риски безопасности. Чтобы решить эту проблему, см. раздел безопасность ниже.
F.55.5. Детали реализации #
Расширение реализовано с использованием декораторов.
Когда ExecutorStart_hook
срабатывает, мы ищем в его
PlanState
интересующие узлы (которые поддерживаются). Если такой узел найден, мы:
Создаем наш регулирующий узел
Копируем содержимое старого узла
Сохраняем
ExecProcNode
старого узла (и другие вещи)Заменяем
ExecProcNode
вPlanState
(он будет вызываться исполнителем)
Затем во время выполнения:
Исполнитель вызывает
PlanState->ExecProcNode
Вызывается наша функция ограничения
Вызываем сохраненный
ExecProcNode
- получаем кортежОбрабатываем логику ограничения
Возвращаем кортеж
Это относится ко всем поддерживаемым узлам, за исключением
CustomScan
и ForeignScan
.
Мы не можем сделать то же самое для них, потому что расширения создают свои
собственные структуры поверх них (также декоратор). Поэтому, делая это снова -
мы перезаписываем их данные. Чтобы преодолеть это, мы создаем такой декоратор
для его интерфейса -
CustomScanMethods
и
FdwRoutine
.
F.55.6. Ограничения в использовании #
Существует множество предостережений, о которых вы должны знать
F.55.6.1. Декораторы #
Расширение использует декораторы для реализации этой функциональности. Если какое-либо другое расширение делает это, однажды они могут перезаписать свои данные, что приведет к непредсказуемому поведению.
Если вы обнаружите это, 1) пожалуйста, сообщите нам (чтобы предупредить других пользователей) и 2) вы должны выбрать только одно расширение.
F.55.6.2. Параллельные рабочие процессы #
Параллельные рабочие обрабатываются таким же образом - они также используют
Scan
узлы, и к ним применяется регулирование.
Но у них отдельные адреса памяти, поэтому режим запроса не будет
работать для них.
F.55.6.3. Пилообразная волна #
Расширение не распределяет нагрузку равномерно - оно просто засыпает, когда порог достигнут. Из-за этого вы можете увидеть графики в виде зубцов пилы.
F.55.6.4. Применение изменений #
Расширение кэширует конфигурацию ограничения в статических переменных. Оно считывает их при инициализации расширения и использует позже.
Таким образом, если вы изменили любой другой параметр ограничения, он будет использован только в следующей сессии. Этому пользователю может потребоваться переподключение.
F.55.6.5. Пользователь, к которому это применяется #
Логика ограничения применяется к SESSION_USER
.
Если вы измените его, например, используя SET ROLE
,
логика ограничения будет применяться к SESSION_USER
с его начальной конфигурацией.
F.55.6.6. Безопасность #
Таблица конфигурации throttlings
читается
во время запуска сессии (инициализация расширения) для получения
параметров ограничения пользователя сессии. Предоставление доступа
SELECT
всем пользователям кажется утечкой безопасности, поэтому для таблицы включена встроенная RLS.
Существует одно правило -
CURRENT_USER = username
, так что пользователи читают только
ограничения для самих себя.
Но при запуске у пользователей по умолчанию нет доступа к этой таблице,
и все запросы приведут к ошибке отказа в разрешении. Чтобы исправить
это, предоставьте SELECT
для
public
(или любой другой роли, которую вы хотите).
REVOKE ALL ON TABLE throttlings FROM public; GRANT SELECT ON TABLE throttlings TO public;
Теперь только суперпользователи могут изменять таблицу конфигурации throttlings
;
Примечание
throttlings
уже имеет RLS
POLICY, поэтому пользователь сессии может работать только со своими
собственными записями.
F.55.6.7. Мертвые кортежи: #
Расширение обрабатывает только количество кортежей возвращенных узлом, но не обрабатывает общее количество обращений к страницам/буферам. Это приводит к ситуациям, когда общее количество затронутых страниц/буферов значительно превышает наложенное ограничение.
Случаи, когда это может сработать:
Ключ сканирования (фильтр) - если в узле есть фильтр, возвращаются только кортежи, удовлетворяющие фильтру - может быть обработано сотни кортежей, прежде чем какой-либо из них будет возвращен из этого узла.
Мертвые кортежи - мертвые кортежи также читаются со страниц, но они не будут возвращаться из узла - сотни мертвых кортежей в таблице могут повлиять на производительность
F.55.6.8. Управление скоростью DML #
Расширение ограничивает количество возвращаемых
кортежей, но операторы INSERT
,
UPDATE
, DELETE
и другие
операторы модификации таблиц возвращают только 1 кортеж. Поэтому
ограничение к ним не применяется.
Но есть случаи, когда это происходит:
Эти операторы имеют
RETURNING
- в этом случае логика применяется правильно, потому что этот узел возвращает несколько запросовВвод для этих узлов осуществляется из других поддерживаемых узлов сканирования - примеры этого включают:
DELETE FROM tbl WHERE value > 1
- ввод дляDELETE
этоSeqScan
(ограничено) из той же таблицыINSERT INTO tbl SELECT * FROM tbl2
- ввод дляINSERT
-SeqScan
(ограниченный) из другой таблицыINSERT INTO tbl SELECT * FROM generate_series(1, 100)
- ввод дляINSERT
являетсяFunctionScan
(ограничено)UPDATE tbl SET value = value + 1
- ввод дляUPDATE
этоSeqScan
(ограничено) из той же таблицы