Package: hasql
hasqlhasqlis a library for acquiring actual connections to masters and replicas
in high available PostgreSQL clusters.Featurescompletely asynchronous apiautomatic detection of the host role in the clusterhealth-checks for each host and automatic traffic outage for
unavailable hostsautodetection of hosts role changes, in case replica
host will be promoted to masterdifferent policies for load balancingsupport forasyncpg,psycopg3,aiopg,sqlalchemyandasyncpgsaUsageSome useful examplesCreating connection poolWhen acquiring a connection, the connection object of the used driver is
returned (aiopg.connection.Connectionforaiopgandasyncpg.pool.PoolConnectionProxyforasyncpgandasyncpgsa)Database URL specirication rulesMultiple hosts should be passed comma separatedmultihost example:postgresql://db1,db2,db3/split result:postgresql://db1:5432/postgresql://db2:5432/postgresql://db3:5432/The non-default port for each host might be passed after hostnames. e.g.multihost example:postgresql://db1:1234,db2:5678,db3/split result:postgresql://db1:1234/postgresql://db2:5678/postgresql://db3:5432/The special case for non-default port for all hostsmultihost example:postgresql://db1,db2,db3:6432/split result:postgresql://db1:6432/postgresql://db2:6432/postgresql://db3:6432/Foraiopgoraiopg.saaiopgmust be installed as a requirement.Code example usingaiopg:fromhasql.aiopgimportPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn)# Waiting for 1 master and 1 replica will be availableawaitpool.ready(masters_count=1,replicas_count=1)returnpoolCode example usingaiopg.sa:fromhasql.aiopg_saimportPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn)# Waiting for 1 master and 1 replica will be availableawaitpool.ready(masters_count=1,replicas_count=1)returnpoolForasyncpgasyncpgmust be installed as a requirementfromhasql.asyncpgimportPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn)# Waiting for 1 master and 1 replica will be availableawaitpool.ready(masters_count=1,replicas_count=1)returnpoolForsqlalchemysqlalchemy[asyncio] & asyncpgmust be installed as requirementsfromhasql.asyncsqlalchemyimportPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn,# Use master for acquire_replica, if no replicas availablefallback_master=True,# You can pass pool-specific optionspool_factory_kwargs=dict(pool_size=10,max_overflow=5))# Waiting for 1 master and 1 replica will be availableawaitpool.ready(masters_count=1,replicas_count=1)returnpoolForasyncpgsaasyncpgsamust be installed as a requirementfromhasql.asyncpgsaimportPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn)# Waiting for 1 master and 1 replica will be availableawaitasyncio.gather(pool.wait_masters_ready(1),pool.wait_replicas_ready(1))returnpoolForpsycopg3psycopg3must be installed as a requirement (package name ispsycopg)fromhasql.psycopg3importPoolManagerhosts=",".join(["master-host:5432","replica-host-1:5432","replica-host-2:5432",])multihost_dsn=f"postgresql://user:password@{hosts}/dbname"asyncdefcreate_pool(dsn)->PoolManager:pool=PoolManager(multihost_dsn)# Waiting for 1 master and 1 replica will be availableawaitpool.ready(masters_count=1,replicas_count=1)returnpoolAcquiring connectionsConnections should be acquired with async context manager:Acquiring master connectionasyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)asyncwithpool.acquire(read_only=False)asconnection:...orasyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)asyncwithpool.acquire_master()asconnection:...Acquiring replica connectionasyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)asyncwithpool.acquire(read_only=True)asconnection:...orasyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)asyncwithpool.acquire_replica()asconnection:...Without context manager (really not recommended)asyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)connection=awaitpool.acquire(read_only=False)awaitpool.release(connection)or more usefulasyncdefdo_something():pool=awaitcreate_pool(multihost_dsn)try:connection=awaitpool.acquire(read_only=False)finally:awaitpool.release(connection)How it works?For each host from dsn string, a connection pool is created. From each pool one
connection is reserved, which is used to check the availability of the host and
its role. The minimum and maximum number of connections in the pool increases
by 1 (to reserve a system connection).For each pool a background task is created, in which the host availability and
its role (master or replica) is checked once everyrefresh_delaysecond.When switching hosts roles, hasql detects this with a slight delay.For PostgreSQL, when switching the master, all connections to all hosts are
broken (the details of implementing PostgreSQL).If there are no available hosts, the methods acquire(), acquire_master(), and
acquire_replica() wait until the host with the desired role startup.Overviewhasql.base.BasePoolManager__init__(dsn, acquire_timeout, refresh_delay, refresh_timeout, fallback_master, master_as_replica_weight, balancer_policy, pool_factory_kwargs):dsn: str- Connection string used by the connection.acquire_timeout: Union[int, float]- Default timeout (in seconds)
for connection operations. 1 sec by default.refresh_delay: Union[int, float]- Delay time (in seconds)
between host polls. 1 sec by default.refresh_timeout: Union[int, float]- Timeout (in seconds) for
trying to connect and get the host role. 1 sec by default.fallback_master: bool- Use connections from master if replicas
are missing. False by default.master_as_replica_weight: float- Probability of using the master
as a replica (from 0. to 1.; 0. - master is not used as a replica;
1. - master can be used as a replica).balancer_policy: type- Connection pool balancing policy
(hasql.balancer_policy.GreedyBalancerPolicy,hasql.balancer_policy.RandomWeightedBalancerPolicyorhasql.balancer_policy.RoundRobinBalancerPolicy).stopwatch_window_size: int- Window size for calculating the
median response time of each pool.pool_factory_kwargs: Optional[dict]- Connection pool creation
parameters that are passed to pool factory.get_pool_freesize(pool)Getting the number of free connections in the connection pool. Returns
number of free connections in the connection pool.pool- Pool for which you to be getting the number of
free connections.coroutine async-withacquire_from_pool(pool, **kwargs)Acquire a connection from pool. Returns connection to the database.pool- Pool from which you to be acquiring the connection.kwargs- Arguments to be passed to the pool acquire() method.coroutinerelease_to_pool(connection, pool, **kwargs)A coroutine that reverts connection conn to pool for future recycling.connection- Connection to be released.pool- Pool to which you are returning the connection.kwargs- Arguments to be passed to the pool release() method.is_connection_closed(connection)Returns True if connection is closed.get_last_response_time(pool)Returns database host last response time (in seconds).coroutine async-withacquire(read_only, fallback_master, timeout, **kwargs)Acquire a connection from free pool.readonly: bool-Trueif need return connection to replica,False- to master. False by default.fallback_master: Optional[bool]- Use connections from master
if replicas are missing. If None, then the default value is used.master_as_replica_weight: float- Probability of using the master
as a replica (from 0. to 1.; 0. - master is not used as a replica;
1. - master can be used as a replica).timeout: Union[int, float]- Timeout (in seconds) for connection
operations.kwargs- Arguments to be passed to the pool acquire() method.coroutine async-withacquire_master(timeout, **kwargs)Acquire a connection from free master pool.
Equivalentacquire(read_only=False)timeout: Union[int, float]- Timeout (in seconds) for
connection operations.kwargs- Arguments to be passed to the pool acquire() method.coroutine async-withacquire_replica(fallback_master, timeout, **kwargs)Acquire a connection from free master pool.
Equivalentacquire(read_only=True)fallback_master: Optional[bool]- Use connections from master if
replicas are missing. If None, then the default value is used.master_as_replica_weight: float- Probability of using the master
as a replica (from 0. to 1.; 0. - master is not used as a replica;
1. - master can be used as a replica).timeout: Union[int, float]- Timeout (in seconds) for connection
operations.kwargs- Arguments to be passed to the pool acquire() method.coroutinerelease(connection, **kwargs)A coroutine that reverts connection conn to pool for future recycling.connection- Connection to be released.kwargs- Arguments to be passed to the pool release() method.coroutineclose()Close pool. Mark all pool connections to be closed on getting back to
pool. Closed pool doesn’t allow to acquire new connections.coroutineterminate()Terminate pool. Close pool with instantly closing all acquired
connections also.coroutinewait_next_pool_check(timeout)Waiting for the next step to update host roles.coroutineready(masters_count, replicas_count, timeout)Waiting for a connection to the database hosts. If masters_count isNoneand replicas_count is None, then connection to all hosts
is expected.masters_count: Optional[int]- Minimum number of master hosts.Noneby default.replicas_count: Optional[int]- Minimum number of replica hosts.Noneby default.timeout: Union[int, float]- Timeout for database connections.
10 seconds by default.coroutinewait_all_ready()`Waiting to connect to all database hosts.coroutinewait_masters_ready(masters_count)Waiting for connection to the specified number of
database master servers.masters_count: int- Minimum number of master hosts.coroutinewait_replicas_ready(replicas_count)Waiting for connection to the specified number of
database replica servers.replicas_count: int- Minimum number of replica hosts.coroutineget_pool(read_only, fallback_master)Returns connection pool with the maximum number of free connections.readonly: bool- True if need return replica pool,False- master pool.fallback_master: Optional[bool]- Returns master pool if
replicas are missing. False by default.coroutineget_master_pools()Returns a list of all master pools.coroutineget_replica_pools(fallback_master)Returns a list of all replica pools.fallback_master: Optional[bool]- Returns a list of all master
pools if replicas are missing. False by default.pool_is_master(pool)Returns True if connection is master.pool_is_replica(pool)Returns True if connection is replica.register_connection(connection, pool)Match connection with the pool from which it was taken.
It is necessary for the release() method to work correctly.hasql.aiopg.PoolManagerhasql.aiopg_sa.PoolManagerhasql.asyncpg.PoolManagerhasql.asyncpgsa.PoolManagerhasql.psycopg3.PoolManagerBalancer policieshasql.balancer_policy.GreedyBalancerPolicyChooses pool with the most free connections. If there are several such pools,
a random one is taken.hasql.balancer_policy.RandomWeightedBalancerPolicyChooses random pool according to their weights. The weight is inversely
proportional to the response time of the database of the respective pool
(faster response - higher weight).hasql.balancer_policy.RoundRobinBalancerPolicy