Rails Connection Pool против PgBouncer

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

Чтобы опробовать это, я использую docker, чтобы не устанавливать дополнительное приложение:

$ mkdir conn-poc; cd conn-poc
$ rails new blog --api -T --database=postgresql

$ mkdir bouncer
$ mkdir db

# create docker network so that PgBouncer and PostgreSQL can communicate with eacher other
$ docker network create conn-poc-1-net

# start postgresql
$ cd db
$ docker run --rm 
  --net conn-poc-1-net 
  --name conn-poc-1-pg 
  -e POSTGRES_USER=postgres 
  -e POSTGRES_PASSWORD=postgres 
  -e POSTGRES_DB=blog_development 
  -v $(pwd):/var/lib/postgresql/data  
  -p 6432:5432 
  -it postgres:13.8-alpine

# start pgbouncer
$ cd bouncer
$ docker run --rm 
  --net conn-poc-1-net 
  --name conn-poc-1-bouncer 
  -e DATABASE_URL="postgres://postgres:postgres@conn-poc-1-pg/blog_development" 
  -v $(pwd):/etc/pgbouncer 
  -p 7432:5432 
  edoburu/pgbouncer
Вход в полноэкранный режим Выход из полноэкранного режима

Конфигурация PgBouncer:

[databases]
blog_development = host=conn-poc-1-pg port=5432 user=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 5432
user = postgres
auth_file = /etc/pgbouncer/userlist.txt
auth_type = md5
ignore_startup_parameters = extra_float_digits
pool_mode = transaction
Войти в полноэкранный режим Выйти из полноэкранного режима

Обновите часть кода:

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  username: "postgres"
  password: "postgres"
  port: <%= ENV.fetch("DB_PORT") %>
  host: localhost
  pool: <%= ENV.fetch("RAILS_MAX_THREAD") %>
  checkout_timeout: 5
  idle_timeout: 3
  prepared_statements: false
Войти в полноэкранный режим Выйти из полноэкранного режима
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
Войти в полноэкранный режим Выход из полноэкранного режима

Настройте БД и создайте таблицу:

$ rails g model User name
$ rails db:create db:migrate
Войти в полноэкранный режим Выйти из полноэкранного режима
# config/routes.rb
Rails.application.routes.draw do
  root "home#index"
end

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    render json: User.first
  end
end

# config/environments/development.rb
config.hosts.clear
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы можем подключиться к PostgreSQL:

# 6432 = Connect to PostgreSQL directly
# 7432 = Connect throught PgBouncer
psql -U postgres -h localhost -d blog_development -p 6432
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы можем использовать этот SQL для проверки количества соединений:

SELECT * FROM pg_stat_activity WHERE datname = 'blog_development'
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы запустить сервер, я использую эту команду (значения будут меняться в зависимости от того, что я хочу попробовать):

DB_PORT=7432 
  RAILS_MAX_THREAD=5 
  WEB_CONCURRENCY=3 
  rails s -b 0.0.0.0
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы отправить запрос, мы можем использовать эту команду:

# Apache benchmark
docker run --rm jordi/ab -c 500 -n 500 http://host.docker.internal:3000/
Войти в полноэкранный режим Выйти из полноэкранного режима

Результат:

Расчет необходимого объема базы данных

Первая часть работы заключается в определении максимального количества соединений на процесс:

  • Размер пула в database.yml зависит от того, сколько потоков/конкуренции вы установили.
  • Если вы установили RAILS_MAX_THREAD на 10, то это и есть необходимый размер пула.
  • Но вы можете установить другое значение, если у вас есть фоновая работа, например: Sidekiq
  • У Sidekiq может быть другой параллелизм, поэтому, если параллелизм установлен на 20, то вам нужно увеличить размер пула для этого. Таким образом, существует вероятность того, что веб может иметь большее значение, если только вы не зададите различную конфигурацию между рабочим и веб сервером (при условии, что они находятся на разных серверах).

Как только мы выяснили это, нам нужно подумать о количестве процессов, которые у нас будут:

  • Процесс — это что-то вроде количества процессов puma/sidekiq, которые у вас есть.
  • В этом упражнении я использую WEB_CONCURRENCY.

Пример:

  • Puma: WEB_CONCURRENCY=2
  • Puma: RAILS_MAX_THREAD=5

Это означает, что нам нужно иметь по крайней мере 2×5 = 10 соединений. Нам также нужно установить размер пула БД на 5.

Давайте добавим Sidekiq:

  • bundle exec sidekiq -C config/sidekiq/payment.yml
  • bundle exec sidekiq -C config/sidekiq/data.yml

Предполагая, что concurrency установлен на 20 в каждом из этих конфигов, нам нужно иметь 2 x 20 = 40 соединений. Пул БД в этом случае должен быть установлен на 20.

Таким образом, нам нужно в общей сложности 10 + 40 = 50 соединений.

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

Примечания

Вот некоторые заметки, основанные на моих наблюдениях:

  • Открытие консоли rails не приводит к немедленному открытию соединения
  • Без PgBouncer Rails немедленно откроет все возможные соединения
  • PgBouncer увеличивает количество соединений после того, как я несколько раз прогнал бенчмарк, но так и не достиг максимума
  • И Rails, и PgBouncer имеют возможность отключать простаивающие соединения.
  • Без правильного размера пула/потока Rails будет выбрасывать ошибку ActiveRecord::ConnectionTimeoutError.
  • Мне пришлось использовать режим транзакций с отключенной опцией prepared_statement. Нужно прочитать больше об этом
  • Я не разделяю чтение и запись.

Вывод

  • Я предполагал, что PgBouncer всегда будет использовать меньше соединений, что в какой-то степени верно, но я не уверен, что если поступит больше запросов, будет ли количество соединений продолжать увеличиваться до максимума?
  • Мы можем полагаться на таймаут для удаления простаивающих соединений как для Rails, так и для PgBouncer.
  • PgBouncer определенно необходим, если мы подключаемся к БД из различных приложений, и одно из них может не иметь собственного менеджера пула.
  • Судя по некоторым поискам, невозможно отключить Rails connection pooler и использовать только PgBouncer.
  • Я думаю, что PgBouncer способен обрабатывать несколько SQL, используя одно соединение, потому что у него есть функция мультиплекса, но пока я не могу это подтвердить.

Ссылки

  • https://devcenter.heroku.com/articles/best-practices-pgbouncer-configuration#pgbouncer-s-connection-pooling-modes
  • https://www.pgbouncer.org/config.html
  • https://makandracards.com/makandra/45360-using-activerecord-with-threads-might-use-more-database-connections-than-you-think
  • https://maxencemalbois.medium.com/the-ruby-on-rails-database-connections-pool-4ce1099a9e9f
  • https://dev.to/hopsoft/optimizing-rails-connections-4gkd

Оцените статью
devanswers.ru
Добавить комментарий