dataprovider: remove transaction for quota update

The update is atomic so no transaction is needed.
Addionally a transaction will ask for a new connection to the pool
and this can deadlock if the pool has a max connection limit too low.

Also make configurable the pool size instead of hard code to the cpu number.

Fixes #47
This commit is contained in:
Nicola Murino
2019-09-13 08:14:07 +02:00
parent bf00ca334d
commit e7eb3476b7
6 changed files with 13 additions and 41 deletions

View File

@@ -153,6 +153,7 @@ The `sftpgo` configuration file contains the following sections:
- 0, disable quota tracking. REST API to scan user dir and update quota will do nothing - 0, disable quota tracking. REST API to scan user dir and update quota will do nothing
- 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions - 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
- 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. With this configuration the "quota scan" REST API can still be used to periodically update space usage for users without quota restrictions - 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. With this configuration the "quota scan" REST API can still be used to periodically update space usage for users without quota restrictions
- `pool_size`, integer. Sets the maximum number of open connections for mysql and postgresql driver. Default 0 (unlimited)
- **"httpd"**, the configuration for the HTTP server used to serve REST API - **"httpd"**, the configuration for the HTTP server used to serve REST API
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080 - `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080
- `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1" - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1"

View File

@@ -73,6 +73,7 @@ func init() {
ManageUsers: 1, ManageUsers: 1,
SSLMode: 0, SSLMode: 0,
TrackQuota: 1, TrackQuota: 1,
PoolSize: 0,
}, },
HTTPDConfig: api.HTTPDConf{ HTTPDConfig: api.HTTPDConf{
BindPort: 8080, BindPort: 8080,

View File

@@ -91,6 +91,9 @@ type Config struct {
// With this configuration the "quota scan" REST API can still be used to periodically update space usage // With this configuration the "quota scan" REST API can still be used to periodically update space usage
// for users without quota restrictions // for users without quota restrictions
TrackQuota int `json:"track_quota" mapstructure:"track_quota"` TrackQuota int `json:"track_quota" mapstructure:"track_quota"`
// Sets the maximum number of open connections for mysql and postgresql driver.
// Default 0 (unlimited)
PoolSize int `json:"pool_size" mapstructure:"pool_size"`
} }
// ValidationError raised if input data is not valid // ValidationError raised if input data is not valid

View File

@@ -3,7 +3,6 @@ package dataprovider
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"runtime"
"time" "time"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
@@ -26,10 +25,8 @@ func initializeMySQLProvider() error {
} }
dbHandle, err := sql.Open("mysql", connectionString) dbHandle, err := sql.Open("mysql", connectionString)
if err == nil { if err == nil {
numCPU := runtime.NumCPU() providerLog(logger.LevelDebug, "mysql database handle created, connection string: %#v, pool size: %v", connectionString, config.PoolSize)
providerLog(logger.LevelDebug, "mysql database handle created, connection string: %#v, pool size: %v", connectionString, numCPU) dbHandle.SetMaxOpenConns(config.PoolSize)
dbHandle.SetMaxIdleConns(numCPU)
dbHandle.SetMaxOpenConns(numCPU)
dbHandle.SetConnMaxLifetime(1800 * time.Second) dbHandle.SetConnMaxLifetime(1800 * time.Second)
provider = MySQLProvider{dbHandle: dbHandle} provider = MySQLProvider{dbHandle: dbHandle}
} else { } else {
@@ -51,21 +48,7 @@ func (p MySQLProvider) getUserByID(ID int64) (User, error) {
} }
func (p MySQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error { func (p MySQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
tx, err := p.dbHandle.Begin() return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
if err != nil {
providerLog(logger.LevelWarn, "error starting transaction to update quota for user %v: %v", username, err)
return err
}
err = sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
if err == nil {
err = tx.Commit()
} else {
err = tx.Rollback()
}
if err != nil {
providerLog(logger.LevelWarn, "error closing transaction to update quota for user %v: %v", username, err)
}
return err
} }
func (p MySQLProvider) getUsedQuota(username string) (int, int64, error) { func (p MySQLProvider) getUsedQuota(username string) (int, int64, error) {

View File

@@ -3,7 +3,6 @@ package dataprovider
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"runtime"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
) )
@@ -25,10 +24,8 @@ func initializePGSQLProvider() error {
} }
dbHandle, err := sql.Open("postgres", connectionString) dbHandle, err := sql.Open("postgres", connectionString)
if err == nil { if err == nil {
numCPU := runtime.NumCPU() providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v", connectionString, config.PoolSize)
providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v", connectionString, numCPU) dbHandle.SetMaxOpenConns(config.PoolSize)
dbHandle.SetMaxIdleConns(numCPU)
dbHandle.SetMaxOpenConns(numCPU)
provider = PGSQLProvider{dbHandle: dbHandle} provider = PGSQLProvider{dbHandle: dbHandle}
} else { } else {
providerLog(logger.LevelWarn, "error creating postgres database handler, connection string: %#v, error: %v", connectionString, err) providerLog(logger.LevelWarn, "error creating postgres database handler, connection string: %#v, error: %v", connectionString, err)
@@ -49,21 +46,7 @@ func (p PGSQLProvider) getUserByID(ID int64) (User, error) {
} }
func (p PGSQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error { func (p PGSQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
tx, err := p.dbHandle.Begin() return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
if err != nil {
providerLog(logger.LevelWarn, "error starting transaction to update quota for user %v: %v", username, err)
return err
}
err = sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
if err == nil {
err = tx.Commit()
} else {
err = tx.Rollback()
}
if err != nil {
providerLog(logger.LevelWarn, "error closing transaction to update quota for user %v: %v", username, err)
}
return err
} }
func (p PGSQLProvider) getUsedQuota(username string) (int, int64, error) { func (p PGSQLProvider) getUsedQuota(username string) (int, int64, error) {

View File

@@ -30,7 +30,8 @@
"connection_string": "", "connection_string": "",
"users_table": "users", "users_table": "users",
"manage_users": 1, "manage_users": 1,
"track_quota": 2 "track_quota": 2,
"pool_size": 0
}, },
"httpd": { "httpd": {
"bind_port": 8080, "bind_port": 8080,