rwproxy

package module
v0.0.0-...-c94e8e7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2018 License: MIT Imports: 6 Imported by: 0

README

rwproxy: reader-writer query-distributing proxy driver

Goal

To provide reader-writer query distribution without userland changes in database usage.

Basics

rwproxy achieves its goal by being an implementation of database/sql/driver.Driver. It doesn't provide any direct database driver facilities, and instead wraps around a delegate driver which provides the concrete implementation:

Getting Started

Install rwproxy with go get:

go get github.com/nedscode/rwproxy

Package rwproxy provides an implementation of "database/sql/driver".Driver, switching between a "writer" connection and series of "reader" connections of an underlying delegate driver.

sql.Register("mysqlrw", rwproxy.New(mysql.MySQLDriver{})) // makes available a `mysqlrw` driver that reader-writer proxies MySQL connections
db, _ := sql.Open("my-writer;my-reader-1;my-reader-2") // `db` can be used to query the reader or writer connections

DSNs

The cluster is specified as a "compound" DSN to sql.Open(). The compound DSN is a semicolon-separated (;) list of DSNs for the delegate driver. The first DSN is used as the "writer", and any subsequent DSNs as a series of "readers" across which queries will be load balanced. If no readers are specified, all queries will be sent to the writer, which should behave identically to not using rwproxy at all.

Routing

rwproxy selects the most appropriate connection as follows:

if inTransaction {
    if TxOptions{ReadOnly: true} {
        reader
    } else {
        writer
    }
} else {
    Exec -> writer
    Query -> reader
}

The rwproxy *sql.Conn lazily connects to the writer and a single reader as necessary, and will retain these until it is closed by the connection pool.

Connection Pooling

Package "database/sql" provides a builtin connection pool when sql.Open() is used. Because the pooling happens at a level above (and therefore out of control of) the rwproxy driver, it is the rwproxy connections (not the delegated connections) that are pooled. This means that, at worst, rwproxy will hold open both a writer and reader connection for each item in the connection pool.

FAQ

Is there any way to force Query to run on the writer, or Exec to run on a reader?

Use database transactions to force the connection to the reader or writer:

wtx, _ := sql.Begin()
wtx.QueryRow() // will run against the writer, because the transaction is assumed to require writes

rtx, _ := sql.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
rtx.Exec() // will run against a reader, because the transaction is marked as read only
I need to perform a write, then read that (or a derived) value back from the database. How can I ensure consistency?

Use a database transaction across the write and read operations:

 // Query() is run on the same transaction (and therefore connection) as the Exec()
tx, := sql.Begin()
tx.Exec("UPDATE …")
tx.Query("SELECT …")
tx.Commit()

// also possible, but less desirable
tx := sql.Begin()
tx.Exec("UPDATE …")
tx.Commit()
// (in a different scope - forces Query to writer)
tx := sql.Begin()
tx.Query("SELECT …")
tx.Commit()

Acknowledgements

  • github.com/tsenart/nap provides similar functionality, though as a wrapper around database/sql, rather than as a driver.

Documentation

Overview

Package rwproxy provides an implementation of "database/sql/driver".Driver, switching between a "writer" connection and series of "reader" connections of an underlying delegate driver.

Goal

To provide reader-writer query distribution without userland changes in database usage.

Basics

rwproxy achieves its goal by being an implementation of database/sql/driver.Driver. It doesn't provide any direct database driver facilities, and instead wraps around a delegate driver which provides the concrete implementation:

sql.Register("mysqlrw", rwproxy.New(mysql.MySQLDriver{})) // makes available a `mysqlrw` driver that reader-writer proxies MySQL connections
db, _ := sql.Open("my-writer;my-reader-1;my-reader-2") // `db` can be used to query the reader or writer connections

DSNs

The cluster is specified as a "compound" DSN to sql.Open(). The compound DSN is a semicolon-separated (;) list of DSNs for the delegate driver. The first DSN is used as the "writer", and any subsequent DSNs as a series of "readers" across which queries will be load balanced. If no readers are specified, all queries will be sent to the writer, which should behave identically to not using rwproxy at all.

Routing

rwproxy selects the most appropriate connection as follows:

if inTransaction {
	if TxOptions{ReadOnly: true} {
		reader
	} else {
		writer
	}
} else {
	Exec -> writer
	Query -> reader
}

The rwproxy *sql.Conn lazily connects to the writer and a single reader as necessary, and will retain these until the it is closed by the connection pool.

Connection Pooling

Package "database/sql" provides a builtin connection pool when sql.Open() is used. Because the pooling happens at a level above (and therefore out of control of) the rwproxy driver, it is the rwproxy connections (not the delegated connections) that are pooled. This means that, at worst, rwproxy will hold open both a writer and reader connection for each item in the connection pool.

Index

Constants

This section is empty.

Variables

View Source
var ErrConnBeginTxUnsupported = errors.New("rwproxy: driver doesn't support BeginTx")

ErrConnBeginTxUnsupported is provided when conn.BeginTx() is used but not supported by the underlying driver

View Source
var ErrNamedParametersNotSupported = errors.New("rwproxy: driver does not support the use of Named Parameters")

ErrNamedParametersNotSupported is provided when named parameters are used but unsupported by the underlying driver

View Source
var ErrUnexpectedTxClose = errors.New("rwproxy: unexpected proxied transaction close")

ErrUnexpectedTxClose is provided when a closed proxied transaction is not currently expected by the connection

Functions

func MakeCompoundDSN

func MakeCompoundDSN(writerDSN string, readerDSNs ...string) string

MakeCompoundDSN combines writer and reader DSNs to build a compound DSN

func ParseCompoundDSN

func ParseCompoundDSN(dsn string) (string, []string)

ParseCompoundDSN breaks up a compound DSN into its component DSNs

Types

type ConnCloseError

type ConnCloseError struct {
	// contains filtered or unexported fields
}

ConnCloseError is provided when conn.Close() fails, encapsulating errors from one or both proxied connections

func (ConnCloseError) Error

func (e ConnCloseError) Error() string

type Driver

type Driver struct {
	// contains filtered or unexported fields
}

Driver is a "database/sql/driver".Driver implemntation that distributes reads/writes

func New

func New(delegate driver.Driver, opts ...Option) *Driver

New wraps a lower level delegate "database/sql/driver".Driver with an rwproxy driver

func (*Driver) Open

func (d *Driver) Open(name string) (driver.Conn, error)

Open implements "database/sql/driver".Driver.Open(), taking a compound DSN containing DSNs for writer and reader connections

func (*Driver) Parent

func (d *Driver) Parent() driver.Driver

Parent returns the wrapped Driver

type IncompleteDSNError

type IncompleteDSNError struct {
	DSN string
}

IncompleteDSNError indicates that the compound DSN is incomplete, and cannot be used

func (IncompleteDSNError) Error

func (e IncompleteDSNError) Error() string

type Log

type Log func(string)

Log is function that is called with near-trace-level debugging to inspect proxying behaviour

type Option

type Option func(*Driver)

Option is a configuration option for a Driver instance

func WithLog

func WithLog(l Log) Option

WithLog creates an Option for the given Log implementation

The log will be called with near-trace-level debugging to inspect proxying behaviour

func WithReaderSelector

func WithReaderSelector(rs ReaderSelector) Option

WithReaderSelector creates an Option for the given ReaderSelector implementation

type ProxiedStatementCloseError

type ProxiedStatementCloseError struct {
	Errs []error
}

ProxiedStatementCloseError is provided when an rwproxy Stmt can't close one of its proxied Stmts

func (ProxiedStatementCloseError) Error

func (pscerr ProxiedStatementCloseError) Error() string

type ReaderSelector

type ReaderSelector func(ctx context.Context, d driver.Driver, readerDSNs []string) (driver.Conn, error)

ReaderSelector implements a read distribution strategy

func RoundRobinReaderSelector

func RoundRobinReaderSelector() ReaderSelector

RoundRobinReaderSelector implements a round robin strategy for selecting a reader by DSN

Directories

Path Synopsis
Package sqldrivermock provides a basic null implementation of "database/sql/driver".Driver
Package sqldrivermock provides a basic null implementation of "database/sql/driver".Driver

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL