🍰

Go言語で学ぶ Webアプリケーション開発4:[デプロイ & DB負荷対策]

2025/03/04に公開

はじめに

前回の記事では、Dockerを使ったコンテナ化とgRPCを導入し、よりスケーラブルなWebアプリケーションを構築しました。
今回は、簡易的なHerokuへのデプロイ方法と、大量のToDoデータを扱う際のパフォーマンス最適化(負荷対策)について解説します。

対象読者

  • Goで開発したWebアプリをデプロイしたい方
  • Webアプリのパフォーマンスを改善し、大量データに対応できる設計を学びたい方

目次

  1. Heroku へのデプロイ
    • Herokuのセットアップ
    • HerokuにGoアプリをデプロイする手順
  2. ToDoアプリの負荷対策
    • インデックス最適化(データベース)
    • キャッシュ戦略(Redis の活用)
    • 非同期処理(Go の Goroutine を活用)
    • ページネーション(大量データの取得を最適化)

1. Herokuへのデプロイ

1.1 Herokuのセットアップ

まず、Heroku CLIをインストールして、Herokuアカウントにログインします。

# Heroku CLI をインストール(Mac)
brew install heroku

# ログイン
heroku login

1.2 HerokuにGoアプリをデプロイする手順

  1. Heroku用のProcfileを作成
    HerokuはProcfileを使ってアプリの起動方法を定義します。

    Procfile

    web: ./main
    
  2. Heroku用のgo.modの作成

    go mod init example.com/myapp
    go mod tidy
    
  3. Herokuにアプリをプッシュ & デプロイ

    git init
    git add .
    git commit -m "Initial commit"
    heroku create my-go-todo-app
    git push heroku main
    
  4. Herokuでアプリを実行 & 確認

    heroku open
    

2. ToDo アプリの負荷対策

2.1 インデックス最適化(データベース)

  • ToDoの数が増えてくると、データベースの検索速度が遅くなる。
  • インデックスを設定することで、検索を高速化できる。
import "gorm.io/gorm"

type Todo struct {
    ID     uint   `gorm:"primaryKey"`
    Task   string `json:"task" gorm:"index"`
    Status bool   `json:"status"`
}
  • gorm:"index"をつけることで、データベース側でTaskフィールドの検索が最適化される。

2.2 キャッシュ戦略(Redisの活用)

  • 頻繁に取得するデータをRedisにキャッシュすることで、DB負荷を軽減する。
  • github.com/go-redis/redis/v8 を利用。

Redisの導入

go get github.com/go-redis/redis/v8

Redisクライアントのセットアップ

import (
    "context"
    "github.com/go-redis/redis/v8"
)

var ctx = context.Background()
var rdb = redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})

キャッシュを使った ToDo 取得

func GetTodos(c *gin.Context) {
    cached, err := rdb.Get(ctx, "todos").Result()
    if err == nil {
        c.JSON(http.StatusOK, cached)
        return
    }
    
    var todos []models.Todo
    database.DB.Find(&todos)
    rdb.Set(ctx, "todos", todos, time.Minute*10)
    c.JSON(http.StatusOK, todos)
}
  • Redisにデータがあればキャッシュから返し、なければDBから取得してキャッシュに保存。

2.3 非同期処理(Goroutineの活用)

  • 新しいToDoの登録時に、非同期でログを記録する。
func CreateTodo(c *gin.Context) {
    var newTodo models.Todo
    if err := c.ShouldBindJSON(&newTodo); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    database.DB.Create(&newTodo)
    
    go func(task string) {
        log.Printf("New ToDo created: %s", task)
    }(newTodo.Task)
    
    c.JSON(http.StatusCreated, newTodo)
}
  • go func()を使うことで、非同期でログの記録を行い、メインスレッドのレスポンス速度を落とさないようにできる。

2.4 ページネーション(大量データの取得を最適化)

  • ページネーションを導入し、1回のAPIリクエストで取得するデータ量を制限。
func GetTodosPaginated(c *gin.Context) {
    var todos []models.Todo
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    limit := 10
    offset := (page - 1) * limit
    database.DB.Limit(limit).Offset(offset).Find(&todos)
    c.JSON(http.StatusOK, todos)
}
  • GET /api/todos?page=2のようにページ番号を指定すると、10件ずつデータを取得するようになる。

まとめ

項目 説明
Heroku デプロイ Heroku CLIを使って簡単にGoアプリをデプロイ
インデックス最適化 データベース検索を高速化し、レスポンス遅延を軽減
Redis キャッシュ 頻繁にアクセスするデータをキャッシュし、DB負荷を削減
非同期処理 (Goroutine) ToDoの作成時にログ記録を非同期で実行し、API応答速度を向上
ページネーション 一度に大量のデータを取得せず、適切なデータ量を取得

本記事では、簡単なデプロイ方法と、大量のToDoデータを扱う際の負荷対策について解説しました。
次は、Kubernetesを活用したデプロイ方法とか、負荷テストの実施について補足がてらにまとめようかな...

Discussion