[chore] set max open / idle conns + conn max lifetime for both postgres and sqlite (#1369)
* [chore] set max open / idle conns + conn max lifetime for both postgres and sqlite * reduce cache size default to 8MiB, reduce connections to 2 * cpu * introduce max open conns multiplier, tune sqlite and pg separately * go fmt
This commit is contained in:
parent
17eecfb6d9
commit
782169da76
|
@ -108,6 +108,30 @@ db-tls-mode: "disable"
|
||||||
# Default: ""
|
# Default: ""
|
||||||
db-tls-ca-cert: ""
|
db-tls-ca-cert: ""
|
||||||
|
|
||||||
|
# Int. Number to multiply by CPU count to set permitted total of open database connections (in-use and idle).
|
||||||
|
# You can use this setting to tune your database connection behavior, though most admins won't need to touch it.
|
||||||
|
#
|
||||||
|
# Example values for multiplier 8:
|
||||||
|
#
|
||||||
|
# 1 cpu = 08 open connections
|
||||||
|
# 2 cpu = 16 open connections
|
||||||
|
# 4 cpu = 32 open connections
|
||||||
|
#
|
||||||
|
# Example values for multiplier 4:
|
||||||
|
#
|
||||||
|
# 1 cpu = 04 open connections
|
||||||
|
# 2 cpu = 08 open connections
|
||||||
|
# 4 cpu = 16 open connections
|
||||||
|
#
|
||||||
|
# A multiplier of 8 is a sensible default, but you may wish to increase this for instances
|
||||||
|
# running on very performant hardware, or decrease it for instances using v. slow CPUs.
|
||||||
|
#
|
||||||
|
# If you set this to 0 or less, it will be adjusted to 1.
|
||||||
|
#
|
||||||
|
# Examples: [16, 8, 10, 2]
|
||||||
|
# Default: 8
|
||||||
|
db-max-open-conns-multiplier: 8
|
||||||
|
|
||||||
# String. SQLite journaling mode.
|
# String. SQLite journaling mode.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
# If set to empty string, the sqlite default will be used.
|
# If set to empty string, the sqlite default will be used.
|
||||||
|
@ -126,11 +150,11 @@ db-sqlite-synchronous: "NORMAL"
|
||||||
|
|
||||||
# Byte size. SQlite cache size.
|
# Byte size. SQlite cache size.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
# If set to empty string or zero, the sqlite default will be used.
|
# If set to empty string or zero, the sqlite default (2MiB) will be used.
|
||||||
# See: https://www.sqlite.org/pragma.html#pragma_cache_size
|
# See: https://www.sqlite.org/pragma.html#pragma_cache_size
|
||||||
# Examples: ["32MiB", "0", "64MiB"]
|
# Examples: ["0", "2MiB", "8MiB", "64MiB"]
|
||||||
# Default: "64MiB"
|
# Default: "8MiB"
|
||||||
db-sqlite-cache-size: "64MiB"
|
db-sqlite-cache-size: "8MiB"
|
||||||
|
|
||||||
# Duration. SQlite busy timeout.
|
# Duration. SQlite busy timeout.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
|
|
|
@ -164,6 +164,30 @@ db-tls-mode: "disable"
|
||||||
# Default: ""
|
# Default: ""
|
||||||
db-tls-ca-cert: ""
|
db-tls-ca-cert: ""
|
||||||
|
|
||||||
|
# Int. Number to multiply by CPU count to set permitted total of open database connections (in-use and idle).
|
||||||
|
# You can use this setting to tune your database connection behavior, though most admins won't need to touch it.
|
||||||
|
#
|
||||||
|
# Example values for multiplier 8:
|
||||||
|
#
|
||||||
|
# 1 cpu = 08 open connections
|
||||||
|
# 2 cpu = 16 open connections
|
||||||
|
# 4 cpu = 32 open connections
|
||||||
|
#
|
||||||
|
# Example values for multiplier 4:
|
||||||
|
#
|
||||||
|
# 1 cpu = 04 open connections
|
||||||
|
# 2 cpu = 08 open connections
|
||||||
|
# 4 cpu = 16 open connections
|
||||||
|
#
|
||||||
|
# A multiplier of 8 is a sensible default, but you may wish to increase this for instances
|
||||||
|
# running on very performant hardware, or decrease it for instances using v. slow CPUs.
|
||||||
|
#
|
||||||
|
# If you set this to 0 or less, it will be adjusted to 1.
|
||||||
|
#
|
||||||
|
# Examples: [16, 8, 10, 2]
|
||||||
|
# Default: 8
|
||||||
|
db-max-open-conns-multiplier: 8
|
||||||
|
|
||||||
# String. SQLite journaling mode.
|
# String. SQLite journaling mode.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
# If set to empty string, the sqlite default will be used.
|
# If set to empty string, the sqlite default will be used.
|
||||||
|
@ -182,11 +206,11 @@ db-sqlite-synchronous: "NORMAL"
|
||||||
|
|
||||||
# Byte size. SQlite cache size.
|
# Byte size. SQlite cache size.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
# If set to empty string or zero, the sqlite default will be used.
|
# If set to empty string or zero, the sqlite default (2MiB) will be used.
|
||||||
# See: https://www.sqlite.org/pragma.html#pragma_cache_size
|
# See: https://www.sqlite.org/pragma.html#pragma_cache_size
|
||||||
# Examples: ["32MiB", "0", "64MiB"]
|
# Examples: ["0", "2MiB", "8MiB", "64MiB"]
|
||||||
# Default: "64MiB"
|
# Default: "8MiB"
|
||||||
db-sqlite-cache-size: "64MiB"
|
db-sqlite-cache-size: "8MiB"
|
||||||
|
|
||||||
# Duration. SQlite busy timeout.
|
# Duration. SQlite busy timeout.
|
||||||
# SQLite only -- unused otherwise.
|
# SQLite only -- unused otherwise.
|
||||||
|
|
|
@ -58,18 +58,19 @@ type Configuration struct {
|
||||||
TrustedProxies []string `name:"trusted-proxies" usage:"Proxies to trust when parsing x-forwarded headers into real IPs."`
|
TrustedProxies []string `name:"trusted-proxies" usage:"Proxies to trust when parsing x-forwarded headers into real IPs."`
|
||||||
SoftwareVersion string `name:"software-version" usage:""`
|
SoftwareVersion string `name:"software-version" usage:""`
|
||||||
|
|
||||||
DbType string `name:"db-type" usage:"Database type: eg., postgres"`
|
DbType string `name:"db-type" usage:"Database type: eg., postgres"`
|
||||||
DbAddress string `name:"db-address" usage:"Database ipv4 address, hostname, or filename"`
|
DbAddress string `name:"db-address" usage:"Database ipv4 address, hostname, or filename"`
|
||||||
DbPort int `name:"db-port" usage:"Database port"`
|
DbPort int `name:"db-port" usage:"Database port"`
|
||||||
DbUser string `name:"db-user" usage:"Database username"`
|
DbUser string `name:"db-user" usage:"Database username"`
|
||||||
DbPassword string `name:"db-password" usage:"Database password"`
|
DbPassword string `name:"db-password" usage:"Database password"`
|
||||||
DbDatabase string `name:"db-database" usage:"Database name"`
|
DbDatabase string `name:"db-database" usage:"Database name"`
|
||||||
DbTLSMode string `name:"db-tls-mode" usage:"Database tls mode"`
|
DbTLSMode string `name:"db-tls-mode" usage:"Database tls mode"`
|
||||||
DbTLSCACert string `name:"db-tls-ca-cert" usage:"Path to CA cert for db tls connection"`
|
DbTLSCACert string `name:"db-tls-ca-cert" usage:"Path to CA cert for db tls connection"`
|
||||||
DbSqliteJournalMode string `name:"db-sqlite-journal-mode" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_journal_mode"`
|
DbMaxOpenConnsMultiplier int `name:"db-max-open-conns-multiplier" usage:"Multiplier to use per cpu for max open database connections. 0 or less is normalized to 1."`
|
||||||
DbSqliteSynchronous string `name:"db-sqlite-synchronous" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_synchronous"`
|
DbSqliteJournalMode string `name:"db-sqlite-journal-mode" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_journal_mode"`
|
||||||
DbSqliteCacheSize bytesize.Size `name:"db-sqlite-cache-size" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_cache_size"`
|
DbSqliteSynchronous string `name:"db-sqlite-synchronous" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_synchronous"`
|
||||||
DbSqliteBusyTimeout time.Duration `name:"db-sqlite-busy-timeout" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_busy_timeout"`
|
DbSqliteCacheSize bytesize.Size `name:"db-sqlite-cache-size" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_cache_size"`
|
||||||
|
DbSqliteBusyTimeout time.Duration `name:"db-sqlite-busy-timeout" usage:"Sqlite only: see https://www.sqlite.org/pragma.html#pragma_busy_timeout"`
|
||||||
|
|
||||||
WebTemplateBaseDir string `name:"web-template-base-dir" usage:"Basedir for html templating files for rendering pages and composing emails."`
|
WebTemplateBaseDir string `name:"web-template-base-dir" usage:"Basedir for html templating files for rendering pages and composing emails."`
|
||||||
WebAssetBaseDir string `name:"web-asset-base-dir" usage:"Directory to serve static assets from, accessible at example.org/assets/"`
|
WebAssetBaseDir string `name:"web-asset-base-dir" usage:"Directory to serve static assets from, accessible at example.org/assets/"`
|
||||||
|
|
|
@ -40,18 +40,19 @@ var Defaults = Configuration{
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
TrustedProxies: []string{"127.0.0.1/32", "::1"}, // localhost
|
TrustedProxies: []string{"127.0.0.1/32", "::1"}, // localhost
|
||||||
|
|
||||||
DbType: "postgres",
|
DbType: "postgres",
|
||||||
DbAddress: "",
|
DbAddress: "",
|
||||||
DbPort: 5432,
|
DbPort: 5432,
|
||||||
DbUser: "",
|
DbUser: "",
|
||||||
DbPassword: "",
|
DbPassword: "",
|
||||||
DbDatabase: "gotosocial",
|
DbDatabase: "gotosocial",
|
||||||
DbTLSMode: "disable",
|
DbTLSMode: "disable",
|
||||||
DbTLSCACert: "",
|
DbTLSCACert: "",
|
||||||
DbSqliteJournalMode: "WAL",
|
DbMaxOpenConnsMultiplier: 8,
|
||||||
DbSqliteSynchronous: "NORMAL",
|
DbSqliteJournalMode: "WAL",
|
||||||
DbSqliteCacheSize: 64 * bytesize.MiB,
|
DbSqliteSynchronous: "NORMAL",
|
||||||
DbSqliteBusyTimeout: time.Minute * 5,
|
DbSqliteCacheSize: 8 * bytesize.MiB,
|
||||||
|
DbSqliteBusyTimeout: time.Minute * 5,
|
||||||
|
|
||||||
WebTemplateBaseDir: "./web/template/",
|
WebTemplateBaseDir: "./web/template/",
|
||||||
WebAssetBaseDir: "./web/assets/",
|
WebAssetBaseDir: "./web/assets/",
|
||||||
|
|
|
@ -51,6 +51,7 @@ func (s *ConfigState) AddGlobalFlags(cmd *cobra.Command) {
|
||||||
cmd.PersistentFlags().String(DbDatabaseFlag(), cfg.DbDatabase, fieldtag("DbDatabase", "usage"))
|
cmd.PersistentFlags().String(DbDatabaseFlag(), cfg.DbDatabase, fieldtag("DbDatabase", "usage"))
|
||||||
cmd.PersistentFlags().String(DbTLSModeFlag(), cfg.DbTLSMode, fieldtag("DbTLSMode", "usage"))
|
cmd.PersistentFlags().String(DbTLSModeFlag(), cfg.DbTLSMode, fieldtag("DbTLSMode", "usage"))
|
||||||
cmd.PersistentFlags().String(DbTLSCACertFlag(), cfg.DbTLSCACert, fieldtag("DbTLSCACert", "usage"))
|
cmd.PersistentFlags().String(DbTLSCACertFlag(), cfg.DbTLSCACert, fieldtag("DbTLSCACert", "usage"))
|
||||||
|
cmd.PersistentFlags().Int(DbMaxOpenConnsMultiplierFlag(), cfg.DbMaxOpenConnsMultiplier, fieldtag("DbMaxOpenConnsMultiplier", "usage"))
|
||||||
cmd.PersistentFlags().String(DbSqliteJournalModeFlag(), cfg.DbSqliteJournalMode, fieldtag("DbSqliteJournalMode", "usage"))
|
cmd.PersistentFlags().String(DbSqliteJournalModeFlag(), cfg.DbSqliteJournalMode, fieldtag("DbSqliteJournalMode", "usage"))
|
||||||
cmd.PersistentFlags().String(DbSqliteSynchronousFlag(), cfg.DbSqliteSynchronous, fieldtag("DbSqliteSynchronous", "usage"))
|
cmd.PersistentFlags().String(DbSqliteSynchronousFlag(), cfg.DbSqliteSynchronous, fieldtag("DbSqliteSynchronous", "usage"))
|
||||||
cmd.PersistentFlags().Uint64(DbSqliteCacheSizeFlag(), uint64(cfg.DbSqliteCacheSize), fieldtag("DbSqliteCacheSize", "usage"))
|
cmd.PersistentFlags().Uint64(DbSqliteCacheSizeFlag(), uint64(cfg.DbSqliteCacheSize), fieldtag("DbSqliteCacheSize", "usage"))
|
||||||
|
|
|
@ -524,6 +524,31 @@ func GetDbTLSCACert() string { return global.GetDbTLSCACert() }
|
||||||
// SetDbTLSCACert safely sets the value for global configuration 'DbTLSCACert' field
|
// SetDbTLSCACert safely sets the value for global configuration 'DbTLSCACert' field
|
||||||
func SetDbTLSCACert(v string) { global.SetDbTLSCACert(v) }
|
func SetDbTLSCACert(v string) { global.SetDbTLSCACert(v) }
|
||||||
|
|
||||||
|
// GetDbMaxOpenConnsMultiplier safely fetches the Configuration value for state's 'DbMaxOpenConnsMultiplier' field
|
||||||
|
func (st *ConfigState) GetDbMaxOpenConnsMultiplier() (v int) {
|
||||||
|
st.mutex.Lock()
|
||||||
|
v = st.config.DbMaxOpenConnsMultiplier
|
||||||
|
st.mutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDbMaxOpenConnsMultiplier safely sets the Configuration value for state's 'DbMaxOpenConnsMultiplier' field
|
||||||
|
func (st *ConfigState) SetDbMaxOpenConnsMultiplier(v int) {
|
||||||
|
st.mutex.Lock()
|
||||||
|
defer st.mutex.Unlock()
|
||||||
|
st.config.DbMaxOpenConnsMultiplier = v
|
||||||
|
st.reloadToViper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DbMaxOpenConnsMultiplierFlag returns the flag name for the 'DbMaxOpenConnsMultiplier' field
|
||||||
|
func DbMaxOpenConnsMultiplierFlag() string { return "db-max-open-conns-multiplier" }
|
||||||
|
|
||||||
|
// GetDbMaxOpenConnsMultiplier safely fetches the value for global configuration 'DbMaxOpenConnsMultiplier' field
|
||||||
|
func GetDbMaxOpenConnsMultiplier() int { return global.GetDbMaxOpenConnsMultiplier() }
|
||||||
|
|
||||||
|
// SetDbMaxOpenConnsMultiplier safely sets the value for global configuration 'DbMaxOpenConnsMultiplier' field
|
||||||
|
func SetDbMaxOpenConnsMultiplier(v int) { global.SetDbMaxOpenConnsMultiplier(v) }
|
||||||
|
|
||||||
// GetDbSqliteJournalMode safely fetches the Configuration value for state's 'DbSqliteJournalMode' field
|
// GetDbSqliteJournalMode safely fetches the Configuration value for state's 'DbSqliteJournalMode' field
|
||||||
func (st *ConfigState) GetDbSqliteJournalMode() (v string) {
|
func (st *ConfigState) GetDbSqliteJournalMode() (v string) {
|
||||||
st.mutex.Lock()
|
st.mutex.Lock()
|
||||||
|
|
|
@ -222,6 +222,32 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pgConn(ctx context.Context) (*DBConn, error) {
|
||||||
|
opts, err := deriveBunDBPGOptions() //nolint:contextcheck
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create bundb postgres options: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqldb := stdlib.OpenDB(*opts)
|
||||||
|
|
||||||
|
// Tune db connections for postgres, see:
|
||||||
|
// - https://bun.uptrace.dev/guide/running-bun-in-production.html#database-sql
|
||||||
|
// - https://www.alexedwards.net/blog/configuring-sqldb
|
||||||
|
sqldb.SetMaxOpenConns(maxOpenConns()) // x number of conns per CPU
|
||||||
|
sqldb.SetMaxIdleConns(2) // assume default 2; if max idle is less than max open, it will be automatically adjusted
|
||||||
|
sqldb.SetConnMaxLifetime(5 * time.Minute) // fine to kill old connections
|
||||||
|
|
||||||
|
conn := WrapDBConn(bun.NewDB(sqldb, pgdialect.New()))
|
||||||
|
|
||||||
|
// ping to check the db is there and listening
|
||||||
|
if err := conn.PingContext(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("postgres ping: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("connected to POSTGRES database")
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
func sqliteConn(ctx context.Context) (*DBConn, error) {
|
func sqliteConn(ctx context.Context) (*DBConn, error) {
|
||||||
// validate db address has actually been set
|
// validate db address has actually been set
|
||||||
address := config.GetDbAddress()
|
address := config.GetDbAddress()
|
||||||
|
@ -236,13 +262,10 @@ func sqliteConn(ctx context.Context) (*DBConn, error) {
|
||||||
// Append our own SQLite preferences
|
// Append our own SQLite preferences
|
||||||
address = "file:" + address
|
address = "file:" + address
|
||||||
|
|
||||||
var inMem bool
|
|
||||||
|
|
||||||
if address == "file::memory:" {
|
if address == "file::memory:" {
|
||||||
address = fmt.Sprintf("file:%s?mode=memory&cache=shared", uuid.NewString())
|
address = fmt.Sprintf("file:%s?mode=memory&cache=shared", uuid.NewString())
|
||||||
log.Infof("using in-memory database address " + address)
|
log.Infof("using in-memory database address " + address)
|
||||||
log.Warn("sqlite in-memory database should only be used for debugging")
|
log.Warn("sqlite in-memory database should only be used for debugging")
|
||||||
inMem = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open new DB instance
|
// Open new DB instance
|
||||||
|
@ -254,12 +277,12 @@ func sqliteConn(ctx context.Context) (*DBConn, error) {
|
||||||
return nil, fmt.Errorf("could not open sqlite db: %s", err)
|
return nil, fmt.Errorf("could not open sqlite db: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if inMem {
|
// Tune db connections for sqlite, see:
|
||||||
// don't close connections on disconnect -- otherwise
|
// - https://bun.uptrace.dev/guide/running-bun-in-production.html#database-sql
|
||||||
// the SQLite database will be deleted when there
|
// - https://www.alexedwards.net/blog/configuring-sqldb
|
||||||
// are no active connections
|
sqldb.SetMaxOpenConns(maxOpenConns()) // x number of conns per cpu
|
||||||
sqldb.SetConnMaxLifetime(0)
|
sqldb.SetMaxIdleConns(1) // only keep max 1 idle connection around
|
||||||
}
|
sqldb.SetConnMaxLifetime(0) // don't kill connections due to age
|
||||||
|
|
||||||
// Wrap Bun database conn in our own wrapper
|
// Wrap Bun database conn in our own wrapper
|
||||||
conn := WrapDBConn(bun.NewDB(sqldb, sqlitedialect.New()))
|
conn := WrapDBConn(bun.NewDB(sqldb, sqlitedialect.New()))
|
||||||
|
@ -276,79 +299,20 @@ func sqliteConn(ctx context.Context) (*DBConn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sqlitePragmas(ctx context.Context, conn *DBConn) error {
|
|
||||||
var pragmas [][]string
|
|
||||||
if mode := config.GetDbSqliteJournalMode(); mode != "" {
|
|
||||||
// Set the user provided SQLite journal mode
|
|
||||||
pragmas = append(pragmas, []string{"journal_mode", mode})
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode := config.GetDbSqliteSynchronous(); mode != "" {
|
|
||||||
// Set the user provided SQLite synchronous mode
|
|
||||||
pragmas = append(pragmas, []string{"synchronous", mode})
|
|
||||||
}
|
|
||||||
|
|
||||||
if size := config.GetDbSqliteCacheSize(); size > 0 {
|
|
||||||
// Set the user provided SQLite cache size (in kibibytes)
|
|
||||||
// Prepend a '-' character to this to indicate to sqlite
|
|
||||||
// that we're giving kibibytes rather than num pages.
|
|
||||||
// https://www.sqlite.org/pragma.html#pragma_cache_size
|
|
||||||
s := "-" + strconv.FormatUint(uint64(size/bytesize.KiB), 10)
|
|
||||||
pragmas = append(pragmas, []string{"cache_size", s})
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout := config.GetDbSqliteBusyTimeout(); timeout > 0 {
|
|
||||||
t := strconv.FormatInt(timeout.Milliseconds(), 10)
|
|
||||||
pragmas = append(pragmas, []string{"busy_timeout", t})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range pragmas {
|
|
||||||
pk := p[0]
|
|
||||||
pv := p[1]
|
|
||||||
|
|
||||||
if _, err := conn.DB.ExecContext(ctx, "PRAGMA ?=?", bun.Ident(pk), bun.Safe(pv)); err != nil {
|
|
||||||
return fmt.Errorf("error executing sqlite pragma %s: %w", pk, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res string
|
|
||||||
if err := conn.DB.NewRaw("PRAGMA ?", bun.Ident(pk)).Scan(ctx, &res); err != nil {
|
|
||||||
return fmt.Errorf("error scanning sqlite pragma %s: %w", pv, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("sqlite pragma %s set to %s", pk, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pgConn(ctx context.Context) (*DBConn, error) {
|
|
||||||
opts, err := deriveBunDBPGOptions() //nolint:contextcheck
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create bundb postgres options: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sqldb := stdlib.OpenDB(*opts)
|
|
||||||
|
|
||||||
// https://bun.uptrace.dev/postgres/running-bun-in-production.html#database-sql
|
|
||||||
maxOpenConns := 4 * runtime.GOMAXPROCS(0)
|
|
||||||
sqldb.SetMaxOpenConns(maxOpenConns)
|
|
||||||
sqldb.SetMaxIdleConns(maxOpenConns)
|
|
||||||
|
|
||||||
conn := WrapDBConn(bun.NewDB(sqldb, pgdialect.New()))
|
|
||||||
|
|
||||||
// ping to check the db is there and listening
|
|
||||||
if err := conn.PingContext(ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("postgres ping: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("connected to POSTGRES database")
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
HANDY STUFF
|
HANDY STUFF
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// maxOpenConns returns multiplier * GOMAXPROCS,
|
||||||
|
// clamping multiplier to 1 if it was below 1.
|
||||||
|
func maxOpenConns() int {
|
||||||
|
multiplier := config.GetDbMaxOpenConnsMultiplier()
|
||||||
|
if multiplier < 1 {
|
||||||
|
multiplier = 1
|
||||||
|
}
|
||||||
|
return multiplier * runtime.GOMAXPROCS(0)
|
||||||
|
}
|
||||||
|
|
||||||
// deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options
|
// deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options
|
||||||
// with sensible defaults, or an error if it's not satisfied by the provided config.
|
// with sensible defaults, or an error if it's not satisfied by the provided config.
|
||||||
func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
|
func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
|
||||||
|
@ -434,6 +398,53 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sqlitePragmas sets desired sqlite pragmas based on configured values, and
|
||||||
|
// logs the results of the pragma queries. Errors if something goes wrong.
|
||||||
|
func sqlitePragmas(ctx context.Context, conn *DBConn) error {
|
||||||
|
var pragmas [][]string
|
||||||
|
if mode := config.GetDbSqliteJournalMode(); mode != "" {
|
||||||
|
// Set the user provided SQLite journal mode
|
||||||
|
pragmas = append(pragmas, []string{"journal_mode", mode})
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode := config.GetDbSqliteSynchronous(); mode != "" {
|
||||||
|
// Set the user provided SQLite synchronous mode
|
||||||
|
pragmas = append(pragmas, []string{"synchronous", mode})
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := config.GetDbSqliteCacheSize(); size > 0 {
|
||||||
|
// Set the user provided SQLite cache size (in kibibytes)
|
||||||
|
// Prepend a '-' character to this to indicate to sqlite
|
||||||
|
// that we're giving kibibytes rather than num pages.
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_cache_size
|
||||||
|
s := "-" + strconv.FormatUint(uint64(size/bytesize.KiB), 10)
|
||||||
|
pragmas = append(pragmas, []string{"cache_size", s})
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout := config.GetDbSqliteBusyTimeout(); timeout > 0 {
|
||||||
|
t := strconv.FormatInt(timeout.Milliseconds(), 10)
|
||||||
|
pragmas = append(pragmas, []string{"busy_timeout", t})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range pragmas {
|
||||||
|
pk := p[0]
|
||||||
|
pv := p[1]
|
||||||
|
|
||||||
|
if _, err := conn.DB.ExecContext(ctx, "PRAGMA ?=?", bun.Ident(pk), bun.Safe(pv)); err != nil {
|
||||||
|
return fmt.Errorf("error executing sqlite pragma %s: %w", pk, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res string
|
||||||
|
if err := conn.DB.NewRaw("PRAGMA ?", bun.Ident(pk)).Scan(ctx, &res); err != nil {
|
||||||
|
return fmt.Errorf("error scanning sqlite pragma %s: %w", pv, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("sqlite pragma %s set to %s", pk, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CONVERSION FUNCTIONS
|
CONVERSION FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"instance-expose-suspended-web":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-max-open-conns-multiplier":3,"db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"instance-expose-suspended-web":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
||||||
|
|
||||||
# Set all the environment variables to
|
# Set all the environment variables to
|
||||||
# ensure that these are parsed without panic
|
# ensure that these are parsed without panic
|
||||||
|
@ -22,6 +22,7 @@ GTS_DB_PORT=6969 \
|
||||||
GTS_DB_USER='sex-haver' \
|
GTS_DB_USER='sex-haver' \
|
||||||
GTS_DB_PASSWORD='hunter2' \
|
GTS_DB_PASSWORD='hunter2' \
|
||||||
GTS_DB_DATABASE='gotosocial_prod' \
|
GTS_DB_DATABASE='gotosocial_prod' \
|
||||||
|
GTS_DB_MAX_OPEN_CONNS_MULTIPLIER=3 \
|
||||||
GTS_DB_SQLITE_JOURNAL_MODE='DELETE' \
|
GTS_DB_SQLITE_JOURNAL_MODE='DELETE' \
|
||||||
GTS_DB_SQLITE_SYNCHRONOUS='FULL' \
|
GTS_DB_SQLITE_SYNCHRONOUS='FULL' \
|
||||||
GTS_DB_SQLITE_CACHE_SIZE=0 \
|
GTS_DB_SQLITE_CACHE_SIZE=0 \
|
||||||
|
|
|
@ -46,16 +46,19 @@ var testDefaults = config.Configuration{
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
TrustedProxies: []string{"127.0.0.1/32", "::1"},
|
TrustedProxies: []string{"127.0.0.1/32", "::1"},
|
||||||
|
|
||||||
DbType: "sqlite",
|
DbType: "sqlite",
|
||||||
DbAddress: ":memory:",
|
DbAddress: ":memory:",
|
||||||
DbPort: 5432,
|
DbPort: 5432,
|
||||||
DbUser: "postgres",
|
DbUser: "postgres",
|
||||||
DbPassword: "postgres",
|
DbPassword: "postgres",
|
||||||
DbDatabase: "postgres",
|
DbDatabase: "postgres",
|
||||||
DbSqliteJournalMode: "WAL",
|
DbTLSMode: "disable",
|
||||||
DbSqliteSynchronous: "NORMAL",
|
DbTLSCACert: "",
|
||||||
DbSqliteCacheSize: 64 * bytesize.MiB,
|
DbMaxOpenConnsMultiplier: 8,
|
||||||
DbSqliteBusyTimeout: time.Minute * 5,
|
DbSqliteJournalMode: "WAL",
|
||||||
|
DbSqliteSynchronous: "NORMAL",
|
||||||
|
DbSqliteCacheSize: 8 * bytesize.MiB,
|
||||||
|
DbSqliteBusyTimeout: time.Minute * 5,
|
||||||
|
|
||||||
WebTemplateBaseDir: "./web/template/",
|
WebTemplateBaseDir: "./web/template/",
|
||||||
WebAssetBaseDir: "./web/assets/",
|
WebAssetBaseDir: "./web/assets/",
|
||||||
|
|
Loading…
Reference in New Issue