Documentation
¶
Overview ¶
Package hasql provides simple and reliable way to access high-availability database setups with multiple hosts.
Index ¶
- type CheckedNode
- type CheckedNodes
- type Cluster
- func (cl *Cluster[T]) Close() (err error)
- func (cl *Cluster[T]) Err() error
- func (cl *Cluster[T]) Node(criterion NodeStateCriterion) *Node[T]
- func (cl *Cluster[T]) NodesIter(criterion NodeStateCriterion) iter.Seq[*Node[T]]
- func (cl *Cluster[T]) WaitForNode(ctx context.Context, criterion NodeStateCriterion) (*Node[T], error)
- type ClusterOpt
- type LatencyNodePicker
- type Node
- type NodeCheckError
- type NodeCheckErrors
- type NodeChecker
- type NodeDiscoverer
- type NodeInfo
- type NodeInfoProvider
- type NodePicker
- type NodeRole
- type NodeStateCriterion
- type Querier
- type RandomNodePicker
- type ReplicationNodePicker
- type RoundRobinNodePicker
- type StaticNodeDiscoverer
- type Tracer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CheckedNode ¶
type CheckedNode[T Querier] struct { Node *Node[T] Info NodeInfoProvider }
CheckedNode contains most recent state of single cluster node
type CheckedNodes ¶
type CheckedNodes[T Querier] struct { // contains filtered or unexported fields }
CheckedNodes holds references to all available cluster nodes
func (CheckedNodes[T]) Alive ¶
func (c CheckedNodes[T]) Alive() []CheckedNode[T]
Alive returns a list of all successfully checked nodes irregarding their cluster role
func (CheckedNodes[T]) Discovered ¶
func (c CheckedNodes[T]) Discovered() []*Node[T]
Discovered returns a list of nodes discovered in cluster
func (CheckedNodes[T]) Err ¶
func (c CheckedNodes[T]) Err() error
Err holds information about cause of node check failure.
func (CheckedNodes[T]) Primaries ¶
func (c CheckedNodes[T]) Primaries() []CheckedNode[T]
Primaries returns list of all successfully checked nodes with primary role
func (CheckedNodes[T]) Standbys ¶
func (c CheckedNodes[T]) Standbys() []CheckedNode[T]
Standbys returns list of all successfully checked nodes with standby role
type Cluster ¶
type Cluster[T Querier] struct { // contains filtered or unexported fields }
Cluster consists of number of 'nodes' of a single SQL database.
Example ¶
ExampleCluster shows how to setup basic hasql cluster with some custom settings
// open connections to database instances db1, err := sql.Open("pgx", "host1.example.com") if err != nil { panic(err) } db2, err := sql.Open("pgx", "host2.example.com") if err != nil { panic(err) } // register connections as nodes with some additional information nodes := []*hasql.Node[*sql.DB]{ hasql.NewNode("bear", db1), hasql.NewNode("battlestar galactica", db2), } // create NodeDiscoverer instance // here we use built-in StaticNodeDiscoverer which always returns all registered nodes discoverer := hasql.NewStaticNodeDiscoverer(nodes...) // use checker suitable for your database checker := hasql.PostgreSQLChecker // change default RandomNodePicker to RoundRobinNodePicker here picker := new(hasql.RoundRobinNodePicker[*sql.DB]) // create cluster instance using previously created discoverer, checker // and some additional options cl, err := hasql.NewCluster(discoverer, checker, // set custom picker via funcopt hasql.WithNodePicker(picker), // change interval of cluster state check hasql.WithUpdateInterval[*sql.DB](500*time.Millisecond), // change cluster check timeout value hasql.WithUpdateTimeout[*sql.DB](time.Second), ) if err != nil { panic(err) } // create context with timeout to wait for at least one alive node in cluster // note that context timeout value must be greater than cluster update interval + update timeout // otherwise you will always receive `context.DeadlineExceeded` error if one of cluster node is dead on startup ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // wait for any alive node to guarantee that your application // does not start without database dependency // this step usually performed on application startup node, err := cl.WaitForNode(ctx, hasql.Alive) if err != nil { panic(err) } // pick standby node to perform query // always check node for nilness to avoid nil pointer dereference error // node object can be nil if no alive nodes for given criterion has been found node = cl.Node(hasql.Standby) if node == nil { panic("no alive standby available") } // get connection from node and perform desired action if err := node.DB().PingContext(ctx); err != nil { panic(err) }
func NewCluster ¶
func NewCluster[T Querier](discoverer NodeDiscoverer[T], checker NodeChecker, opts ...ClusterOpt[T]) (*Cluster[T], error)
NewCluster returns object representing a single 'cluster' of SQL databases
func (*Cluster[T]) Close ¶
Close stops node updates. Close function must be called when cluster is not needed anymore. It returns combined error if multiple nodes returned errors
func (*Cluster[T]) Err ¶
Err returns cause of nodes most recent check failures. In most cases error is a list of errors of type CheckNodeErrors, original errors could be extracted using `errors.As`. Example:
var cerrs NodeCheckErrors if errors.As(err, &cerrs) { for _, cerr := range cerrs { fmt.Printf("node: %s, err: %s\n", cerr.Node(), cerr.Err()) } }
func (*Cluster[T]) Node ¶
func (cl *Cluster[T]) Node(criterion NodeStateCriterion) *Node[T]
Node returns cluster node with specified status
func (*Cluster[T]) NodesIter ¶ added in v2.1.0
func (cl *Cluster[T]) NodesIter(criterion NodeStateCriterion) iter.Seq[*Node[T]]
NodesIter returns iterator over a set of nodes. Set content is determined by given criterion. For example PreferStandby criterion will return next sequence: [standby1, standby2, standby3, primary1]. Nodes order in sequence is determined by cluster NodePicker. It is guaranteed that iterator will never return nil Node.
func (*Cluster[T]) WaitForNode ¶
func (cl *Cluster[T]) WaitForNode(ctx context.Context, criterion NodeStateCriterion) (*Node[T], error)
WaitForNode with specified status to appear or until context is canceled
type ClusterOpt ¶
ClusterOpt is a functional option type for Cluster constructor
func WithNodePicker ¶
func WithNodePicker[T Querier](picker NodePicker[T]) ClusterOpt[T]
WithNodePicker sets algorithm for node selection (e.g. random, round robin etc.)
func WithTracer ¶
func WithTracer[T Querier](tracer Tracer[T]) ClusterOpt[T]
WithTracer sets tracer for actions happening in the background
func WithUpdateInterval ¶
func WithUpdateInterval[T Querier](d time.Duration) ClusterOpt[T]
WithUpdateInterval sets interval between cluster state updates
func WithUpdateTimeout ¶
func WithUpdateTimeout[T Querier](d time.Duration) ClusterOpt[T]
WithUpdateTimeout sets timeout for update of each node in cluster
type LatencyNodePicker ¶
type LatencyNodePicker[T Querier] struct{}
LatencyNodePicker returns node with least latency and sorts checked nodes by reported latency ascending. WARNING: This picker requires that NodeInfoProvider can report node's network latency otherwise code will panic!
func (*LatencyNodePicker[T]) CompareNodes ¶
func (*LatencyNodePicker[T]) CompareNodes(a, b CheckedNode[T]) int
CompareNodes performs nodes comparison based on reported network latency
func (*LatencyNodePicker[T]) PickNode ¶
func (*LatencyNodePicker[T]) PickNode(nodes []CheckedNode[T]) CheckedNode[T]
PickNode returns node with least network latency
type Node ¶
type Node[T Querier] struct { // contains filtered or unexported fields }
Node holds reference to database connection pool with some additional data
type NodeCheckError ¶
type NodeCheckError[T Querier] struct { // contains filtered or unexported fields }
NodeCheckError implements `error` and contains information about unsuccessful node check
func (NodeCheckError[T]) Error ¶
func (n NodeCheckError[T]) Error() string
Error implements `error` interface
func (NodeCheckError[T]) Node ¶
func (n NodeCheckError[T]) Node() *Node[T]
Node returns dead node instance
func (NodeCheckError[T]) Unwrap ¶
func (n NodeCheckError[T]) Unwrap() error
Unwrap returns underlying error
type NodeCheckErrors ¶
type NodeCheckErrors[T Querier] []NodeCheckError[T]
NodeCheckErrors is a set of checked nodes errors. This type can be used in errors.As/Is as it implements errors.Unwrap method
func (NodeCheckErrors[T]) Error ¶
func (n NodeCheckErrors[T]) Error() string
func (NodeCheckErrors[T]) Unwrap ¶
func (n NodeCheckErrors[T]) Unwrap() []error
Unwrap is a helper for errors.Is/errors.As functions
type NodeChecker ¶
type NodeChecker func(context.Context, Querier) (NodeInfoProvider, error)
NodeChecker is a function that can perform request to SQL node and retrieve various information
type NodeDiscoverer ¶
type NodeDiscoverer[T Querier] interface { // DiscoverNodes returns list of nodes registered in cluster DiscoverNodes(context.Context) ([]*Node[T], error) }
NodeDiscoverer represents a provider of cluster nodes list. NodeDiscoverer must node check nodes liveness or role, just return all nodes registered in cluster
type NodeInfo ¶
type NodeInfo struct { // Role contains determined node's role in cluster ClusterRole NodeRole // Latency stores time that has been spent to send check request // and receive response from server NetworkLatency time.Duration // ReplicaLag represents how far behind is data on standby // in comparison to primary. As determination of real replication // lag is a tricky task and value type vary from one DBMS to another // (e.g. bytes count lag, time delta lag etc.) this field contains // abstract value for sorting purposes only ReplicaLag int }
NodeInfo contains various information about single cluster node
func (NodeInfo) Latency ¶
Latency reports time spend on query execution from client's point of view. It can be used in LatencyNodePicker to determine node with fastest response time
func (NodeInfo) ReplicationLag ¶
ReplicationLag reports data replication delta on standby. It can be used in ReplicationNodePicker to determine node with most up-to-date data
type NodeInfoProvider ¶
type NodeInfoProvider interface { // Role reports role of node in cluster. // For SQL servers it is usually either primary or standby Role() NodeRole }
NodeInfoProvider information about single cluster node
func MSSQLChecker ¶
func MSSQLChecker(ctx context.Context, db Querier) (NodeInfoProvider, error)
MSSQLChecker checks state of MSSQL node
func MySQLChecker ¶
func MySQLChecker(ctx context.Context, db Querier) (NodeInfoProvider, error)
MySQLChecker checks state of MySQL node. ATTENTION: database user must have REPLICATION CLIENT privilege to perform underlying query.
func PostgreSQLChecker ¶
func PostgreSQLChecker(ctx context.Context, db Querier) (NodeInfoProvider, error)
PostgreSQLChecker checks state on PostgreSQL node. It reports appropriate information for PostgreSQL nodes version 10 and higher
type NodePicker ¶
type NodePicker[T Querier] interface { // PickNode returns a single node from given set PickNode(nodes []CheckedNode[T]) CheckedNode[T] // CompareNodes is a comparison function to be used to sort checked nodes CompareNodes(a, b CheckedNode[T]) int }
NodePicker decides which node must be used from given set. It also provides a comparer to be used to pre-sort nodes for better performance
type NodeRole ¶
type NodeRole uint8
NodeRole represents role of node in SQL cluster (usually primary/standby)
type NodeStateCriterion ¶
type NodeStateCriterion uint8
NodeStateCriterion represents a node selection criterion
const ( // Alive is a criterion to choose any alive node Alive NodeStateCriterion = iota + 1 // Primary is a criterion to choose primary node Primary // Standby is a criterion to choose standby node Standby // PreferPrimary is a criterion to choose primary or any alive node PreferPrimary // PreferStandby is a criterion to choose standby or any alive node PreferStandby )
type Querier ¶
type Querier interface { // QueryRowContext executes a query that is expected to return at most one row QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row // QueryContext executes a query that returns rows, typically a SELECT QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) }
Querier describes abstract base SQL client such as database/sql.DB. Most of database/sql compatible third-party libraries already implement it
type RandomNodePicker ¶
type RandomNodePicker[T Querier] struct{}
RandomNodePicker returns random node on each call and does not sort checked nodes
func (*RandomNodePicker[T]) CompareNodes ¶
func (*RandomNodePicker[T]) CompareNodes(_, _ CheckedNode[T]) int
CompareNodes always treats nodes as equal, effectively not changing nodes order
func (*RandomNodePicker[T]) PickNode ¶
func (*RandomNodePicker[T]) PickNode(nodes []CheckedNode[T]) CheckedNode[T]
PickNode returns random node from picker
type ReplicationNodePicker ¶
type ReplicationNodePicker[T Querier] struct{}
ReplicationNodePicker returns node with smallest replication lag and sorts checked nodes by reported replication lag ascending. Note that replication lag reported by checkers can vastly differ from the real situation on standby server. WARNING: This picker requires that NodeInfoProvider can report node's replication lag otherwise code will panic!
func (*ReplicationNodePicker[T]) CompareNodes ¶
func (*ReplicationNodePicker[T]) CompareNodes(a, b CheckedNode[T]) int
CompareNodes performs nodes comparison based on reported replication lag
func (*ReplicationNodePicker[T]) PickNode ¶
func (*ReplicationNodePicker[T]) PickNode(nodes []CheckedNode[T]) CheckedNode[T]
PickNode returns node with lowest replication lag value
type RoundRobinNodePicker ¶
type RoundRobinNodePicker[T Querier] struct { // contains filtered or unexported fields }
RoundRobinNodePicker returns next node based on Round Robin algorithm and tries to preserve nodes order across checks
func (*RoundRobinNodePicker[T]) CompareNodes ¶
func (r *RoundRobinNodePicker[T]) CompareNodes(a, b CheckedNode[T]) int
CompareNodes performs lexicographical comparison of two nodes
func (*RoundRobinNodePicker[T]) PickNode ¶
func (r *RoundRobinNodePicker[T]) PickNode(nodes []CheckedNode[T]) CheckedNode[T]
PickNode returns next node in Round-Robin sequence
type StaticNodeDiscoverer ¶
type StaticNodeDiscoverer[T Querier] struct { // contains filtered or unexported fields }
StaticNodeDiscoverer always returns list of provided nodes
func NewStaticNodeDiscoverer ¶
func NewStaticNodeDiscoverer[T Querier](nodes ...*Node[T]) StaticNodeDiscoverer[T]
NewStaticNodeDiscoverer returns new staticNodeDiscoverer instance
func (StaticNodeDiscoverer[T]) DiscoverNodes ¶
func (s StaticNodeDiscoverer[T]) DiscoverNodes(_ context.Context) ([]*Node[T], error)
DiscoverNodes returns static list of nodes from StaticNodeDiscoverer
type Tracer ¶
type Tracer[T Querier] struct { // UpdateNodes is called when before updating nodes status. UpdateNodes func() // NodesUpdated is called after all nodes are updated. The nodes is a list of currently alive nodes. NodesUpdated func(nodes CheckedNodes[T]) // NodeDead is called when it is determined that specified node is dead. NodeDead func(err error) // NodeAlive is called when it is determined that specified node is alive. NodeAlive func(node CheckedNode[T]) // WaitersNotified is called when callers of 'WaitForNode' function have been notified. WaitersNotified func() }
Tracer is a set of hooks to be called at various stages of background nodes status update. Any particular hook may be nil. Functions may be called concurrently from different goroutines.