README.ru.md 7.5 KB

Пул соединений для работы с PostgreSQL

Библиотека реализует комплексное решение для взаимодействия эрланг проекта с базой PostgreSQL на базе асинхронной версию драйвера epgsql и пула процессов pooler.

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

Внутри пула есть несколько процессов, и каждый из них устанавливает свое соединение с базой и посылает запросы к ней независимо от остальных процессов.

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

Запуск и остановка пула

Для запуска пула нужно вызывать функцию epgsql_pool:start/4 с аргументами:

  • имя пула atom() | string() | binary()
  • число соединений integer()
  • максимальное число соединений integer()
  • настройки соединения map()

    Params = #{host => "localhost",
           port => 5432,
           username => "someuser",
           password => "pass",
           database => "main_db"},
    {ok, _} = epgsql_pool:start(main_pool, 10, 20, Params),
    Params2 = #{host => "localhost",
            port => 5432,
            username => "someuser",
            password => "pass",
            database => "other_db"},
    {ok, _} = epgsql_pool:start(other_pool, 10, 20, Params2),
    

Настройки соединения должны быть map() или #epgsql_connection_params{} (определена в include/epgsql_pool.hrl).

Для остановки пула нужно вызывать epgsql_pool:stop(PoolName).

Проверка настроек

Каждый процесс в пуле пытается установить соединение с базой. Если по каким-то причинам это не удается, то процесс логирует ошибку, и через короткий промежуток времени снова пытается соединиться. Если настройки соединения указаны неправильно, то такие попытки повторяются бесконечно. И если процессов в пуле много, то генерируется много сообщений об ошибках, которые создают большую нагрузку на IO и записывают много логов.

Поэтому, перед запуском пула рекомендуется проверить правильность настроек. Это можно сделать вызовом epgsql_pool:validate_connection_params/1

1> Params = #{host => "localhost",
1>                port => 5432,
1>                username => "test",
1>                password => "test",
1>                database => "testdb"}.
2> epgsql_pool:validate_connection_params(Params).
ok
3> epgsql_pool:validate_connection_params(Params#{password := "123"}).
{error,invalid_password}
4> epgsql_pool:validate_connection_params(Params#{database := "some"}).
{error,{error,fatal,<<"3D000">>,
              <<"database \"some\" does not exist">>,[]}}

Здесь тоже настройки должны быть map() или #epgsql_connection_params{}.

Если настройки оказались неправильными, то, вероятно, вы захотите сообщить об ошибке и остановить ноду.

Запрос к базе данных

Для отправки запроса нужно вызывать одну из функций epgsql_pool:query/2, /3, /4 с аргументами:

  • имя пула atom() | string() | binary()
  • SQL-запрос _iolist()
  • опционально, параметры запроса [term()]
  • опционально, дополнительные настройки [proplists:option()]

Формат SQL-запроса, параметров к нему, возможные форматы ответа такие же, как требует драйвер epgsql. Подробности смотрите в документаци драйвера.

Напрямую работать с пулом соединений не нужно, об этом заботится библиотека.

5> epgsql_pool:query(my_pool, "INSERT INTO category (id, title) VALUES (1, 'My Category'), (2, 'Other Category')").
{ok,2}
6> epgsql_pool:query(my_pool, "INSERT INTO category (id, title) VALUES (3, 'Next Category') RETURNING id").
{ok,1,[{column,<<"id">>,int8,8,-1,1}],[{3}]}
7> epgsql_pool:query(my_pool, "SELECT * FROM category").
{ok,[{column,<<"id">>,int8,8,-1,1},
     {column,<<"title">>,text,-1,-1,1}],
    [{1,<<"My Category">>},
     {2,<<"Other Category">>},
     {3,<<"Next Category">>}]}

Есть ограничение на время выполнения запроса, по умолчанию оно 10 секунд. Если за это время библиотека не получает ответ от базы, то запрос отменяется, и возвращается {error, timeout}.

8> epgsql_pool:query(my_pool, "select pg_sleep(100)").
{error,timeout}

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

Вы можете изменить timeout для конкретного запроса.

9> epgsql_pool:query(my_pool, "select pg_sleep(10)", [], [{timeout, 15000}]).
{ok,[{column,<<"b">>,void,4,-1,0}],[{<<>>}]}

Или даже указать {timeout, infinity}, если вообще не хотите ограничивать время запроса.

10> epgsql_pool:query(my_pool, "select pg_sleep(10)", [], [{timeout, infinity}]).
{ok,[{column,<<"b">>,void,4,-1,0}],[{<<>>}]}

timeout задается в миллисекундах. И это пока единственная настройка запроса, которая поддерживается библиотекой. Возможно, в следующих версиях появятся и другие настройки. (Поэтому 4-й аргумент -- proplist, а не атомарное значение).

Можно изменить и timeout по умолчанию, что повлияет на все запросы. Смотрите ниже раздел Настройки.

Транзациии

TODO

keep-alive

TODO

reconnect

TODO with exponential backoff

настройки

TODO

get_settings set_settings