」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 資料庫遷移對於 Golang 服務,為什麼重要?

資料庫遷移對於 Golang 服務,為什麼重要?

發佈於2024-11-17
瀏覽:545

DB Migration For Golang Services, Why it matters?

DB Migration, why it matters?

Have you ever faced the situations when you deploy new update on production with updated database schemas, but got bugs after that and need to revert things.... that's when migration comes into place.

Database migration serves several key purposes:

  1. Schema Evolution: As applications evolve, their data models change. Migrations allow developers to systematically update the database schema to reflect these changes, ensuring that the database structure matches the application code.
  2. Version Control: Migrations provide a way to version the database schema, allowing teams to track changes over time. This versioning helps in understanding the evolution of the database and aids in collaboration among developers.
  3. Consistency Across Environments: Migrations ensure that the database schema is consistent across different environments (development, testing, production). This reduces the risk of discrepancies that can lead to bugs and integration issues.
  4. Rollback Capability: Many migration tools support rolling back changes, allowing developers to revert to a previous state of the database if a migration causes issues. This enhances stability during the development and deployment process.
  5. Automated Deployment: Migrations can be automated as part of the deployment process, ensuring that the necessary schema changes are applied to the database without manual intervention. This streamlines the release process and reduces human error.

Applying in golang projects

To create a comprehensive, production-grade setup for a Golang service using GORM with MySQL that allows for easy migrations, updates, and rollbacks, you need to include migration tooling, handle database connection pooling, and ensure proper struct definitions. Here’s a complete example to guide you through the process:

Project Structure

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

1. Database Configuration (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 (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 (models/user.go)

package models

import "gorm.io/gorm"

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

4. Environment Configuration (.env)

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

5. Main Entry Point (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. Explanation:

  • Database Config: Manages connection pooling for production-grade performance.
  • Migration Files: (in migrations folder) Helps in versioning the database schema.
  • GORM Models: Maps database tables to Go structs.
  • Database Migrations: (in database folder) Custom logic for altering tables over time, allowing for easy rollbacks.
  • Testing: You can create integration tests for this setup using httptest and testify.

7. Create First Migration

  1. For production environments, we could use a migration library like golang-migrate to apply, rollback, or redo migrations.

    Install golang-migrate:

    go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
    
  2. Generate migrate files for users table

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

    After running the command we'll get a pair of .up.sql (to update schema) and down.sql (for potential rollback later) . The number 000001 is the auto generated index of migration.

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

    Add relevant sql command to .up file , and .down file.

    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;
    

    Run the up migration and apply changes to the database with the following command (-verbose flag to see more log details):

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

    In case we got issue with migration we can use the following command to see the current migration version and its status:

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

    If we have a broken migration for some reasons we can consider to use the force (use carefully) command with the version number of the dirty migration. If the version is 1 (could check it in migrations or schema_migrations table), we would run:

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

8. Changing schemes

  1. At some point in time, we might like to add new features and some of those might require chaning data schemes, for instance we'd like to add email field to users table. We'd do it as following.

    Make a new migration for adding email column to users table

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

    Now we have a new pair of .up.sql and .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. Adding following content to *_add_email_to_users.*.sql files

    000002_add_email_to_users.up.sql

    ALTER TABLE `users` ADD COLUMN `email` VARCHAR(255) UNIQUE;
    

    000002_add_email_to_users.down.sql

    ALTER TABLE `users` DROP COLUMN `email`;
    

    Run the up migration command again to make update to the data schemas

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

    We'll also need to update the golang users struct (adding Email field) to keep it in sync with the new schemas..

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

9. Rolling Back Migrations:

In case for some reasons we got bugs with new updated schemas, and we need to rollback, this case we'll use the down command:

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

Number 1 indicates that we want to rollback 1 migration.

Here we also need manually to update golang users struct (remove the Email field) to reflect the data schema changes.

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

10. Use with Makefile

To simplify the process of migration and rolling back, we can add a Makefile .

/golang-service
|-- ...
|-- Makefile

The content of Makefile as following.

include .env

create_migration:
    migrate create -ext=sql -dir=./migrations -seq create_new_migration

migrate_up:
    migrate -path=./migrations -database "mysql://${DB_USER}:${DB_PASS}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME}" -verbose up 1

migrate_down:
    migrate -path=./migrations -database "mysql://${DB_USER}:${DB_PASS}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME}" -verbose down 1

.PHONY:  create_migration migrate_up migrate_down

Now we can simply run make migrate_up or make migrate_down on CLI to do the migration and the rollback.

11.Considerations:

  • Data Loss During Rollback: Rolling back migrations that delete columns or tables may result in data loss, so always backup data before running a rollback.
  • CI/CD Integration: Integrate the migration process into your CI/CD pipeline to automate schema changes during deployment.
  • DB Backups: Schedule regular database backups to prevent data loss in case of migration errors.

About DB backups

Before rolling back a migration or making changes that could potentially affect your database, here are some key points to consider.

  1. Schema Changes: If the migration involved altering the schema (e.g., adding or removing columns, changing data types), rolling back to a previous migration can result in the loss of any data stored in those altered columns or tables.
  2. Data Removal: If the migration includes commands that delete data (like dropping tables or truncating tables), rolling back will execute the corresponding "down" migration, which could permanently remove that data.
  3. Transaction Handling: If your migration tool supports transactions, the rollback might be safer since changes are applied in a transaction. However, if you manually run SQL commands outside of transactions, there is a risk of losing data.
  4. Data Integrity: If you have modified data in a way that depends on the current schema, rolling back could leave your database in an inconsistent state.

So it’s crucial to back up your data. Here’s a brief guide:

  1. Database Dump:
    Use database-specific tools to create a full backup of your database. For MySQL, you can use:

     mysqldump -u root -p dbname > backup_before_rollback.sql
    

    This creates a file (backup_before_rollback.sql) that contains all the data and schema of the dbname database.

  2. Export Specific Tables:
    If you only need to back up certain tables, specify them in the mysqldump command:

    mysqldump -u root -p golang_1 users > users_table_backup.sql
    
  3. Verify the Backup:
    Ensure that the backup file has been created and check its size or open it to ensure it contains the necessary data.

  4. Store Backups Securely:
    Keep a copy of the backup in a secure location, such as cloud storage or a separate server, to prevent data loss during the rollback process.

Backups on cloud

To back up your MySQL data when using Golang and deploying on AWS EKS, you can follow these steps:

  1. Use mysqldump for Database Backup:
    Create a mysqldump of your MySQL database using a Kubernetes cron job.

    mysqldump -h  -u  -p > backup.sql
    

    Store this in a persistent volume or an S3 bucket.

  2. Automate with Kubernetes CronJob:
    Use a Kubernetes CronJob to automate the mysqldump process.
    Example YAML configuration:yaml

    apiVersion: batch/v1
    kind: CronJob
    metadata:
    name: mysql-backup
    spec:
       schedule: "0 2 * * *" # Runs every day at 2 AM
       jobTemplate:
         spec:
           template:
             spec:
               containers:
                 - name: mysql-backup
                   image: mysql:5.7
                   args:
                     - /bin/sh
                     - -c
                     - "mysqldump -h  -u  -p | aws s3 cp - s3:///backup-$(date  \\%F).sql"
                   env:
                     - name: AWS_ACCESS_KEY_ID
                       value: ""
                     - name: AWS_SECRET_ACCESS_KEY
                       value: ""
               restartPolicy: OnFailure
    


    `

  3. Using AWS RDS Automated Backups (if using RDS):
    If your MySQL database is on AWS RDS, you can leverage RDS automated backups and snapshots.
    Set a backup retention period and take snapshots manually or automate snapshots using Lambda functions.

  4. Back Up Persistent Volumes (PV) with Velero:
    Use Velero, a backup tool for Kubernetes, to back up the persistent volume that holds MySQL data.
    Install Velero on your EKS cluster and configure it to back up to S3.

By using these methods, you can ensure your MySQL data is regularly backed up and securely stored.

版本聲明 本文轉載於:https://dev.to/truongpx396/db-migration-for-golang-services-why-it-matters-17a?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何在 CSS 類別名稱中使用轉義百分號來建立動態佈局元素?
    如何在 CSS 類別名稱中使用轉義百分號來建立動態佈局元素?
    CSS中.container.\31 25\25是什麼意思? 反斜線字元()用於轉義特殊字元CSS,例如百分號 (%)$。這允許使用原本無效的標識符,例如包含某些標點符號的標識符。 在提供的範例中,反斜線用於轉義類別名稱 .container 中的百分號。 \ 31 25\25。這導致類別名稱等同於...
    程式設計 發佈於2024-11-17
  • 如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    在 PHP 中組合關聯數組在 PHP 中,將兩個關聯數組組合成一個數組是常見任務。考慮以下請求:問題描述:提供的代碼定義了兩個關聯數組,$array1 和 $array2。目標是建立一個新陣列 $array3,它合併兩個陣列中的所有鍵值對。 此外,提供的陣列具有唯一的 ID,而名稱可能重疊。要求是建...
    程式設計 發佈於2024-11-17
  • 如何使用標記有效地拆分 C++ 字串?
    如何使用標記有效地拆分 C++ 字串?
    使用標記有效拆分C 字串要根據指定標記將C std::string 拆分為子字串,有多種方法可供選擇可考慮。最有效的解決方案取決於您的應用程式的特定要求。 在您的情況下,字串由 ; 分隔。字符,並且 C 字串函數和 Boost 的使用受到限制,您可以使用 std::getline() 函數。此函數允...
    程式設計 發佈於2024-11-17
  • Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta:列偏移的刪除和恢復Bootstrap 4 在其Beta 1 版本中引入了重大更改柱子偏移了。然而,隨著 Beta 2 的後續發布,這些變化已經逆轉。 從 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    程式設計 發佈於2024-11-17
  • 為什麼 Go 中 rune 是 int32 的別名而不是 uint32?
    為什麼 Go 中 rune 是 int32 的別名而不是 uint32?
    為什麼 rune 是 Go 中 int32 的別名,而不是 uint32? 儘管 rune 類型的主要目的是表示字元值,但 rune 類型Go 中沒有定義為 uint32 的別名。相反,它是 int32 的別名。鑑於字元通常由正值表示,此選擇似乎違反直覺。 此決定背後的基本原理源於符文作為 Unic...
    程式設計 發佈於2024-11-17
  • 在 Spans 中使用「float: right」時如何保留 HTML 順序?
    在 Spans 中使用「float: right」時如何保留 HTML 順序?
    使用Float:right 反轉Span 順序使用Float:right 反轉Span 順序在提供的HTML 中,具有「button」類別的Span 的樣式為「float : right” ,”導致它們以與HTML 結構相反的順序顯示。順序?不要直接浮動span 元素,而是將它們包裝在包含元素中並將...
    程式設計 發佈於2024-11-17
  • 如何將 SDL2 和 SDL_image 與 CMake 一起用於 C++ 專案?
    如何將 SDL2 和 SDL_image 與 CMake 一起用於 C++ 專案?
    在CMake中使用SDL2和SDL_image在這篇文章中,我們深入研究了在CMake中使用SDL2圖形庫和SDL_image擴展的步驟您的C 專案在CMake 的幫助下。 配置專案並依賴項project(shooter-cmake2) cmake_minimum_required(VERSION ...
    程式設計 發佈於2024-11-17
  • 大批
    大批
    方法是可以在物件上呼叫的 fns 數組是對象,因此它們在 JS 中也有方法。 slice(begin):將陣列的一部分提取到新數組中,而不改變原始數組。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index ...
    程式設計 發佈於2024-11-17
  • 為什麼我在 Django 資料庫查詢中收到「InterfaceError (0, \'\')\」?
    為什麼我在 Django 資料庫查詢中收到「InterfaceError (0, \'\')\」?
    在Django 查詢執行中遇到InterfaceError (0, '')Django 使用者可能會遇到持久的「InterfaceError (0 , '')” "嘗試資料庫操作時出錯,特別是在伺服器重新啟動後。資料庫的持久連接,在多個資料庫中保持開啟狀態運...
    程式設計 發佈於2024-11-17
  • MySQL 中的分片真的是大型資料集的最佳方法嗎?
    MySQL 中的分片真的是大型資料集的最佳方法嗎?
    MySQL 中的分片:一種關鍵方法在優化MySQL 資料庫時,分片作為處理大型資料集的潛在解決方案而出現。然而,在實施分片之前了解與分片相關的權衡和潛在陷阱至關重要。 最佳方法:不分片與普遍看法相反,除非絕對必要,否則對 MySQL 表進行分片的最佳方法是避免這樣做。為什麼?分片帶來了重大的技術挑戰...
    程式設計 發佈於2024-11-17
  • 如何在 MySQL SELECT 查詢中使用 IF 語句:語法與最佳實務指南
    如何在 MySQL SELECT 查詢中使用 IF 語句:語法與最佳實務指南
    MySQL SELECT 查詢中的IF 語句用法嘗試在MySQL SELECT 查詢中實現IF 語句時,您可能會遇到困難,因為具體語法要求。在 MySQL 中,傳統的 IF/THEN/ELSE 結構僅在預存程序和函數中可用。 要解決此問題,重構查詢至關重要。查詢中支援的 IF() 函數主要用於根據 ...
    程式設計 發佈於2024-11-17
  • 如何在Python中產生特定範圍內的唯一隨機數?
    如何在Python中產生特定範圍內的唯一隨機數?
    產生範圍內的唯一隨機數產生隨機數時,確保每個數字都是唯一的可能是一個挑戰。雖然可以使用條件語句來檢查重複項,但在處理大範圍或大數字時,這種方法會變得很麻煩。 產生唯一隨機數字列表的簡單方法是使用 Python 的 random.sample( ) 功能。此函數有兩個參數:總體(產生隨機數的數字範圍)...
    程式設計 發佈於2024-11-17
  • 如何使用佔位符和參數執行帶有“WHERE...IN”的 PDO 查詢?
    如何使用佔位符和參數執行帶有“WHERE...IN”的 PDO 查詢?
    PDO 查詢“WHERE...IN”為了使用PDO 增強數據庫訪問,許多開發人員遇到了挑戰,特別是“WHERE...IN” IN”查詢。讓我們深入研究其中的複雜性,並發現在PDO 準備好的語句中使用項目列表的正確方法。“WHERE... IN”難題考慮一個場景,您需要根據表單中已檢查項目的清單從資料...
    程式設計 發佈於2024-11-17
  • 如何有效率地存取和刪除Python字典中的任意元素?
    如何有效率地存取和刪除Python字典中的任意元素?
    存取 Python 字典中的任意元素在 Python 中,字典將鍵值對儲存在無序集合中。如果字典不為空,則可以使用下列語法存取任意(隨機)元素:mydict[list(mydict.keys())[0]]但是,這這種方法可能冗長且低效,特別是當您需要執行多次迭代時。讓我們探索更有效的方法來實現此目的...
    程式設計 發佈於2024-11-17
  • 如何使用 jQuery 製作背景顏色動畫?
    如何使用 jQuery 製作背景顏色動畫?
    使用 jQuery 淡化背景顏色引人注目的網站元素通常需要微妙的動畫,例如淡入和淡出。雖然 jQuery 廣泛用於動畫文字內容,但它也可用於動態增強背景顏色。 在 jQuery 中淡入/淡出背景顏色進行操作要使用 jQuery 設定元素的背景顏色,您首先需要合併 jQueryUI 函式庫。整合後,可...
    程式設計 發佈於2024-11-17

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3