Configuration

View Source

Every option minato takes, what it defaults to, and why.

A connection

Passed as connection in a pool or listener, or to minato_conn:connect/1 directly.

optiondefault
userrequiredthe role to authenticate as
passwordnonea binary, or a fun(() -> binary()) called once per attempt and never stored
databasethe useras PostgreSQL itself defaults
host"localhost"
port5432
parameterssee belowadded to the StartupMessage
sslfalse
ssl_optionsverify against the OS trust storeeach option replaces one default
channel_bindingprefer under TLS, disable withoutrequire refuses to connect without it
authevery method[scram_sha_256] to accept nothing else
connect_timeout5000covers the TCP connect and the TLS handshake
timeout15000the per read timeout, and the default statement deadline
cancel_timeout5000how long a cancelled statement has to acknowledge
prepared_statements64how many statements a connection keeps parsed; 0 disables
socket_optionsbinary, {active,false}, {packet,raw}, {nodelay,true}each replaces one default
frame_opts#{max_message_length => 67108864}raise it only for a single value near PostgreSQL's 1 GB limit

Two startup parameters are sent unless parameters overrides them: client_encoding is UTF8, because the codecs decode text as UTF-8 and a server sending LATIN1 would put mojibake in a binary rather than raise; DateStyle is ISO, MDY, because the text format for dates is only unambiguous under ISO. Set application_name here - it is what pg_stat_activity shows, and it is the difference between finding the query that is hurting and guessing.

A pool

optiondefault
connectionrequiredthe map above
size10the most connections this pool will hold
min_size1how many it opens before anybody asks
disconnectedfailwhat a checkout gets when the pool is empty and cannot connect
max_idle60000close a connection nobody has wanted for this long, down to min_size
max_ageinfinityretire a connection checked in older than this

min_size is one because a node with twenty pools should not want a hundred connections the moment it boots, and one is still enough for the pool to prove it can connect and complain at start up rather than at the first query. The rest open when a caller finds none free. Set it to size for a pool that must be warm on the first request, and to 0 for one that may never be used at all.

max_idle is the other half of min_size. A pool that grew to meet a busy hour would otherwise hold that hour's connections until the process died, which is a cost the server pays and nobody sees. The floor is never swept below, because the point of the floor is that somebody has already paid for those connections. infinity keeps whatever the pool has grown to.

disconnected is fail: a checkout against a pool that holds nothing and whose last attempt to connect failed answers {error, disconnected} immediately. During an outage the alternative is every caller holding a process for the whole checkout timeout to learn the same thing, and a queue that outlives the outage. wait queues anyway, for work that would rather be slow than fail.

size is not a throughput dial. A pool larger than the database can serve moves the queue from your application to the server, where you cannot see it; a pool smaller than your concurrency makes callers wait, which [minato, checkout, stop] measures exactly.

max_age is infinity because a connection to PostgreSQL is good indefinitely, and churning connections is work nobody asked for. Set it when something sits in the middle - a proxy, a load balancer, a NAT - since those have limits of their own and a connection one of them cut is otherwise found by a query failing on it.

How many connections is that

size per pool, times the pools on the node, times the nodes, against the server's max_connections. Nobody does that multiplication until PostgreSQL starts refusing, so minato does it: the first connection a pool opens asks the server for its limit, and a pool that alone wants a quarter of it says so once, at warning, with event => connection_budget.

The arithmetic bites hardest in test suites, where every module tends to start a pool and none of them stop one. See the testing note below.

Pools in tests

A test suite that starts a pool per module and stops none of them is asking for sorry, too many clients already, and it will get it sooner with minato than with a client that opens connections lazily, unless min_size is low.

Three things that keep it quiet: start one pool for the suite rather than one per module; use min_size => 1 and a small size, since tests are rarely concurrent enough to need more; and stop the pool when the suite ends - minato:stop_pool/1 closes every connection it holds.

A listener

{ok, _Pid} = minato:start_listener(events, #{connection => #{user => ~"minato"}}).

connection is the only option. A listener holds one connection and never shares it, because LISTEN is session state.

Per query

Options to minato:query/4, minato:simple/3 and the minato_query functions:

optiondefault
timeoutthe connection's timeoutdeadline for the statement; on expiry it is cancelled on the server, not abandoned
return_rows_as_mapsfalsea map per row instead of a tuple
column_name_as_atomfalseatom keys in those maps
uuid_formatstringbinary for the raw 16 bytes
datetime_formatdatetimemicroseconds for lossless integers

column_name_as_atom is off because a column name is not always something you wrote: a query built at run time can alias a column after a value, and every distinct alias would make an atom that is never collected. Turn it on for queries whose column names you chose.

The application environment

{minato, [
    {pools, #{main => #{size => 10, connection => #{user => ~"minato"}}}},
    {listeners, #{events => #{connection => #{user => ~"minato"}}}},
    {log_statements, false}
]}.

pools and listeners are started by minato's supervisor, so they come up with the application rather than after it. log_statements puts the SQL in events and logs; it is read per call, so it can be turned on while something is happening and off again afterwards. See Security for why it is off by default.