Каков наихудший сценарий, если вы запускаете отдельные запросы, когда у pgbouncer есть pool_mode = транзакция? - PullRequest
2 голосов
/ 30 марта 2020

Давайте предположим следующую конфигурацию для pgbouncer:

pool_mode=transaction
reset_query=discard all
reset_query_always=0

Если у меня есть соединение, которое делает

begin transaction
...
commit transaction

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

Однако, если вместо этого клиентское приложение отправляет

select select pg_advisory_lock(123);
begin transaction isolation level serializable
...
commit transaction
select select pg_advisory_unlock(123);

, возможно ли, что внутреннее соединение переключается между запросами так что внутреннее соединение № 1 получает блокировку, соединение № 2 выполняет транзакцию, а соединение № 3 пытается разблокировать консультативную блокировку и, очевидно, не удается?

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

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

1 Ответ

0 голосов
/ 07 апреля 2020

Я проверил это с помощью следующей конфигурации pgbouncer:

[pgbouncer]
pool_mode = transaction
server_reset_query = DISCARD ALL
server_check_query = select 1
server_reset_query_always = 0

max_client_conn = 2000
default_pool_size = 1
min_pool_size = 1

reserve_pool_size = 0
reserve_pool_timeout = 5

max_db_connections = 1

и двух параллельных подключений к одной базе данных через pgbouncer. Далее используется P1 и P2 в качестве идентификаторов для запросов, отправляемых каждым процессом. Если выполнить простой тест, такой как:

P1: set application_name=p1;
P1: select pg_advisory_lock(42);
P2: set application_name=p2;
P1: show application_name;
    p1
P2: select pg_advisory_lock(42);
P2. show application_name;
    p2

... , кажется, что оба соединения смогли получить одну и ту же исключительную консультативную блокировку, даже если они имеют уникальные значения application_name, поэтому каждое соединение должно быть уникально! В действительности все команды были отправлены на postgres с использованием одного соединения, поэтому это единственное соединение дважды получило консультативную блокировку 42, и все в порядке, если позднее она дважды снимает такую ​​же блокировку (логически один раз для P1 и P2 соединение). Это происходит из-за того, что один и тот же владелец может неоднократно снимать монопольные блокировки и должен сниматься одинаково много раз, прежде чем любой другой процесс сможет получить такую ​​же блокировку.

В результате также возможно следующее:

P1: select pg_advisory_lock(42);
P2: select pg_advisory_lock(42);
P1: begin transaction isolation level serializable;
P1: select 1;
P1: commit;
P2: begin transaction isolation level serializable;
P2: select 1;
P2: commit;
P1: select pg_advisory_unlock(42);
P2: select pg_advisory_unlock(42);

Представьте несколько запросов и изменений в базе данных вместо select 1 выше.

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

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

В результате, это определенно НЕ безопасно выполнять любые запросы вне транзакции, если pool_mode=transaction активно. Опция server_reset_query_always=1 не может действительно решить проблему и никогда не должна использоваться. Если вы чувствуете, что вам нужно server_reset_query_always=1, вам нужно использовать pool_mode=session вместо этого или вы рискуете случайным повреждением данных.

Кроме того, pgbouncer кажется достаточно умным, чтобы подделать некоторые данные подключения c данные. Например, когда P1 устанавливает свое имя_приложения в первом примере выше и позже запрашивает его после того, как P2 уже установил имя своего приложения в том же соединении, P1 получит ожидаемый результат. Однако, если вы наблюдаете pg_stat_activity, пока это происходит, единственное активное соединение на postgres меняет значение application_name каждый раз, когда P1 или P2 отправляет запрос. Это заставляет казаться , что этот тип микширования в порядке с pool_mode=transaction.

В конце, установка application_name вне транзакции должна быть безопасной, но любая функция, фактически реализованная на * Уровень 1053 * небезопасен. Если вы не абсолютно уверены, , что pgbouncer может эмулировать нужную вам функцию, не отправляйте никаких запросов к соединению с базой данных, полученных от pgbouncer в pool_mode=transaction, за исключением begin ..., commit ... или rollback. Когда транзакция активна, соединение зарезервировано для вас, и все работает так же, как и при прямом прямом соединении с postgres, пока вы не выполните commit или rollback.

Мне бы очень хотелось, чтобы pgbouncer всегда возвращать ошибку каждый раз, когда клиент пытается выполнить любой запрос вне транзакции, если установлено pool_mode=transaction. К сожалению, это не та реальность, в которой мы живем, и клиенты pgbouncer должны быть осторожны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...