Goの実行モデルと前提
Goの実行モデルと前提
Section titled “Goの実行モデルと前提”Goの実行モデルと、実務で事故を防ぐための前提条件を詳しく解説します。
実行モデルとリソースの物理的制約
Section titled “実行モデルとリソースの物理的制約”コンピュータ資源は有限であり、性能ではなく制約を前提に設計することが基本です。
主な物理的制約
Section titled “主な物理的制約”CPU・メモリよりも先に枯渇するリソース:
-
DB・外部APIのコネクション数
- 接続プールの上限(例:
SetMaxOpenConns(10)) - 接続リークは数時間後にシステム全体を停止させる
- 接続プールの上限(例:
-
Goroutineのリーク
- Goroutineが終了しないと、メモリが枯渇する
- 数千から数百万のgoroutineがリークすると、システムがクラッシュする
-
ファイル記述子
- OSレベルの制限(通常1024〜65536)
- ファイルやソケットを適切にクローズしないと枯渇
-
Channelのデッドロック
- Channelの送信と受信がブロックされると、Goroutineが停止する
実際の事故例:
10:00:00 - アプリケーション起動(Goroutine数: 10)10:00:01 - リクエスト1受信(Goroutine1を開始、終了しない)10:00:02 - リクエスト2受信(Goroutine2を開始、終了しない)...10:30:00 - リクエスト1000受信(Goroutine1000を開始、終了しない)10:30:01 - メモリ使用量: 2GB10:30:02 - OutOfMemoryError発生10:30:03 - アプリケーションがクラッシュGoの実行モデル
Section titled “Goの実行モデル”GoroutineとChannel
Section titled “GoroutineとChannel”実行モデル:
Goプログラム├─ メインgoroutine├─ goroutine1(並行実行)├─ goroutine2(並行実行)└─ goroutine3(並行実行)重要な特徴:
- Goroutine: 軽量スレッド、数千から数百万のgoroutineを実行可能(ただし、リークに注意)
- Channel: goroutine間の通信(デッドロックに注意)
- GOMAXPROCS: 並行実行されるgoroutineの数
- ガベージコレクション: 自動メモリ管理(ただし、参照が保持されている場合は動作しない)
トランザクション境界
Section titled “トランザクション境界”Goのトランザクション管理:
// GORMでのトランザクション管理func CreateOrder(orderData OrderData) (*Order, error) { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }()
// トランザクション内の処理 order := &Order{UserID: orderData.UserID, Amount: orderData.Amount} if err := tx.Create(order).Error; err != nil { tx.Rollback() return nil, err }
// 在庫を更新 for _, item := range orderData.Items { if err := tx.Model(&Inventory{}). Where("product_id = ?", item.ProductID). Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil { tx.Rollback() return nil, err } }
if err := tx.Commit().Error; err != nil { return nil, err }
return order, nil}特徴:
- 明示的なトランザクション境界:
Begin()とCommit()で明示 - 手動ロールバック: エラー時に手動でロールバック
- defer文: エラー時のロールバックを保証
他言語との比較:
// Java: 宣言的トランザクション管理@Transactionalpublic Order createOrder(OrderData orderData) { Order order = orderRepository.save(new Order(orderData)); return order;}Goの非同期処理:
// Goroutineを使用func processOrder(orderID int64) { go func() { // 非同期処理 paymentService.ChargePayment(orderID) }()}特徴:
- 信頼できる非同期: Goroutineによる制御
- エラーハンドリング: 手動で実装する必要がある
- 再実行: 手動で実装する必要がある
他言語との比較:
// Node.js: Promise/async-awaitasync function processOrder(orderId: number) { await paymentService.chargePayment(orderId);}実行環境による特性
Section titled “実行環境による特性”| 環境 | 特徴 | 主なリスク |
|---|---|---|
| Serverless (Lambda/Vercel) | 短寿命・自動スケール | コールドスタート、接続バースト、DBパンク、実行時間制限(Lambda: 15分、Vercel: 300秒) |
| 常駐プロセス (Go HTTP Server) | 長寿命・安定動作 | Goroutineリーク、メモリリーク、接続リーク、Channelデッドロック |
Serverless環境での実行
Section titled “Serverless環境での実行”制約:
// ❌ 悪い例: Serverless環境で問題のあるコードfunc Handler(w http.ResponseWriter, r *http.Request) { // 問題: 長時間実行される可能性がある // 問題: トランザクションが長時間保持される // 問題: 接続プールが適切に管理されない order := createOrder(orderData) json.NewEncoder(w).Encode(order)}問題点:
- 実行時間の制限: Lambdaは最大15分、Vercelは最大300秒
- コールドスタート: Goの起動に時間がかかる(50-200ms)
- メモリ制限: メモリ使用量に制限がある(Lambda: 128MB〜10GB)
- 接続バースト: スケールアウト時に接続プールが急増し、DBがパンクする可能性
解決策:
// ✅ 良い例: Serverless環境に適したコードfunc Handler(w http.ResponseWriter, r *http.Request) { // 1. バリデーション(短時間) if err := validateOrderData(orderData); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }
// 2. 注文を作成(短時間) order, err := createOrder(orderData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
// 3. 非同期処理をキューに投入 messageQueue.Send("order.created", map[string]interface{}{ "orderId": order.ID, "amount": order.Amount, })
// 4. 即座にレスポンスを返す w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "orderId": order.ID, "status": "PROCESSING", })}常駐プロセス環境での実行
Section titled “常駐プロセス環境での実行”Goアプリケーションサーバー
Section titled “Goアプリケーションサーバー”特徴:
// 常駐プロセス環境での実行func main() { http.HandleFunc("/orders", createOrderHandler) http.ListenAndServe(":8080", nil)}メリット:
- 長時間実行可能: 実行時間の制限がない
- 接続プール: データベース接続プールを保持
- Goroutine: 並行処理が可能
実装例:
// ✅ 良い例: 常駐プロセス環境に適したコードfunc createOrderHandler(w http.ResponseWriter, r *http.Request) { orderData := parseOrderData(r)
// トランザクション内で処理 order, err := createOrder(orderData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
// バックグラウンド処理をGoroutineで実行 go func() { paymentService.ChargePayment(order.ID, order.Amount) }()
w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(order)}Goの実行モデルと前提のポイント:
- リソースの物理的制約: CPU・メモリよりも先に枯渇するのは、DB接続数・Goroutineリーク・ファイル記述子・Channelデッドロック
- Goroutine: 軽量スレッド、並行処理が可能(リークに注意)
- トランザクション境界:
Begin()とCommit()で明示、手動ロールバック - 非同期処理: Goroutineによる制御、エラーハンドリングが必要
- Serverless環境: 実行時間制限、コールドスタート、メモリ制限、接続バースト
- 常駐プロセス環境: 長時間実行可能、接続プール、Goroutine(Goroutineリーク・接続リークに注意)
重要な原則: 性能ではなく制約を前提に設計する。Goroutineリークや接続リークは数時間後にシステム全体を停止させる。