ホームページ  >  記事  >  データベース  >  Golang サービスの DB 移行、なぜそれが重要なのか?

Golang サービスの DB 移行、なぜそれが重要なのか?

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-22 21:06:02981ブラウズ

DB Migration For Golang Services, Why it matters?

DB の移行、なぜそれが重要なのでしょうか?

更新されたデータベース スキーマを使用して新しい更新を実稼働環境にデプロイしたが、その後バグが発生し、元に戻す必要があるという状況に直面したことはありませんか。移行が必要になるのはそのときです。

データベースの移行は、いくつかの重要な目的を果たします。

  1. スキーマの進化: アプリケーションが進化するにつれて、そのデータ モデルも変化します。移行により、開発者はデータベース スキーマを体系的に更新してこれらの変更を反映し、データベース構造がアプリケーション コードと確実に一致するようにすることができます。
  2. バージョン管理: 移行により、データベース スキーマのバージョンを管理する方法が提供され、チームが経時的に変更を追跡できるようになります。このバージョン管理は、データベースの進化を理解するのに役立ち、開発者間のコラボレーションに役立ちます。
  3. 環境全体の一貫性: 移行により、データベース スキーマが異なる環境 (開発、テスト、運用) 間で一貫していることが保証されます。これにより、バグや統合の問題につながる可能性のある不一致のリスクが軽減されます。
  4. ロールバック機能: 多くの移行ツールは変更のロールバックをサポートしており、移行によって問題が発生した場合に開発者がデータベースを以前の状態に戻すことができます。これにより、開発および展開プロセス中の安定性が向上します。
  5. 自動展開: 導入プロセスの一部として移行を自動化でき、手動介入なしで必要なスキーマ変更がデータベースに確実に適用されます。これにより、リリース プロセスが合理化され、人的エラーが削減されます。

golang プロジェクトに応募する

MySQL で GORM を使用し、簡単な移行、更新、ロールバックを可能にする、Golang サービスの包括的な運用グレードのセットアップを作成するには、移行ツールを組み込み、データベース接続プーリングを処理し、適切な構造体定義を確保する必要があります。プロセスをガイドする完全な例を次に示します:

プロジェクトの構造

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

1. データベース設定 (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. データベースの移行 (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. モデル (models/user.go)

package models

import "gorm.io/gorm"

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

4. 環境設定(.env)

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

5. メインエントリーポイント (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. 説明:

  • データベース構成: 運用グレードのパフォーマンスを実現するために接続プーリングを管理します。
  • 移行ファイル: (移行フォルダー内) データベース スキーマのバージョン管理に役立ちます。
  • GORM モデル: データベース テーブルを Go 構造体にマップします。
  • データベース移行: (データベース フォルダー内) 時間の経過とともにテーブルを変更するためのカスタム ロジック。簡単なロールバックが可能です。
  • テスト: httptest と testify を使用して、このセットアップの統合テストを作成できます。

7. 最初の移行の作成

  1. 実稼働環境の場合、golang-Migrate のような移行ライブラリを使用して、移行を適用、ロールバック、またはやり直すことができます。

    golang-移行をインストールします:

    go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
    
  2. ユーザーテーブルの移行ファイルを生成

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

    コマンドを実行すると、.up.sql (スキーマを更新するため) と down.sql (後でロールバックする可能性があるため) のペアが取得されます。番号 000001 は、自動生成された移行のインデックスです。

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

    関連する sql コマンド を .up ファイルと .down ファイルに追加します。

    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;
    

    次のコマンド (ログの詳細を表示するには -verbose フラグ) を使用して、アップ移行を実行し、データベースに変更を適用します。

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

    移行で問題が発生した場合は、次のコマンドを使用して現在の移行バージョンとそのステータスを確認できます。

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

    何らかの理由で移行が失敗した場合は、ダーティ マイグレーションのバージョン番号を指定して、force (慎重に使用してください) コマンドを使用することを検討できます。バージョンが 1 の場合 (migrations または schema_migrations テーブルで確認できます)、次を実行します:

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

8. スキームの変更

  1. ある時点で、新しい機能を追加したい場合があり、その中にはデータ スキームの変更が必要になる場合もあります。たとえば、users テーブルに email フィールドを追加したいと考えています。次のようにします。

    メール列を users テーブルに追加するための新しい移行を作成します

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

    これで、.up.sql と .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. 次のコンテンツを *_add_email_to_users.*.sql ファイルに追加します

    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
    }
    
    

    up Migration コマンドを再度実行して、データ スキーマを更新します

    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
    }
    
    

    新しいスキーマとの同期を保つために、golang ユーザーの構造体を更新する (Email フィールドを追加する) 必要もあります。

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

9. 移行のロールバック:

何らかの理由で新しく更新されたスキーマにバグが発生し、ロールバックする必要がある場合は、down コマンドを使用します。

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

番号 1 は、1 つの移行をロールバックすることを示します。

ここでは、データ スキーマの変更を反映するために、golang ユーザーの構造体を手動で更新する (Email フィールドを削除する) 必要もあります。

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. Makefile で使用する

移行とロールバックのプロセスを簡素化するために、Makefile を追加できます。

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

Makefileの内容は以下の通りです。

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

これで、CLI で make mitigrate_up または make mitigrate_down を実行するだけで、移行とロールバックを行うことができます。

11.考慮事項:

  • ロールバック中のデータ損失: 列またはテーブルを削除する移行をロールバックすると、データ損失が発生する可能性があるため、ロールバックを実行する前に必ずデータをバックアップしてください。
  • CI/CD 統合: 移行プロセスを CI/CD パイプラインに統合して、デプロイメント中のスキーマ変更を自動化します。
  • DB バックアップ: 移行エラーによるデータ損失を防ぐために、定期的なデータベース バックアップをスケジュールします。

DBバックアップについて

移行をロールバックしたり、データベースに影響を与える可能性のある変更を加えたりする前に、考慮すべき重要な点がいくつかあります。

  1. スキーマの変更: 移行にスキーマの変更 (列の追加または削除、データ型の変更など) が含まれている場合、以前の移行にロールバックすると、変更された列またはテーブルに保存されているデータが失われる可能性があります。 .
  2. データ削除: 移行にデータを削除するコマンド (テーブルの削除やテーブルの切り捨てなど) が含まれている場合、ロールバックすると対応する「ダウン」移行が実行され、そのデータが永久に削除される可能性があります。
  3. トランザクション処理: 移行ツールがトランザクションをサポートしている場合、変更はトランザクションで適用されるため、ロールバックがより安全になる可能性があります。ただし、トランザクション外で SQL コマンドを手動で実行すると、データが失われるリスクがあります。
  4. データの整合性: 現在のスキーマに依存する方法でデータを変更した場合、ロールバックするとデータベースが不整合な状態になる可能性があります。

データをバックアップすることが重要です。簡単なガイドは次のとおりです:

  1. データベースダンプ:
    データベース固有のツールを使用して、データベースの完全バックアップを作成します。 MySQL の場合、以下を使用できます:

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

    これにより、dbname データベースのすべてのデータとスキーマを含むファイル (backup_before_rollback.sql) が作成されます。

  2. 特定のテーブルをエクスポート:
    特定のテーブルのみをバックアップする必要がある場合は、mysqldump コマンドで指定します。

    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. バックアップを確認します:
    バックアップ ファイルが作成されていることを確認し、そのサイズを確認するか、バックアップ ファイルを開いて必要なデータが含まれていることを確認してください。

  4. バックアップを安全に保存します:
    ロールバック プロセス中のデータ損失を防ぐために、バックアップのコピーをクラウド ストレージや別のサーバーなどの安全な場所に保管してください。

クラウド上のバックアップ

Golang を使用し、AWS EKS にデプロイするときに MySQL データをバックアップするには、次の手順に従います。

  1. データベースのバックアップに mysqldump を使用する:
    Kubernetes cron ジョブを使用して、MySQL データベースの mysqldump を作成します。

    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
    }
    
    

    これを永続ボリュームまたは S3 バケットに保存します。

  2. Kubernetes CronJob で自動化する:
    Kubernetes CronJob を使用して、mysqldump プロセスを自動化します。
    YAML 設定の例:yaml

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


    `

  3. AWS RDS 自動バックアップの使用 (RDS を使用している場合):
    MySQL データベースが AWS RDS 上にある場合は、RDS の自動バックアップとスナップショットを利用できます。
    バックアップの保持期間を設定し、スナップショットを手動で取得するか、Lambda 関数を使用してスナップショットを自動化します。

  4. Velero を使用した永続ボリューム (PV) のバックアップ:
    Kubernetes のバックアップ ツールである Velero を使用して、MySQL データを保持する永続ボリュームをバックアップします。
    EKS クラスターに Velero をインストールし、S3 にバックアップするように構成します。

これらの方法を使用すると、MySQL データが定期的にバックアップされ、安全に保存されます。

以上がGolang サービスの DB 移行、なぜそれが重要なのか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。