DB optimization: pool, golang-migrate, consolidate to single Postgres
- Fix DB_NAME=db_yal -> mydb in api_yal .env - Add connection pool (MaxOpenConns 25, MaxIdleConns 10, ConnMaxLifetime 30m) - Replace GORM AutoMigrate with golang-migrate in api_yal and api_bb - Create embedded SQL migrations for both APIs - Add DB_SCHEMA support to api_bb config - Consolidate to single Postgres: db_bb -> schema 'bb' on db container - Remove db_bb service, bb-network, db_bb volume from compose - Remove api_tp targets from Makefile - Clean up old migrate.go
This commit is contained in:
@@ -3,7 +3,7 @@ DB_HOST=db_tp
|
||||
DB_PORT=5432
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_NAME=db_yal
|
||||
DB_NAME=mydb
|
||||
APP_PORT=8787
|
||||
JWT_SECRET=secret
|
||||
UPLOAD_PATH=./storage/uploads
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.25.0
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||
golang.org/x/crypto v0.49.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
@@ -13,12 +14,16 @@ require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
|
||||
@@ -2,13 +2,19 @@ package database
|
||||
|
||||
import (
|
||||
"api_yal/internal/config"
|
||||
"api_yal/internal/models"
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/migrations"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"api_yal/internal/logger"
|
||||
)
|
||||
|
||||
func NewPostgresConnection(cfg *config.Config) (*gorm.DB, error) {
|
||||
@@ -23,45 +29,47 @@ func NewPostgresConnection(cfg *config.Config) (*gorm.DB, error) {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
zapLogger.Info("AutoMigrate models")
|
||||
// Автомиграция
|
||||
if err := autoMigrate(db); err != nil {
|
||||
zapLogger.Error("can't migrate models, error = %s", zap.Error(err))
|
||||
return nil, fmt.Errorf("can't migrate models, error = %s", err)
|
||||
zapLogger.Info("Configure connection pool")
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get underlying sql.DB: %w", err)
|
||||
}
|
||||
zapLogger.Info("Migrate complite successfully")
|
||||
sqlDB.SetMaxOpenConns(25)
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
sqlDB.SetConnMaxLifetime(30 * time.Minute)
|
||||
|
||||
zapLogger.Info("Fill init data")
|
||||
zapLogger.Info("Run database migrations")
|
||||
if err := runMigrations(sqlDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
zapLogger.Info("Migrations completed successfully")
|
||||
|
||||
zapLogger.Info("Successfully connected to database")
|
||||
zapLogger.Info("Successfully connected to database")
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func autoMigrate(db *gorm.DB) error {
|
||||
zapLogger := logger.Get()
|
||||
zapLogger.Debug("Start migration")
|
||||
models := []interface{}{
|
||||
&models.Account{},
|
||||
&models.UpdateHistory{},
|
||||
&models.Object{},
|
||||
&models.ObjectImage{},
|
||||
&models.Amenity{},
|
||||
&models.RatingVote{},
|
||||
&models.VoteBreakdown{},
|
||||
&models.Rating{},
|
||||
&models.Feedback{},
|
||||
&models.Comment{},
|
||||
&models.Appeal{},
|
||||
&models.AppealHistory{},
|
||||
&models.PasswordReset{},
|
||||
func runMigrations(sqlDB *sql.DB) error {
|
||||
zapLogger := logger.Get()
|
||||
|
||||
source, err := iofs.New(migrations.FS, ".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migration source: %w", err)
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
return fmt.Errorf("failed to migrate %T: %w", model, err)
|
||||
}
|
||||
driver, err := postgres.WithInstance(sqlDB, &postgres.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create postgres driver: %w", err)
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migrate instance: %w", err)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
zapLogger.Error("Migration error", zap.Error(err))
|
||||
return fmt.Errorf("failed to apply migrations: %w", err)
|
||||
}
|
||||
|
||||
zapLogger.Debug("End migration seccessfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
DROP TABLE IF EXISTS password_resets CASCADE;
|
||||
DROP TABLE IF EXISTS appeal_histories CASCADE;
|
||||
DROP TABLE IF EXISTS appeals CASCADE;
|
||||
DROP TABLE IF EXISTS comments CASCADE;
|
||||
DROP TABLE IF EXISTS feedbacks CASCADE;
|
||||
DROP TABLE IF EXISTS rating_votes CASCADE;
|
||||
DROP TABLE IF EXISTS vote_breakdowns CASCADE;
|
||||
DROP TABLE IF EXISTS ratings CASCADE;
|
||||
DROP TABLE IF EXISTS object_amenities CASCADE;
|
||||
DROP TABLE IF EXISTS amenities CASCADE;
|
||||
DROP TABLE IF EXISTS object_images CASCADE;
|
||||
DROP TABLE IF EXISTS objects CASCADE;
|
||||
DROP TABLE IF EXISTS update_histories CASCADE;
|
||||
DROP TABLE IF EXISTS accounts CASCADE;
|
||||
@@ -0,0 +1,227 @@
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255) NOT NULL DEFAULT 'Unknown',
|
||||
first_name VARCHAR(255) NOT NULL DEFAULT 'FirstName',
|
||||
last_name VARCHAR(255) NOT NULL DEFAULT 'LastName',
|
||||
phone VARCHAR(255),
|
||||
city VARCHAR(255),
|
||||
organization_form VARCHAR(255),
|
||||
organization_name VARCHAR(255),
|
||||
organization_short VARCHAR(255),
|
||||
inn VARCHAR(255),
|
||||
personal_inn VARCHAR(255),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
role VARCHAR(255) NOT NULL DEFAULT 'user'
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_deleted_at ON accounts(deleted_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS update_histories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
model_id BIGINT,
|
||||
model_type VARCHAR(255),
|
||||
timestamp TIMESTAMPTZ,
|
||||
updated_by BIGINT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_update_histories_updated_by ON update_histories(updated_by);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS objects (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
owner_id BIGINT,
|
||||
title VARCHAR(255) DEFAULT '',
|
||||
short_name VARCHAR(255) NOT NULL,
|
||||
long_name VARCHAR(255),
|
||||
type VARCHAR(255),
|
||||
price DECIMAL DEFAULT 0,
|
||||
price_period VARCHAR(255) DEFAULT 'per_unit',
|
||||
phone VARCHAR(255),
|
||||
email VARCHAR(255),
|
||||
site VARCHAR(255),
|
||||
short_description VARCHAR(255),
|
||||
description VARCHAR(255),
|
||||
address VARCHAR(255),
|
||||
latitude DECIMAL,
|
||||
longitude DECIMAL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
is_verified BOOLEAN DEFAULT FALSE,
|
||||
status VARCHAR(255) DEFAULT 'active',
|
||||
view_count BIGINT DEFAULT 0,
|
||||
feedback_count BIGINT DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_objects_deleted_at ON objects(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_objects_is_active ON objects(is_active);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_images (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
object_id BIGINT NOT NULL,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
sort_order BIGINT DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_object_images_deleted_at ON object_images(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_object_images_object_id ON object_images(object_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS amenities (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(255),
|
||||
icon VARCHAR(255),
|
||||
description VARCHAR(255)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_amenities_name ON amenities(name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_amenities (
|
||||
object_id BIGINT NOT NULL,
|
||||
amenity_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (object_id, amenity_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ratings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
owner_id BIGINT,
|
||||
object_id BIGINT,
|
||||
platform VARCHAR(255),
|
||||
average_score DECIMAL,
|
||||
total_votes BIGINT DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_deleted_at ON ratings(deleted_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vote_breakdowns (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
rating_id BIGINT,
|
||||
score_1 BIGINT DEFAULT 0,
|
||||
score_2 BIGINT DEFAULT 0,
|
||||
score_3 BIGINT DEFAULT 0,
|
||||
score_4 BIGINT DEFAULT 0,
|
||||
score_5 BIGINT DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_vote_breakdowns_deleted_at ON vote_breakdowns(deleted_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rating_votes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
platform VARCHAR(255),
|
||||
target_id BIGINT,
|
||||
voter_id BIGINT,
|
||||
score BIGINT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rating_votes_deleted_at ON rating_votes(deleted_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS feedbacks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
owner_id BIGINT NOT NULL,
|
||||
object_id BIGINT NOT NULL,
|
||||
platform VARCHAR(255),
|
||||
score BIGINT,
|
||||
comment_count BIGINT DEFAULT 0,
|
||||
text VARCHAR(255)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedbacks_deleted_at ON feedbacks(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedbacks_owner_id ON feedbacks(owner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedbacks_object_id ON feedbacks(object_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS comments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
author_id BIGINT NOT NULL,
|
||||
feedback_id BIGINT NOT NULL,
|
||||
text VARCHAR(255) NOT NULL,
|
||||
parent_id BIGINT,
|
||||
is_edited BOOLEAN DEFAULT FALSE,
|
||||
is_verified BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_deleted_at ON comments(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_author_id ON comments(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_feedback_id ON comments(feedback_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_parent_id ON comments(parent_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS appeals (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
author_id BIGINT,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(255) NOT NULL DEFAULT 'new',
|
||||
priority VARCHAR(255) NOT NULL DEFAULT 'medium',
|
||||
title VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
attachments TEXT[],
|
||||
contact_name VARCHAR(255),
|
||||
contact_email VARCHAR(255),
|
||||
contact_phone VARCHAR(255),
|
||||
object_id BIGINT,
|
||||
feedback_id BIGINT,
|
||||
comment_id BIGINT,
|
||||
ip_address VARCHAR(255),
|
||||
user_agent VARCHAR(255),
|
||||
assigned_to_id BIGINT,
|
||||
resolved_at TIMESTAMPTZ,
|
||||
resolved_by BIGINT,
|
||||
resolution VARCHAR(255),
|
||||
category VARCHAR(255),
|
||||
labels TEXT[],
|
||||
custom_data JSONB
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_deleted_at ON appeals(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_author_id ON appeals(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_type ON appeals(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_status ON appeals(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_priority ON appeals(priority);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_object_id ON appeals(object_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_feedback_id ON appeals(feedback_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_comment_id ON appeals(comment_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_assigned_to_id ON appeals(assigned_to_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS appeal_histories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
appeal_id BIGINT NOT NULL,
|
||||
user_id BIGINT,
|
||||
old_status VARCHAR(255),
|
||||
new_status VARCHAR(255),
|
||||
comment VARCHAR(255)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeal_histories_deleted_at ON appeal_histories(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeal_histories_appeal_id ON appeal_histories(appeal_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS password_resets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
account_id BIGINT NOT NULL,
|
||||
token VARCHAR(255) NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_password_resets_deleted_at ON password_resets(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_password_resets_account_id ON password_resets(account_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_password_resets_token ON password_resets(token);
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP TABLE IF EXISTS users CASCADE;
|
||||
DROP TABLE IF EXISTS reviews CASCADE;
|
||||
DROP TABLE IF EXISTS news CASCADE;
|
||||
DROP TABLE IF EXISTS authentications CASCADE;
|
||||
DROP TABLE IF EXISTS filters CASCADE;
|
||||
DROP TABLE IF EXISTS reports CASCADE;
|
||||
@@ -0,0 +1,6 @@
|
||||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.sql
|
||||
var FS embed.FS
|
||||
Reference in New Issue
Block a user