Module eysql

Topology-aware load balancing and failover for epgsql.

Description

Topology-aware load balancing and failover for epgsql.

eysql gives epgsql what the YugabyteDB smart drivers give JDBC, pgx and psycopg: it discovers the cluster's servers with yb_servers(), opens each connection to the least-loaded server in the preferred zone or region, and skips hosts that fail. It adds a pool that recycles and rebalances connections, and a transaction helper that retries serialization failures. With PostgreSQL it uses the configured hosts.

Load balancing is off unless load_balance says otherwise, as in the smart drivers: without it, the pool uses the configured hosts only.

Connections are ordinary epgsql connections: check one out and call epgsql on it.

  {ok, Pool} = eysql:start_link(#{hosts => ["yb-tservers.db.svc.cluster.local"],
                                 username => <<"app">>,
                                 password => <<"secret">>,
                                 database => <<"app">>,
                                 load_balance => true,
                                 topology_keys => "gcp.us-east1.us-east1-b:1,gcp.us-east1.*:2"}),
  {ok, _, Rows} = eysql:equery(Pool, "SELECT id FROM accounts WHERE realm = $1", [Realm]),
  eysql:with_connection(Pool, fun(Conn) -> epgsql:equery(Conn, Sql, Params) end).

Data Types

pool()

pool() = gen_server:server_ref()

transaction_error()

transaction_error() = {rollback, term()} | commit_outcome_unknown | transaction_aborted | checkout_timeout | term()

Function Index

checkin/2Return a connection for reuse.
checkout/1
checkout/2Take an epgsql connection, waiting up to Timeout ms.
child_spec/2A child spec for a registered pool, Name being a local name.
cluster_info/1The cluster's view: servers, where new connections go now (placement), which hosts the pool keeps connections on (allowed), counts per host, hosts that failed and have not accepted a connection since (failed), and standbys that target_session_attrs => read_write turned away (read_only).
discard/2Return a connection that must not be reused, for example after an error left it in an unknown state.
equery/3epgsql:equery/3 on a pooled connection, through eysql_conn:equery/3: bounded by socket_timeout, and a lost connection is an error rather than an exit.
squery/2epgsql:squery/2 on a pooled connection, as equery/3.
start_link/1Start a pool linked to the caller.
start_link/2Start a registered pool, for example {local, my_db}.
stats/1Connections by state and by host.
stop/1
transaction/2Equivalent to transaction(Pool, Fun, #{}).
transaction/3Run Fun inside BEGIN and COMMIT, retrying when that is safe.
with_connection/2Equivalent to with_connection(Pool, Fun, 5000).
with_connection/3Run Fun with a connection and return its result.

Function Details

checkin/2

checkin(Pool::pool(), Conn::pid()) -> ok

Return a connection for reuse. Only the process that checked it out can return it: the pool ignores a checkin from any other process, including a late one from an earlier holder. If the holder exits instead, the pool closes the connection and opens another.

The pool does not look at the connection: the next holder gets it as it is. End any transaction with COMMIT or ROLLBACK first, or call discard/2 instead. eysql_conn:transaction_status/1 tells whether one is still open; with_connection/3 does this for you.

checkout/1

checkout(Pool::pool()) -> {ok, pid()} | {error, term()}

checkout/2

checkout(Pool::pool(), Timeout::timeout()) -> {ok, pid()} | {error, term()}

Take an epgsql connection, waiting up to Timeout ms. Return it with checkin/2, or discard/2 if it may be in a bad state. Until then, the pool's socket_timeout bounds the queries this process makes on it with eysql_conn:squery/2 and eysql_conn:equery/3.

child_spec/2

child_spec(Name::atom(), Options::eysql_config:options()) -> supervisor:child_spec()

A child spec for a registered pool, Name being a local name.

cluster_info/1

cluster_info(Pool::pool()) -> #{atom() => term()}

The cluster's view: servers, where new connections go now (placement), which hosts the pool keeps connections on (allowed), counts per host, hosts that failed and have not accepted a connection since (failed), and standbys that target_session_attrs => read_write turned away (read_only). See eysql_cluster:snapshot/1.

discard/2

discard(Pool::pool(), Conn::pid()) -> ok

Return a connection that must not be reused, for example after an error left it in an unknown state. The pool closes it and opens another. As with checkin/2, only the process holding the connection can discard it; a discard from any other process is ignored.

equery/3

equery(Pool::pool(), Sql::iodata(), Params::[term()]) -> term()

epgsql:equery/3 on a pooled connection, through eysql_conn:equery/3: bounded by socket_timeout, and a lost connection is an error rather than an exit.

squery/2

squery(Pool::pool(), Sql::iodata()) -> term()

epgsql:squery/2 on a pooled connection, as equery/3.

start_link/1

start_link(Options::eysql_config:options()) -> {ok, pid()} | {error, term()}

Start a pool linked to the caller. See eysql_config:defaults/0 for the options.

start_link/2

start_link(Name::gen_server:server_name(), Options::eysql_config:options()) -> {ok, pid()} | {error, term()}

Start a registered pool, for example {local, my_db}.

stats/1

stats(Pool::pool()) -> #{atom() => term()}

Connections by state and by host.

stop/1

stop(Pool::pool()) -> ok

transaction/2

transaction(Pool::pool(), Fun::fun((pid()) -> {ok, T} | {rollback, term()} | {error, term()})) -> {ok, T} | {error, transaction_error()}

Equivalent to transaction(Pool, Fun, #{}).

transaction/3

transaction(Pool::pool(), Fun::fun((pid()) -> {ok, T} | {rollback, term()} | {error, term()}), Options::map()) -> {ok, T} | {error, transaction_error()}

Run Fun inside BEGIN and COMMIT, retrying when that is safe.

Fun returns {ok, Value} to commit, {rollback, Reason} to roll back, or {error, Reason}, usually an epgsql error it got back, to roll back.

The whole transaction runs again, on a fresh checkout, when an error is retryable (serialization failure or deadlock, see eysql_error) or the connection was lost before COMMIT. Nothing was committed in either case. Fun must therefore have no side effects outside the database.

The pool's socket_timeout bounds BEGIN, COMMIT and ROLLBACK, and the queries Fun makes with eysql_conn:squery/2 and eysql_conn:equery/3, as in with_connection/3. One that runs out loses the connection: before COMMIT, the transaction runs again; during COMMIT, the result is commit_outcome_unknown.

Results:

If no connection can be checked out for a retry, because none came in time or the pool has stopped, the retries stop there and the result is the error that made the last attempt retry, such as the serialization failure, as when the attempts run out. A checkout error such as {error, checkout_timeout} therefore means Fun never ran.

Options: If Fun raises, the connection is discarded, which ends the transaction, and the exception is re-raised.

with_connection/2

with_connection(Pool::pool(), Fun::fun((pid()) -> Result)) -> Result | {error, term()}

Equivalent to with_connection(Pool, Fun, 5000).

with_connection/3

with_connection(Pool::pool(), Fun::fun((pid()) -> Result), Timeout::timeout()) -> Result | {error, term()}

Run Fun with a connection and return its result. Returns {error, Reason} if no connection could be had.

The connection goes back to the pool afterwards only if no transaction is open on it. If Fun ran BEGIN without COMMIT or ROLLBACK, or left the connection in a state the driver's transaction_status/1 cannot read (see eysql_conn:transaction_status/1), the connection is discarded instead: the pool closes it, which rolls the transaction back, and opens another. The check costs no round trip to the server. If Fun raises, the connection is discarded and the exception re-raised.

With the pool's socket_timeout set, queries Fun makes with eysql_conn:squery/2 and eysql_conn:equery/3 wait for the server that long at most, then return {error, {connection_lost, socket_timeout}}, the connection closed. epgsql's own functions, called on the connection directly, wait for as long as the server takes.

If the pool stops while Fun runs, the result of Fun is still returned. The pool closes its connections as it stops, so that is usually an error that eysql_error:is_connection_lost/1 accepts, such as {error, closed}.


Generated by EDoc