Categorygithub.com/segmentio/nap
repositorypackage
0.0.0-20240709031905-db51f95f22e0
Repository: https://github.com/segmentio/nap.git
Documentation: pkg.go.dev

# README

Nap

Note Segment has paused maintenance on this project, but may return it to an active status in the future. Issues and pull requests from external contributors are not being considered, although internal contributions may appear from time to time. The project remains available under its open source license for anyone to use.

Nap is a library that abstracts access to primary-replica physical SQL servers topologies as a single logical database mimicking the standard sql.DB APIs.

Install

$ go get github.com/tsenart/nap

Usage

package main

import (
  "log"

  "github.com/tsenart/nap"
  _ "github.com/go-sql-driver/mysql" // Any sql.DB works
)

func main() {
  // The first DSN is assumed to be the primary and all
  // other to be replicas
  dsns := "tcp://user:password@primary/dbname;"
  dsns += "tcp://user:password@replica01/dbname;"
  dsns += "tcp://user:password@replica02/dbname"

  db, err := nap.Open("mysql", dsns)
  if err != nil {
    log.Fatal(err)
  }

  if err := db.Ping(); err != nil {
    log.Fatalf("Some physical database is unreachable: %s", err)
  }

  // Read queries are directed to replicas with Query and QueryRow.
  // Always use Query or QueryRow for SELECTS
  // Load distribution is round-robin only for now.
  var count int
  err = db.QueryRow("SELECT COUNT(*) FROM sometable").Scan(&count)
  if err != nil {
    log.Fatal(err)
  }

  // Write queries are directed to the primary with Exec.
  // Always use Exec for INSERTS, UPDATES
  err = db.Exec("UPDATE sometable SET something = 1")
  if err != nil {
    log.Fatal(err)
  }

  // Prepared statements are aggregates. If any of the underlying
  // physical databases fails to prepare the statement, the call will
  // return an error. On success, if Exec is called, then the
  // primary is used, if Query or QueryRow are called, then a replica
  // is used.
  stmt, err := db.Prepare("SELECT * FROM sometable WHERE something = ?")
  if err != nil {
    log.Fatal(err)
  }

  // Transactions always use the primary
  tx, err := db.Begin()
  if err != nil {
    log.Fatal(err)
  }
  // Do something transactional ...
  if err = tx.Commit(); err != nil {
    log.Fatal(err)
  }

  // If needed, one can access the primary or a replica explicitly.
  primary, replica := db.Primary(), db.Replica()
}

Todo

  • Support other replica load balancing algorithms.

License

See LICENSE