Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve nakama migrate errors #1258

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 43 additions & 31 deletions server/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import (
"go.uber.org/zap"
)

const dbErrorDatabaseDoesNotExist = pgerrcode.InvalidCatalogName
const (
dbErrorDatabaseInvalidPassword = pgerrcode.InvalidPassword
dbErrorDatabaseDoesNotExist = pgerrcode.InvalidCatalogName
)

var ErrDatabaseDriverMismatch = errors.New("database driver mismatch")

Expand All @@ -50,7 +53,7 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
}
query := parsedURL.Query()
var queryUpdated bool
if len(query.Get("sslmode")) == 0 {
if query.Get("sslmode") == "" {
query.Set("sslmode", "prefer")
queryUpdated = true
}
Expand All @@ -77,16 +80,14 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
logger.Fatal("Failed to open database", zap.Error(err))
}

dbPing(ctx, logger, db, dbName)

if create {
var nakamaDBExists bool
if err = db.QueryRow("SELECT EXISTS (SELECT 1 from pg_database WHERE datname = $1)", dbName).Scan(&nakamaDBExists); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == dbErrorDatabaseDoesNotExist {
nakamaDBExists = false
} else {
db.Close()
logger.Fatal("Failed to check if db exists", zap.String("db", dbName), zap.Error(err))
}
nakamaDBExists = false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can be removed as the logger.Fatal will terminate the execution anyways

db.Close()
logger.Fatal("Failed to check if db exists", zap.String("db", dbName), zap.Error(err))
}

if !nakamaDBExists {
Expand All @@ -104,45 +105,32 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
logger.Fatal("Failed to create database", zap.Error(err))
}
db.Close()

parsedURL.Path = fmt.Sprintf("/%s", dbName)
db, err = sql.Open("pgx", parsedURL.String())
if err != nil {
db.Close()
logger.Fatal("Failed to open database", zap.Error(err))
}
}
}

logger.Debug("Complete database connection URL", zap.String("raw_url", parsedURL.String()))
db, err = sql.Open("pgx", parsedURL.String())
if err != nil {
logger.Fatal("Error connecting to database", zap.Error(err))
}
// Limit max time allowed across database ping and version fetch to 15 seconds total.
pingCtx, pingCtxCancelFn := context.WithTimeout(ctx, 15*time.Second)
defer pingCtxCancelFn()
if err = db.PingContext(pingCtx); err != nil {
if strings.HasSuffix(err.Error(), "does not exist (SQLSTATE 3D000)") {
logger.Fatal("Database schema not found, run `nakama migrate up`", zap.Error(err))
}
logger.Fatal("Error pinging database", zap.Error(err))
logger.Fatal("Failed to open database", zap.Error(err))
}

dbPing(ctx, logger, db, dbName)

db.SetConnMaxLifetime(time.Millisecond * time.Duration(config.GetDatabase().ConnMaxLifetimeMs))
db.SetMaxOpenConns(config.GetDatabase().MaxOpenConns)
db.SetMaxIdleConns(config.GetDatabase().MaxIdleConns)

var dbVersion string
if err = db.QueryRowContext(pingCtx, "SELECT version()").Scan(&dbVersion); err != nil {
logger.Fatal("Error querying database version", zap.Error(err))
versionCtx, versionCtxCancelFn := context.WithTimeout(ctx, 5*time.Second)
defer versionCtxCancelFn()
if err = db.QueryRowContext(versionCtx, "SELECT version()").Scan(&dbVersion); err != nil {
logger.Fatal("Failed to query database version", zap.Error(err))
}

logger.Info("Database information", zap.String("version", dbVersion))
if strings.Split(dbVersion, " ")[0] == "CockroachDB" {
isCockroach = true
} else {
isCockroach = false
}
isCockroach = strings.HasPrefix(dbVersion, "CockroachDB ")

// Periodically check database hostname for underlying address changes.
go func() {
Expand Down Expand Up @@ -237,6 +225,30 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
return db
}

func dbPing(ctx context.Context, logger *zap.Logger, db *sql.DB, dbName string) {
pingCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

err := db.PingContext(pingCtx)
if err != nil {
db.Close()

errLogger := logger.With(zap.String("db", dbName), zap.Error(err))

var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case dbErrorDatabaseInvalidPassword:
errLogger.Fatal("Invalid credentials")
case dbErrorDatabaseDoesNotExist:
errLogger.Fatal("Database schema not found, run `nakama migrate up`")
}
} else {
errLogger.Fatal("Failed to ping database")
}
}
}

func dbResolveAddress(ctx context.Context, logger *zap.Logger, host string) ([]string, map[string]struct{}) {
resolveCtx, resolveCtxCancelFn := context.WithTimeout(ctx, 15*time.Second)
defer resolveCtxCancelFn()
Expand Down
Loading