Open4

バッチ・メール

engineer rebornengineer reborn

バッチの設計

システムの停止

  • 主な停止理由
    • awsなどの基盤の障害
    • panicの発生
    • oom
      • プロセスの矯正終了
  • その他メトリクス
    • cpuが100%以上の場合は遅延
    • ネットワーク
      • 遅延 or タイムアウト

気を付けること要点

  • 停止しない
    • メモリ OOM
  • 実行時間
    • 実行時間が長いとデータをロックする時間も長いのでいつ実行するか?は大事
  • ロック
    • リトライ必要じゃない?

気をつけること

  • 安定性と堅牢性
    • エラーハンドリング
      • 外部サービスの一時的な接続エラー
        • リトライ、スキップ
    • 冪等性
      • 同じバッチが複数回実行されても最終的なシステムの状態が一貫している
        • ex) 同じ日付なら同じ結果になるとか
          • 最初の抽出データが日付で区切られていることが多いので
    • リトライ戦略
      • 外部サービス連携(例: SendGrid API)で一時的なエラーが発生した場合に、どのようにリトライするかの戦略
        • 回数、間隔、指数バックオフなど
    • データ整合性の維持
      • システム障害の時などはトランザクションなどでロールバック
  • パフォーマンス
    • 実行時間の見積もり
    • リソース消費の最適化
    • 処理単位とバッチサイズ
      • 一気にやるのではなく、適切なサイズに分けること
        • メモリ使用量の削減
        • エラー時
  • 運用と監視
    • ログの出力
      • 開始、進捗、完了をログ出力が基本
        • 進捗は「件数」が基本
      • エラーログ
        • ユーザidなどのデータを特定できる情報が必要
    • 監視とアラート
    • 通知機能
  • セキュリティ
    • 認証管理
      • DBアクセス・APIキー
engineer rebornengineer reborn

検証コード

https://github.com/Awtanabe/mail_cobra

実装参考

参考2 jordan-wright/email

  • 簡易メール送信

https://github.com/jordan-wright/email

参考 3

https://shungoblog.com/programming/docker-go-mailhog-net-smtp.html

go html

https://github.com/yosssi/gohtml

送信サンプル

<h1>{{.Title}}</h1>
<p>{{.Message}}</p>
package main

import (
	"bytes"
	"embed"
	"fmt"
	"html/template"
	"log"

	"github.com/yosssi/gohtml"
)

type EmailData struct {
	Title   string
	Message string
}

//go:embed email_template.html
var emailTemplateFS embed.FS

func main() {
	// 1. 埋め込みファイル読み込み
	tmplContent, err := emailTemplateFS.ReadFile("email_template.html")
	if err != nil {
		log.Fatal("テンプレート読み込み失敗:", err)
	}

	// 2. テンプレート作成
	tmpl := template.Must(template.New("email").Parse(string(tmplContent)))

	// 3. データ適用
	var buf bytes.Buffer
	err = tmpl.Execute(&buf, EmailData{
		Title:   "こんにちは",
		Message: "GoでHTMLメールを送っています!",
	})
	if err != nil {
		log.Fatal("テンプレート実行失敗:", err)
	}

	// 4. 整形表示(開発中の確認などに)
	formatted := gohtml.Format(buf.String())
	fmt.Println(formatted)

	// 5. メール送信時は buf.Bytes() を使えばOK
	// email.HTML = buf.Bytes()
}

engineer rebornengineer reborn

DBの理解

ロック

  • データロック
    • デットロック
      • バッチでロックしているデータにAPIでもロックをかけて処理が走る
        • user user_settingがあったとして、バッチでuserにロックを apiでuser_settingにロックをかける
      • デットロックが発生した場合
        • 片方を失敗させて処理を継続させる
    • ロックの粒度
    • ロックモード
      • 共有
        • selectはできる
      • 排他ロック
        • selectもできない

デットロックはなぜ発生するか?

  • dbにはコネクションを複数貼ることができる
    • batchの実行毎、APIの1リクエスト事
  • コネクション毎に独立しているので、別のコネクションのトランザクションの情報などは知らない為発生する

データのパッチ当て

  • SET SESSION lock_wait_timeout=5;などを指定するのは、優先的にパッチ側を失敗させる。
    • これは、「オンラインサービス(API、Webサイトなど)の可用性維持が最優先」だから
  • トランザクションを張るべきである