diff --git a/README.md b/README.md index 0d1dee8..464c42f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import ```go -import "golang.yandex/hasql" +import "golang.yandex/hasql/v2" ``` to your code, and then `go [build|run|test]` will automatically fetch the @@ -30,19 +30,22 @@ necessary dependencies. Otherwise, to install the `hasql` package, run the following command: ```console -$ go get -u golang.yandex/hasql +$ go get -u golang.yandex/hasql/v2 ``` ## How does it work -`hasql` operates using standard `database/sql` connection pool objects. User creates `*sql.DB` objects for each node of database cluster and passes them to constructor. Library keeps up to date information on state of each node by 'pinging' them periodically. User is provided with a set of interfaces to retrieve `*sql.DB` object suitable for required operation. +`hasql` operates using standard `database/sql` connection pool objects. User creates `*sql.DB`-compatible objects for each node of database cluster and passes them to constructor. Library keeps up to date information on state of each node by 'pinging' them periodically. User is provided with a set of interfaces to retrieve database node object suitable for required operation. ```go dbFoo, _ := sql.Open("pgx", "host=foo") dbBar, _ := sql.Open("pgx", "host=bar") -cl, err := hasql.NewCluster( - []hasql.Node{hasql.NewNode("foo", dbFoo), hasql.NewNode("bar", dbBar) }, - checkers.PostgreSQL, + +discoverer := NewStaticNodeDiscoverer( + NewNode("foo", dbFoo), + NewNode("bar", dbBar), ) + +cl, err := hasql.NewCluster(discoverer, hasql.PostgreSQLChecker) if err != nil { ... } node := cl.Primary() @@ -50,62 +53,71 @@ if node == nil { err := cl.Err() // most recent errors for all nodes in the cluster } -// Do anything you like -fmt.Println("Node address", node.Addr) +fmt.Printf("got node %s\n", node) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() + if err = node.DB().PingContext(ctx); err != nil { ... } ``` `hasql` does not configure provided connection pools in any way. It is user's job to set them up properly. Library does handle their lifetime though - pools are closed when `Cluster` object is closed. +### Concepts and entities + +**Cluster** is a set of `database/sql`-compatible nodes that tracks their lifespan and provides access to each individual nodes. + +**Node** is a single database instance in high-availability cluster. + +**Node discoverer** provides nodes objects to cluster. This abstraction allows user to dynamically change set of cluster nodes, for example collect nodes list via Service Discovery (etcd, Consul). + +**Node checker** collects information about current state of individual node in cluster, such as: cluster role, network latency, replication lag, etc. + +**Node picker** picks node from cluster by given criterion using predefined algorithm: random, round-robin, lowest latency, etc. + ### Supported criteria -_Alive primary_|_Alive standby_|_Any alive_ node, or _none_ otherwise +_Alive primary_|_Alive standby_|_Any alive_ node or _none_ otherwise ```go -node := c.Primary() -if node == nil { ... } +node := c.Node(hasql.Alive) ``` -_Alive primary_|_Alive standby_, or _any alive_ node, or _none_ otherwise +_Alive primary_ or _none_ otherwise ```go -node := c.PreferPrimary() -if node == nil { ... } +node := c.Node(hasql.Primary) ``` -### Ways of accessing nodes -Any of _currently alive_ nodes _satisfying criteria_, or _none_ otherwise +_Alive standby_ or _none_ otherwise ```go -node := c.Primary() -if node == nil { ... } +node := c.Node(hasql.Standby) ``` -Any of _currently alive_ nodes _satisfying criteria_, or _wait_ for one to become _alive_ +_Alive primary_|_Alive standby_ or _none_ otherwise ```go -ctx, cancel := context.WithTimeout(context.Background(), time.Second) -defer cancel() -node, err := c.WaitForPrimary(ctx) -if err == nil { ... } +node := c.Node(hasql.PreferPrimary) +``` + +_Alive standby_|_Alive primary_ or _none_ otherwise +```go +node := c.Node(hasql.PreferStandby) ``` ### Node pickers When user asks `Cluster` object for a node a random one from a list of suitable nodes is returned. User can override this behavior by providing a custom node picker. -Library provides a couple of predefined pickers. For example if user wants 'closest' node (with lowest latency) `PickNodeClosest` picker should be used. +Library provides a couple of predefined pickers. For example if user wants 'closest' node (with lowest latency) `LatencyNodePicker` should be used. ```go cl, err := hasql.NewCluster( - []hasql.Node{hasql.NewNode("foo", dbFoo), hasql.NewNode("bar", dbBar) }, - checkers.PostgreSQL, - hasql.WithNodePicker(hasql.PickNodeClosest()), + hasql.NewStaticNodeDiscoverer(hasql.NewNode("foo", dbFoo), hasql.NewNode("bar", dbBar)), + hasql.PostgreSQLChecker, + hasql.WithNodePicker(new(hasql.LatencyNodePicker[*sql.DB])), ) -if err != nil { ... } ``` ## Supported databases -Since library works over standard `database/sql` it supports any database that has a `database/sql` driver. All it requires is a database-specific checker function that can tell if node is primary or standby. +Since library requires `Querier` interface, which describes a subset of `database/sql.DB` methods, it supports any database that has a `database/sql` driver. All it requires is a database-specific checker function that can provide node state info. -Check out `golang.yandex/hasql/checkers` package for more information. +Check out `node_checker.go` file for more information. ### Caveats Node's state is transient at any given time. If `Primary()` returns a node it does not mean that node is still primary when you execute statement on it. All it means is that it was primary when it was last checked. Nodes can change their state at a whim or even go offline and `hasql` can't control it in any manner. @@ -115,8 +127,3 @@ This is one of the reasons why nodes do not expose their perceived state to user ## Extensions ### Instrumentation You can add instrumentation via `Tracer` object similar to [httptrace](https://godoc.org/net/http/httptrace) in standard library. - -### sqlx -`hasql` can operate over `database/sql` pools wrapped with [sqlx](https://github.com/jmoiron/sqlx). It works the same as with standard library but requires user to import `golang.yandex/hasql/sqlx` instead. - -Refer to `golang.yandex/hasql/sqlx` package for more information.