How to Implement Read Replicas and Write Splitting in Go
Go does not have built-in support for read replicas or write splitting; you must implement this logic using a database connection pool and a custom routing layer. Use the database/sql package to manage separate connection pools for your primary (write) and replica (read) databases, then route queries based on the operation type.
package main
import (
"database/sql"
"context"
"fmt"
"log"
)
var writeDB *sql.DB // Primary database
var readDB *sql.DB // Read replica database
func init() {
var err error
writeDB, err = sql.Open("postgres", "postgres://user:pass@primary:5432/db")
if err != nil {
log.Fatalf("failed to open write DB: %v", err)
}
if err = writeDB.PingContext(context.Background()); err != nil {
log.Fatalf("failed to ping write DB: %v", err)
}
readDB, err = sql.Open("postgres", "postgres://user:pass@replica:5432/db")
if err != nil {
log.Fatalf("failed to open read DB: %v", err)
}
if err = readDB.PingContext(context.Background()); err != nil {
log.Fatalf("failed to ping read DB: %v", err)
}
}
func CreateUser(ctx context.Context, name string) error {
_, err := writeDB.ExecContext(ctx, "INSERT INTO users (name) VALUES ($1)", name)
return err
}
func GetUsers(ctx context.Context) ([]string, error) {
rows, err := readDB.QueryContext(ctx, "SELECT name FROM users")
if err != nil {
return nil, err
}
defer rows.Close()
var names []string
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
names = append(names, name)
}
return names, nil
}
func main() {
ctx := context.Background()
if err := CreateUser(ctx, "Alice"); err != nil {
log.Fatalf("failed to create user: %v", err)
}
users, err := GetUsers(ctx)
if err != nil {
log.Fatalf("failed to get users: %v", err)
}
fmt.Printf("Users: %v\n", users)
}