Heim  >  Artikel  >  Datenbank  >  Warum ist die DB-Migration für Golang-Dienste wichtig?

Warum ist die DB-Migration für Golang-Dienste wichtig?

Patricia Arquette
Patricia ArquetteOriginal
2024-10-22 21:06:02975Durchsuche

DB Migration For Golang Services, Why it matters?

DB-Migration, warum ist sie wichtig?

Sind Sie schon einmal mit der Situation konfrontiert, dass Sie ein neues Update in der Produktion mit aktualisierten Datenbankschemata bereitstellen, danach aber Fehler auftreten und Dinge rückgängig gemacht werden müssen? Dann kommt die Migration ins Spiel.

Die Datenbankmigration dient mehreren wichtigen Zwecken:

  1. Schemaentwicklung: Mit der Weiterentwicklung von Anwendungen ändern sich auch deren Datenmodelle. Durch Migrationen können Entwickler das Datenbankschema systematisch aktualisieren, um diese Änderungen widerzuspiegeln, und so sicherstellen, dass die Datenbankstruktur mit dem Anwendungscode übereinstimmt.
  2. Versionskontrolle: Migrationen bieten eine Möglichkeit, das Datenbankschema zu versionieren, sodass Teams Änderungen im Laufe der Zeit verfolgen können. Diese Versionierung hilft beim Verständnis der Entwicklung der Datenbank und unterstützt die Zusammenarbeit zwischen Entwicklern.
  3. Konsistenz über Umgebungen hinweg: Migrationen stellen sicher, dass das Datenbankschema über verschiedene Umgebungen hinweg (Entwicklung, Tests, Produktion) konsistent ist. Dadurch wird das Risiko von Unstimmigkeiten verringert, die zu Fehlern und Integrationsproblemen führen können.
  4. Rollback-Fähigkeit: Viele Migrationstools unterstützen das Rollback von Änderungen, sodass Entwickler zu einem früheren Zustand der Datenbank zurückkehren können, wenn eine Migration Probleme verursacht. Dies erhöht die Stabilität während des Entwicklungs- und Bereitstellungsprozesses.
  5. Automatisierte Bereitstellung: Migrationen können als Teil des Bereitstellungsprozesses automatisiert werden, um sicherzustellen, dass die erforderlichen Schemaänderungen ohne manuelle Eingriffe auf die Datenbank angewendet werden. Dies rationalisiert den Freigabeprozess und reduziert menschliche Fehler.

Bewerbung in Golang-Projekten

Um ein umfassendes, produktionstaugliches Setup für einen Golang-Dienst mithilfe von GORM mit MySQL zu erstellen, das einfache Migrationen, Aktualisierungen und Rollbacks ermöglicht, müssen Sie Migrationstools einbinden, das Datenbankverbindungspooling verwalten und die richtigen Strukturdefinitionen sicherstellen. Hier ist ein vollständiges Beispiel, das Sie durch den Prozess führt:

Projektstruktur

/golang-service
|-- main.go
|-- database
|   |-- migration.go
|-- models
|   |-- user.go
|-- config
|   |-- config.go
|-- migrations
|   |-- ...
|-- go.mod

1. Datenbankkonfiguration (config/config.go)

package config

import (
    "fmt"
    "log"
    "os"
    "time"

    "github.com/joho/godotenv"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var DB *gorm.DB

func ConnectDB() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }

    // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis.
    // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type.
    // loc=Local: Uses the local timezone of the server for time-related queries and storage.
    dsn := fmt.Sprintf(
        "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASS"),
        os.Getenv("DB_HOST"),
        os.Getenv("DB_PORT"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    sqlDB, err := db.DB()
    if err != nil {
        panic("failed to configure database connection")
    }

    // Set connection pool settings
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)

    // 1.sqlDB.SetMaxIdleConns(10)
    // Sets the maximum number of idle (unused but open) connections in the connection pool.
    // A value of 10 means up to 10 connections can remain idle, ready to be reused.

    // 2. sqlDB.SetMaxOpenConns(100):
    // Sets the maximum number of open (active or idle) connections that can be created to the database.
    // A value of 100 limits the total number of connections, helping to prevent overloading the database.

    // 3. sqlDB.SetConnMaxLifetime(time.Hour):
    // Sets the maximum amount of time a connection can be reused before it’s closed.
    // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed.

    DB = db
}

2. Datenbankmigration (database/migration.go)

package database

import (
    "golang-service/models"
    "golang-service/migrations"
    "gorm.io/gorm"
)

func Migrate(db *gorm.DB) {
    db.AutoMigrate(&models.User{})
    // Apply additional custom migrations if needed
}

3. Modelle (models/user.go)

package models

import "gorm.io/gorm"

type User struct {
    gorm.Model
    Name  string `json:"name"`
}

4. Umgebungskonfiguration (.env)

DB_USER=root
DB_PASS=yourpassword
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=yourdb

5. Haupteinstiegspunkt (main.go)

package main

import (
    "golang-service/config"
    "golang-service/database"
    "golang-service/models"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

func main() {
    config.ConnectDB()
    database.Migrate(config.DB)

    r := gin.Default()
    r.POST("/users", createUser)
    r.GET("/users/:id", getUser)
    r.Run(":8080")
}

func createUser(c *gin.Context) {
    var user models.User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := config.DB.Create(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }

    c.JSON(201, user)
}

func getUser(c *gin.Context) {
    id := c.Param("id")
    var user models.User

    if err := config.DB.First(&user, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    c.JSON(200, user)
}

6. Erläuterung:

  • Datenbankkonfiguration: Verwaltet das Verbindungspooling für eine Leistung auf Produktionsniveau.
  • Migrationsdateien: (im Migrationsordner) Hilft bei der Versionierung des Datenbankschemas.
  • GORM-Modelle: Ordnet Datenbanktabellen Go-Strukturen zu.
  • Datenbankmigrationen: (im Datenbankordner) Benutzerdefinierte Logik zum Ändern von Tabellen im Laufe der Zeit, was einfache Rollbacks ermöglicht.
  • Testen: Sie können Integrationstests für dieses Setup mit httptest und testify erstellen.

7. Erstellen Sie die erste Migration

  1. Für Produktionsumgebungen könnten wir eine Migrationsbibliothek wie golang-migrate verwenden, um Migrationen anzuwenden, rückgängig zu machen oder zu wiederholen.

    Installieren Sie golang-migrate:

    go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
    
  2. Migrationsdateien für Benutzertabelle generieren

    migrate create -ext=sql -dir=./migrations -seq create_users_table
    

    Nachdem wir den Befehl ausgeführt haben, erhalten wir ein Paar .up.sql (zur Aktualisierung des Schemas) und down.sql (für ein mögliches späteres Rollback). Die Zahl 000001 ist der automatisch generierte Migrationsindex.

    /golang-service
    |-- migrations
    |   |-- 000001_create_users_table.down.sql
    |   |-- 000001_create_users_table.up.sql
    

    Fügen Sie den relevanten SQL-Befehl zur .up-Datei und .down-Datei hinzu.

    000001_create_users_table.up.sql

    CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at DATETIME,
    updated_at DATETIME,
    deleted_at DATETIME);
    

    000001_create_users_table.down.sql

    DROP TABLE IF EXISTS users;
    

    Führen Sie die Hochmigration aus und übernehmen Sie Änderungen an der Datenbank mit dem folgenden Befehl (Flag -verbose, um weitere Protokolldetails anzuzeigen):

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" -verbose up
    

    Falls wir Probleme mit der Migration haben, können wir den folgenden Befehl verwenden, um die aktuelle Migrationsversion und ihren Status anzuzeigen:

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" version
    

    Wenn wir aus irgendeinem Grund eine fehlerhafte Migration haben, können wir in Betracht ziehen, den Force-Befehl (vorsichtig verwenden) mit der Versionsnummer der schmutzigen Migration zu verwenden. Wenn die Version 1 ist (könnte in der Tabelle migrations oder schema_migrations überprüft werden), würden wir Folgendes ausführen:

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" force 1
    

8. Schemata ändern

  1. Irgendwann möchten wir möglicherweise neue Funktionen hinzufügen, und einige davon erfordern möglicherweise eine Änderung der Datenschemata. Beispielsweise möchten wir der Benutzertabelle ein E-Mail-Feld hinzufügen. Wir würden es wie folgt machen.

    Führen Sie eine neue Migration durch, um die E-Mail-Spalte zur Benutzertabelle hinzuzufügen

    migrate create -ext=sql -dir=./migrations -seq add_email_to_users
    

    Jetzt haben wir ein neues Paar von .up.sql und .down.sql

    /golang-service
    |-- migrations
    |   |-- 000001_create_users_table.down.sql
    |   |-- 000001_create_users_table.up.sql
    |   |-- 000002_add_email_to_users.down.sql
    |   |-- 000002_add_email_to_users.up.sql
    
  2. Folgenden Inhalt zu *_add_email_to_users.*.sql-Dateien

    hinzufügen

    000002_add_email_to_users.up.sql

    /golang-service
    |-- main.go
    |-- database
    |   |-- migration.go
    |-- models
    |   |-- user.go
    |-- config
    |   |-- config.go
    |-- migrations
    |   |-- ...
    |-- go.mod
    
    

    000002_add_email_to_users.down.sql

    package config
    
    import (
        "fmt"
        "log"
        "os"
        "time"
    
        "github.com/joho/godotenv"
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
    )
    
    var DB *gorm.DB
    
    func ConnectDB() {
        err := godotenv.Load()
        if err != nil {
            log.Fatal("Error loading .env file")
        }
    
        // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis.
        // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type.
        // loc=Local: Uses the local timezone of the server for time-related queries and storage.
        dsn := fmt.Sprintf(
            "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
            os.Getenv("DB_USER"),
            os.Getenv("DB_PASS"),
            os.Getenv("DB_HOST"),
            os.Getenv("DB_PORT"),
            os.Getenv("DB_NAME"),
        )
    
        db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
    
        sqlDB, err := db.DB()
        if err != nil {
            panic("failed to configure database connection")
        }
    
        // Set connection pool settings
        sqlDB.SetMaxIdleConns(10)
        sqlDB.SetMaxOpenConns(100)
        sqlDB.SetConnMaxLifetime(time.Hour)
    
        // 1.sqlDB.SetMaxIdleConns(10)
        // Sets the maximum number of idle (unused but open) connections in the connection pool.
        // A value of 10 means up to 10 connections can remain idle, ready to be reused.
    
        // 2. sqlDB.SetMaxOpenConns(100):
        // Sets the maximum number of open (active or idle) connections that can be created to the database.
        // A value of 100 limits the total number of connections, helping to prevent overloading the database.
    
        // 3. sqlDB.SetConnMaxLifetime(time.Hour):
        // Sets the maximum amount of time a connection can be reused before it’s closed.
        // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed.
    
        DB = db
    }
    
    

    Führen Sie den Befehl „up migration“ erneut aus, um die Datenschemata zu aktualisieren

    package database
    
    import (
        "golang-service/models"
        "golang-service/migrations"
        "gorm.io/gorm"
    )
    
    func Migrate(db *gorm.DB) {
        db.AutoMigrate(&models.User{})
        // Apply additional custom migrations if needed
    }
    
    

    Wir müssen auch die Golang-Benutzerstruktur aktualisieren (das Feld E-Mail hinzufügen), um sie mit den neuen Schemata synchron zu halten.

    package models
    
    import "gorm.io/gorm"
    
    type User struct {
        gorm.Model
        Name  string `json:"name"`
    }
    
    

9. Rollback von Migrationen:

Falls wir aus irgendeinem Grund Fehler mit neuen aktualisierten Schemata haben und ein Rollback durchführen müssen, verwenden wir in diesem Fall den Down-Befehl:

DB_USER=root
DB_PASS=yourpassword
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=yourdb

Nummer 1 gibt an, dass wir eine Migration rückgängig machen möchten.

Hier müssen wir auch die Golang-Benutzerstruktur manuell aktualisieren (entfernen Sie das Feld E-Mail), um die Änderungen am Datenschema widerzuspiegeln.

package main

import (
    "golang-service/config"
    "golang-service/database"
    "golang-service/models"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

func main() {
    config.ConnectDB()
    database.Migrate(config.DB)

    r := gin.Default()
    r.POST("/users", createUser)
    r.GET("/users/:id", getUser)
    r.Run(":8080")
}

func createUser(c *gin.Context) {
    var user models.User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := config.DB.Create(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }

    c.JSON(201, user)
}

func getUser(c *gin.Context) {
    id := c.Param("id")
    var user models.User

    if err := config.DB.First(&user, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    c.JSON(200, user)
}

10. Verwendung mit Makefile

Um den Prozess der Migration und des Rollbacks zu vereinfachen, können wir ein Makefile hinzufügen.

go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

Der Inhalt von Makefile wie folgt.

migrate create -ext=sql -dir=./migrations -seq create_users_table

Jetzt können wir einfach make migrate_up oder make migrate_down auf der CLI ausführen, um die Migration und das Rollback durchzuführen.

11. Überlegungen:

  • Datenverlust während des Rollbacks: Rollback-Migrationen, bei denen Spalten oder Tabellen gelöscht werden, können zu Datenverlust führen. Sichern daher immer die Daten, bevor ein Rollback ausgeführt wird.
  • CI/CD-Integration: Integrieren Sie den Migrationsprozess in Ihre CI/CD-Pipeline, um Schemaänderungen während der Bereitstellung zu automatisieren.
  • DB-Backups: Planen Sie regelmäßige Datenbank-Backups, um Datenverlust im Falle von Migrationsfehlern zu verhindern.

Über DB-Backups

Bevor Sie eine Migration rückgängig machen oder Änderungen vornehmen, die sich möglicherweise auf Ihre Datenbank auswirken könnten, sollten Sie die folgenden wichtigen Punkte berücksichtigen.

  1. Schemaänderungen: Wenn die Migration eine Änderung des Schemas beinhaltete (z. B. Hinzufügen oder Entfernen von Spalten, Ändern von Datentypen), kann ein Rollback auf eine frühere Migration zum Verlust aller in diesen geänderten Spalten oder Tabellen gespeicherten Daten führen .
  2. Datenentfernung: Wenn die Migration Befehle umfasst, die Daten löschen (z. B. das Löschen von Tabellen oder das Abschneiden von Tabellen), führt ein Rollback die entsprechende „Down“-Migration aus, wodurch diese Daten möglicherweise dauerhaft entfernt werden.
  3. Transaktionsabwicklung: Wenn Ihr Migrationstool Transaktionen unterstützt, ist das Rollback möglicherweise sicherer, da Änderungen in einer Transaktion angewendet werden. Wenn Sie jedoch SQL-Befehle außerhalb von Transaktionen manuell ausführen, besteht die Gefahr eines Datenverlusts.
  4. Datenintegrität: Wenn Sie Daten in einer Weise geändert haben, die vom aktuellen Schema abhängt, kann ein Rollback dazu führen, dass Ihre Datenbank in einem inkonsistenten Zustand bleibt.

Daher ist es wichtig, Ihre Daten zu sichern. Hier ist eine kurze Anleitung:

  1. Datenbank-Dump:
    Verwenden Sie datenbankspezifische Tools, um eine vollständige Sicherung Ihrer Datenbank zu erstellen. Für MySQL können Sie Folgendes verwenden:

    /golang-service
    |-- main.go
    |-- database
    |   |-- migration.go
    |-- models
    |   |-- user.go
    |-- config
    |   |-- config.go
    |-- migrations
    |   |-- ...
    |-- go.mod
    
    

    Dadurch wird eine Datei (backup_before_rollback.sql) erstellt, die alle Daten und das Schema der dbname-Datenbank enthält.

  2. Spezifische Tabellen exportieren:
    Wenn Sie nur bestimmte Tabellen sichern müssen, geben Sie diese im mysqldump-Befehl an:

    package config
    
    import (
        "fmt"
        "log"
        "os"
        "time"
    
        "github.com/joho/godotenv"
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
    )
    
    var DB *gorm.DB
    
    func ConnectDB() {
        err := godotenv.Load()
        if err != nil {
            log.Fatal("Error loading .env file")
        }
    
        // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis.
        // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type.
        // loc=Local: Uses the local timezone of the server for time-related queries and storage.
        dsn := fmt.Sprintf(
            "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
            os.Getenv("DB_USER"),
            os.Getenv("DB_PASS"),
            os.Getenv("DB_HOST"),
            os.Getenv("DB_PORT"),
            os.Getenv("DB_NAME"),
        )
    
        db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
    
        sqlDB, err := db.DB()
        if err != nil {
            panic("failed to configure database connection")
        }
    
        // Set connection pool settings
        sqlDB.SetMaxIdleConns(10)
        sqlDB.SetMaxOpenConns(100)
        sqlDB.SetConnMaxLifetime(time.Hour)
    
        // 1.sqlDB.SetMaxIdleConns(10)
        // Sets the maximum number of idle (unused but open) connections in the connection pool.
        // A value of 10 means up to 10 connections can remain idle, ready to be reused.
    
        // 2. sqlDB.SetMaxOpenConns(100):
        // Sets the maximum number of open (active or idle) connections that can be created to the database.
        // A value of 100 limits the total number of connections, helping to prevent overloading the database.
    
        // 3. sqlDB.SetConnMaxLifetime(time.Hour):
        // Sets the maximum amount of time a connection can be reused before it’s closed.
        // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed.
    
        DB = db
    }
    
    
  3. Überprüfen Sie die Sicherung:
    Stellen Sie sicher, dass die Sicherungsdatei erstellt wurde und überprüfen Sie ihre Größe oder öffnen Sie sie, um sicherzustellen, dass sie die erforderlichen Daten enthält.

  4. Backups sicher speichern:
    Bewahren Sie eine Kopie des Backups an einem sicheren Ort auf, z. B. in einem Cloud-Speicher oder auf einem separaten Server, um Datenverlust während des Rollback-Vorgangs zu verhindern.

Backups in der Cloud

Um Ihre MySQL-Daten zu sichern, wenn Sie Golang verwenden und auf AWS EKS bereitstellen, können Sie die folgenden Schritte ausführen:

  1. Verwenden Sie mysqldump für die Datenbanksicherung:
    Erstellen Sie mit einem Kubernetes-Cron-Job einen MySQL-Dump Ihrer MySQL-Datenbank.

    package database
    
    import (
        "golang-service/models"
        "golang-service/migrations"
        "gorm.io/gorm"
    )
    
    func Migrate(db *gorm.DB) {
        db.AutoMigrate(&models.User{})
        // Apply additional custom migrations if needed
    }
    
    

    Speichern Sie dies in einem persistenten Volume oder einem S3-Bucket.

  2. Automatisieren Sie mit Kubernetes CronJob:
    Verwenden Sie einen Kubernetes CronJob, um den mysqldump-Prozess zu automatisieren.
    Beispiel einer YAML-Konfiguration:yaml

    package models
    
    import "gorm.io/gorm"
    
    type User struct {
        gorm.Model
        Name  string `json:"name"`
    }
    
    


    `

  3. Verwenden von AWS RDS Automated Backups (bei Verwendung von RDS):
    Wenn sich Ihre MySQL-Datenbank auf AWS RDS befindet, können Sie RDS automatisierte Backups und Snapshots nutzen.
    Legen Sie einen Backup-Aufbewahrungszeitraum fest und erstellen Sie Snapshots manuell oder automatisieren Sie Snapshots mithilfe von Lambda-Funktionen.

  4. Persistente Volumes (PV) mit Velero sichern:
    Verwenden Sie Velero, ein Backup-Tool für Kubernetes, um das persistente Volume zu sichern, das MySQL-Daten enthält.
    Installieren Sie Velero auf Ihrem EKS-Cluster und konfigurieren Sie es für die Sicherung auf S3.

Mit diesen Methoden können Sie sicherstellen, dass Ihre MySQL-Daten regelmäßig gesichert und sicher gespeichert werden.

Das obige ist der detaillierte Inhalt vonWarum ist die DB-Migration für Golang-Dienste wichtig?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn