WIP: Postgres read-only replica

This commit is contained in:
binwiederhier
2026-03-10 22:17:40 -04:00
parent 997e20fa3f
commit f1865749d7
16 changed files with 229 additions and 83 deletions

View File

@@ -95,7 +95,8 @@ type Config struct {
ListenUnixMode fs.FileMode
KeyFile string
CertFile string
DatabaseURL string // PostgreSQL connection string (e.g. "postgres://user:pass@host:5432/ntfy")
DatabaseURL string // PostgreSQL connection string (e.g. "postgres://user:pass@host:5432/ntfy")
DatabaseReplicaURLs []string // PostgreSQL read replica connection strings
FirebaseKeyFile string
CacheFile string
CacheDuration time.Duration

View File

@@ -33,6 +33,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v2"
"heckel.io/ntfy/v2/db"
"heckel.io/ntfy/v2/db/pg"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/message"
@@ -47,7 +48,7 @@ import (
// Server is the main server, providing the UI and API for ntfy
type Server struct {
config *Config
db *sql.DB // Shared PostgreSQL connection pool, nil when using SQLite
db *db.DB // Shared PostgreSQL connection pool (with optional replicas), nil when using SQLite
httpServer *http.Server
httpsServer *http.Server
httpMetricsServer *http.Server
@@ -179,13 +180,26 @@ func New(conf *Config) (*Server, error) {
stripe = newStripeAPI()
}
// Open shared PostgreSQL connection pool if configured
var pool *sql.DB
var pool *db.DB
if conf.DatabaseURL != "" {
var err error
pool, err = pg.Open(conf.DatabaseURL)
primary, err := pg.Open(conf.DatabaseURL)
if err != nil {
return nil, err
}
var replicas []*sql.DB
for _, replicaURL := range conf.DatabaseReplicaURLs {
r, err := pg.Open(replicaURL)
if err != nil {
// Close already-opened replicas before returning
for _, opened := range replicas {
opened.Close()
}
primary.Close()
return nil, fmt.Errorf("failed to open database replica: %w", err)
}
replicas = append(replicas, r)
}
pool = db.NewDB(primary, replicas)
}
messageCache, err := createMessageCache(conf, pool)
if err != nil {
@@ -277,7 +291,7 @@ func New(conf *Config) (*Server, error) {
return s, nil
}
func createMessageCache(conf *Config, pool *sql.DB) (*message.Cache, error) {
func createMessageCache(conf *Config, pool *db.DB) (*message.Cache, error) {
if conf.CacheDuration == 0 {
return message.NewNopStore()
} else if pool != nil {