Config: Set TCP timeout for establishing a database connection #4059

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-03-31 09:58:49 +02:00
parent 37c3c9d624
commit 7336304828
7 changed files with 45 additions and 5 deletions

View File

@@ -41,7 +41,7 @@ services:
PHOTOPRISM_DATABASE_PASSWORD: "photoprism"
PHOTOPRISM_TEST_DRIVER: "sqlite"
PHOTOPRISM_TEST_DSN: ".test.db"
PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true"
# PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_STORAGE_PATH: "/go/src/github.com/photoprism/photoprism/storage"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/storage/originals"

View File

@@ -78,7 +78,7 @@ services:
PHOTOPRISM_DATABASE_USER: "root"
PHOTOPRISM_DATABASE_PASSWORD: "photoprism"
PHOTOPRISM_TEST_DRIVER: "sqlite"
# PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true"
# PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_STORAGE_PATH: "/go/src/github.com/photoprism/photoprism/storage"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/storage/originals"

View File

@@ -72,20 +72,22 @@ func (c *Config) DatabaseDsn() string {
}
return fmt.Sprintf(
"%s:%s@%s/%s?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
"%s:%s@%s/%s?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=%ds",
c.DatabaseUser(),
c.DatabasePassword(),
address,
c.DatabaseName(),
c.DatabaseTimeout(),
)
case Postgres:
return fmt.Sprintf(
"user=%s password=%s dbname=%s host=%s port=%d sslmode=disable TimeZone=UTC",
"user=%s password=%s dbname=%s host=%s port=%d connect_timeout=%d sslmode=disable TimeZone=UTC",
c.DatabaseUser(),
c.DatabasePassword(),
c.DatabaseName(),
c.DatabaseHost(),
c.DatabasePort(),
c.DatabaseTimeout(),
)
case SQLite3:
return filepath.Join(c.StoragePath(), "index.db?_busy_timeout=5000")
@@ -209,6 +211,21 @@ func (c *Config) DatabasePassword() string {
return c.options.DatabasePassword
}
// DatabaseTimeout returns the TCP timeout in seconds for establishing a database connection:
// - https://github.com/photoprism/photoprism/issues/4059#issuecomment-1989119004
// - https://github.com/go-sql-driver/mysql/blob/master/README.md#timeout
func (c *Config) DatabaseTimeout() int {
// Ensure that the timeout is between 1 and a maximum
// of 60 seconds, with a default of 15 seconds.
if c.options.DatabaseTimeout <= 0 {
return 15
} else if c.options.DatabaseTimeout > 60 {
return 60
}
return c.options.DatabaseTimeout
}
// DatabaseConns returns the maximum number of open connections to the database.
func (c *Config) DatabaseConns() int {
limit := c.options.DatabaseConns

View File

@@ -96,7 +96,7 @@ func TestConfig_DatabaseDsn(t *testing.T) {
assert.Equal(t, SQLite3, driver)
c.options.DatabaseDsn = ""
c.options.DatabaseDriver = "MariaDB"
assert.Equal(t, "photoprism:@tcp(localhost)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true", c.DatabaseDsn())
assert.Equal(t, "photoprism:@tcp(localhost)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", c.DatabaseDsn())
c.options.DatabaseDriver = "tidb"
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDsn())
c.options.DatabaseDriver = "Postgres"
@@ -117,6 +117,21 @@ func TestConfig_DatabaseFile(t *testing.T) {
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDsn())
}
func TestConfig_DatabaseTimeout(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, 15, c.DatabaseTimeout())
c.options.DatabaseTimeout = 1
assert.Equal(t, 1, c.DatabaseTimeout())
c.options.DatabaseTimeout = -1
assert.Equal(t, 15, c.DatabaseTimeout())
c.options.DatabaseTimeout = 120
assert.Equal(t, 60, c.DatabaseTimeout())
c.options.DatabaseTimeout = 0
assert.Equal(t, 15, c.DatabaseTimeout())
c.options.DatabaseTimeout = 15
assert.Equal(t, 15, c.DatabaseTimeout())
}
func TestConfig_DatabaseConns(t *testing.T) {
c := NewConfig(CliTestContext())
c.options.DatabaseConns = 28

View File

@@ -571,6 +571,12 @@ var Flags = CliFlags{
Usage: "database user `PASSWORD`",
EnvVar: EnvVar("DATABASE_PASSWORD"),
}}, {
Flag: cli.IntFlag{
Name: "database-timeout",
Usage: "timeout in `SECONDS` for establishing a database connection (1-60)",
EnvVar: EnvVar("DATABASE_TIMEOUT"),
Value: 15,
}}, {
Flag: cli.IntFlag{
Name: "database-conns",
Usage: "maximum `NUMBER` of open database connections",

View File

@@ -128,6 +128,7 @@ type Options struct {
DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`
DatabaseUser string `yaml:"DatabaseUser" json:"-" flag:"database-user"`
DatabasePassword string `yaml:"DatabasePassword" json:"-" flag:"database-password"`
DatabaseTimeout int `yaml:"DatabaseTimeout" json:"-" flag:"database-timeout"`
DatabaseConns int `yaml:"DatabaseConns" json:"-" flag:"database-conns"`
DatabaseConnsIdle int `yaml:"DatabaseConnsIdle" json:"-" flag:"database-conns-idle"`
SipsBin string `yaml:"SipsBin" json:"-" flag:"sips-bin"`

View File

@@ -179,6 +179,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"database-port", c.DatabasePortString()},
{"database-user", c.DatabaseUser()},
{"database-password", strings.Repeat("*", utf8.RuneCountInString(c.DatabasePassword()))},
{"database-timeout", fmt.Sprintf("%d", c.DatabaseTimeout())},
{"database-conns", fmt.Sprintf("%d", c.DatabaseConns())},
{"database-conns-idle", fmt.Sprintf("%d", c.DatabaseConnsIdle())},
{"mariadb-bin", c.MariadbBin()},