dbcontrol

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2021 License: MIT Imports: 4 Imported by: 0

README

dbcontrol

This package is a wrapper on Go's standard database/sql, providing a general mechanism to limit the number of active connections to the database, no matter the driver you use. Provided that you don't explicitly declare sql.Row, sql.Rows or sql.Stmt variables, you can use dbcontrol just by adding a couple of calls (setting the actual limit and wrapping up the DB) and using dbcontrol.DB instead of sql.DB. All operations from database/sql are then transparently supported.

We use this package at VividCortex to cope with concurrent access to our HTTP servers, while a native solution to limit the number of connections is not included in Go's standard library itself.

Documentation

Please read the generated package documentation.

Getting Started

This package is a wrapper on Go's standard database/sql, providing a general mechanism so that you're free to use statements as usual, yet have the number of active connections limited. A wrapper DB type is declared, that supports all standard operations from database/sql. To use it, you should set the maximum number of connections you want to allow, just like:

dbcontrol.SetConcurrency(10)

All databases opened by the package afterwards will use a maximum of 10 connections. You can change this setting as often as you wish, but keep in mind that the number is bound to databases as they are opened, i.e., changing this concurrency setting has no effect on already-opened databases. Note also that you can get the default non-limited behavior by setting concurrency to zero. To open a DB you proceed just like with the database/sql package, like so:

db, err := dbcontrol.Open("mysql", dsn)

Note that sql.Row, sql.Rows and sql.Stmt types are overridden by this package, but that's probably transparent unless you declare the types explicitly. If you declare variables using the := operator you'll be fine. Usage now follows the expected pattern from database/sql:

rows, err := db.Query("select id, name from customers")
if err != nil {
	log.Fatal(err)
}

for rows.Next() {
	var id int
	var name string
	if err := rows.Scan(&id, &name); err != nil {
		log.Fatal(err)
	}

	fmt.Println(id, name)
}

The full set of features at database/sql is supported, including transactions, even though not all functions need to be overridden. This package was designed to provide the feature with minimum overhead, and thus uses knowledge of database/sql internals to know when locking is required/appropriate. As an extension, you can set a channel to receive the locking duration each time a connection has to be waited for. This can work as an aid to help you tune the pool size or otherwise work around concurrency problems. You can also set a channel where notifications will be sent every time a connection is held for longer than a certain timeout. The notification includes the full stack trace of the caller at the time the connection was requested. This can prove useful to identify long-running queries that are locking connections, and possibly impeding others from running. The feature can be turned on and off at will. A small performance penalty will be paid if on (that of retrieving the caller's stack), but none if the feature is off (the default).

Contributing

We only accept pull requests for minor fixes or improvements. This includes:

  • Small bug fixes
  • Typos
  • Documentation or comments

Please open issues to discuss new features. Pull requests for new features will be rejected, so we recommend forking the repository and making changes in your fork for your use case.

License

Copyright (c) 2013 VividCortex, licensed under the MIT license. Please see the LICENSE file for details.

Documentation

Overview

Package dbcontrol limits the number of active connections for database/sql.

Implementations of Go's database/sql package prior to Go 1.2, don't let the user put a limit on the number of active connections to the underlying DB. If enough concurrent requests are made, so that the package runs out of available connections, then more are requested from the driver no matter how many you have at the pool already. Hence, unless you take precautions in your design/code, or there's specific support from the actual DB driver you're using, you are likely to hit some other limit (DB engine itself, OS) or simply run out of resources.

None of those situations are desirable, of course. If you hit DB or OS limits on the number of connections, then many of your statements will start failing cause no connection is available for them to use. You can get around it if you're lucky enough to have driver support, but then you are tied to a particular DB.

This package is a wrapper on Go's standard database/sql, providing a general mechanism so that you're free to use statements as usual, yet have the number of active connections limited. A wrapper DB type is declared, that supports all standard operations from database/sql. To use it, you should set the maximum number of connections you want to allow, just like:

dbcontrol.SetConcurrency(10)

All databases opened by the package afterwards will use a maximum of 10 connections. You can change this setting as often as you wish, but keep in mind that the number is bound to databases as they are opened, i.e., changing this concurrency setting has no effect on already-opened databases. Note also that you can get the default non-limited behavior by setting concurrency to zero. To open a DB you proceed just like with the database/sql package, like so:

db, err := dbcontrol.Open("mysql", dsn)

Note that sql.Row, sql.Rows and sql.Stmt types are overridden by this package, but that's probably transparent unless you declare the types explicitly. If you declare variables using the := operator you'll be fine. Usage now follows the expected pattern from database/sql:

rows, err := db.Query("select id, name from customers")
if err != nil {
	log.Fatal(err)
}

for rows.Next() {
	var id int
	var name string
	if err := rows.Scan(&id, &name); err != nil {
		log.Fatal(err)
	}

	fmt.Println(id, name)
}

The full set of features at database/sql is supported, including transactions, even though not all functions need to be overridden. This package was designed to provide the feature with minimum overhead, and thus uses knowledge of database/sql internals to know when locking is required/appropriate. As an extension, you can set a channel to receive the locking duration each time a connection has to be waited for. This can work as an aid to help you tune the pool size or otherwise work around concurrency problems. You can also set a channel where notifications will be sent every time a connection is held for longer than a certain timeout. The notification includes the full stack trace of the caller at the time the connection was requested. This can prove useful to identify long-running queries that are locking connections, and possibly impeding others from running. The feature can be turned on and off at will. A small performance penalty will be paid if on (that of retrieving the caller's stack), but none if the feature is off (the default).

Note that only functions specific to this package or with altered semantics are documented. Please refer to the database/sql package documentation for more information.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Concurrency

func Concurrency() int

Concurrency returns the concurrency setting. See SetConcurrency().

func SetConcurrency

func SetConcurrency(count int)

SetConcurrency sets the maximum number of simultaneous connections per database, for all subsequent DBs that are opened. (Note that this doesn't affect databases that are already open.) No capping on the number of connections is performed if concurrency is set to a non-possitive value.

Types

type DB

type DB struct {
	*sql.DB
	// contains filtered or unexported fields
}

DB is the main type wrapping up sql.DB. You should use it just like you would sql.DB. If a connection is required and not available, the statement using the type will block until another connection is returned to the pool.

func Open

func Open(driver, dsn string) (*DB, error)

func (*DB) Begin

func (db *DB) Begin() (*Tx, error)

func (*DB) Exec

func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error)

func (*DB) MaxConns

func (db *DB) MaxConns() int

MaxConn returns the maximum number of connections for the DB.

func (*DB) Ping

func (db *DB) Ping() error

func (*DB) Prepare

func (db *DB) Prepare(query string) (*Stmt, error)

func (*DB) Query

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

func (*DB) QueryRow

func (db *DB) QueryRow(query string, args ...interface{}) *Row

func (*DB) SetBlockDurationCh

func (db *DB) SetBlockDurationCh(c chan<- time.Duration)

SetBlockDurationCh sets a channel used to report blocks on connections. Each time a connection has to be waited for due to the limit imposed by SetConcurrency(), this channel will receive the duration for that wait as soon as the connection becomes available. Setting the channel to nil (i.e., calling SetBlockDurationCh(nil)) will close the previously assigned channel, if any. Note also that this operation is safe to be called in parallel with other database requests.

func (*DB) SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

SetMaxIdleConns sets the maximum number of idle connections to the database. However, note that this only makes sense if you're not limiting the number of concurrent connections. Databases opened under SetConcurrency(n) for n>0 will silently ignore this call. (The maximum number of connections in that case will match the concurrency value n.)

func (*DB) SetUsageTimeout

func (db *DB) SetUsageTimeout(c chan<- string, timeout time.Duration)

SetUsageTimeout sets a maximum time for connection usage since it was granted to the caller (i.e., usage starts when a spare connection could be withdrawn from the pool, in case connection limiting is in use; see SetConcurrency()). After the time has elapsed a notification will be sent to the provided channel including the stack trace for the offending consumer (at the time the connection was requested). Setting the timeout to zero (the default) disables this feature. Note that this function is safe to be called anytime. Changes in the timeout will take effect for new requests; pending timers will still use the previous value. Changing the channel takes effect immediately, though. The previously set channel is guaranteed not to be used again after SetUsageTimeout() returns, thus allowing to safely close it if appropriate. Setting the channel to nil disables all pending and future notifications, until set to another valid channel. Note though that, in order to avoid needless resource usage, setting the channel to nil implies that no further timers will be started. (That is, you won't get a notification for a long running consumer that requested a connection when the channel was nil, even if you set the channel to a non-nil value before the timeout would expire. Consider fixing the channel and filtering events when reading from it if you're looking for that effect.) Note also that the timer expiring, whether notified or not, has no effect whatsoever on the routine using the connection.

type Row

type Row struct {
	*sql.Row
	// contains filtered or unexported fields
}

func (*Row) Scan

func (row *Row) Scan(dest ...interface{}) error

type Rows

type Rows struct {
	*sql.Rows
	// contains filtered or unexported fields
}

func (*Rows) Close

func (rows *Rows) Close() error

func (*Rows) Next

func (rows *Rows) Next() bool

type Stmt

type Stmt struct {
	*sql.Stmt
	// contains filtered or unexported fields
}

func (*Stmt) Exec

func (s *Stmt) Exec(args ...interface{}) (sql.Result, error)

func (*Stmt) Query

func (s *Stmt) Query(args ...interface{}) (*Rows, error)

func (*Stmt) QueryRow

func (s *Stmt) QueryRow(args ...interface{}) *Row

type Tx

type Tx struct {
	*sql.Tx
	// contains filtered or unexported fields
}

func (*Tx) Commit

func (tx *Tx) Commit() error

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Jump to

Keyboard shortcuts

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