以下の内容はhttps://techblog.zozo.com/entry/chatops-data-syncより取得しました。


運用コストを99%削減 ── ChatOpsによる3社間データ連携の自動化

運用コストを99%削減 ── ChatOpsによる3社間データ連携の自動化

はじめに

こんにちは、カート決済部カート決済サービスブロックの河津です。2025年度に新卒入社し、現在のチームに配属されました。ZOZOTOWN内のカート機能や決済機能の開発、保守運用を担当しています。

私たちのチームでは、取引先から受け取ったデータを外部サービスへ連携する作業を月2回実施しています。従来は全て手動運用で行っており、30分の作業負担ヒューマンエラーのリスクが課題となっていました。

本記事では、ChatOps構築により、作業時間を30分から数秒へ大幅短縮し、
運用の安全性・効率性・業務の自律性を同時に高めることができた仕組みについて紹介します。

目次

背景・課題

カート決済に関わる業務の中で、A社、B社、そして弊社のビジネス部門とエンジニア部門の3社間で定期的に手動運用によるデータ連携作業が月に2回発生しています。この作業は毎回30分以上かかり、ヒューマンエラーの可能性やコミュニケーションコストが高く、関係者の負担になっていました。

具体化している3つの課題

1. 作業負荷と属人化

月2回の定期作業であり、以下の作業に30分以上費やしていました。 作業負荷

  1. A社からのExcelファイルをローカル端末に保存
  2. 社内DBからデータ取得
  3. Excelファイルの内容に必要な項目を補完
  4. 補完後のExcelファイルをS3へ手動アップロード
  5. 関係者に完了連絡

さらに、これらの手順は特定の担当者のみが把握しており、急な欠勤時の作業が困難な状況でした。

2. ヒューマンエラーのリスク

従来のデータ連携は、複数の手作業工程(Excelファイルの加工、S3アップロード、外部通知など)を伴っていました。
そのため、作業者の確認漏れやミスにより、業務全体に影響する以下のようなヒューマンエラーの発生が懸念されました。

エラー種別 発生しうる例 影響範囲・リスク
連携データの不整合 Excel での加工時にセルのズレやフィルタ漏れがある 外部サービス側で整合性違反が発生し、異常処理が困難になる恐れ
S3 アップロードのパス間違い 手入力でパスを指定する際のタイプミスや、過去の作業をコピー&ペーストして誤ったディレクトリに上書き 外部サービスがファイルを取得できず、連携全体が停止する
B社への連携通知時のファイルパス誤り Slackやメールでの手動連絡時に、最新ファイルではない旧ファイルのパスを記載 外部サービスが古いデータで処理を進め、再加工での修正が必要になる

これらのエラーは、発生頻度は低くても影響が大きいものばかりです。特に対象となるファイルは業務上欠かせないデータであり、一度でもミスが発生すると顧客対応による時間ロスや信頼低下を引き起こします。こうしたリスクを最小化するためにも、作業時に人力処理を減らし、機械的に正確な処理ができる体制を整える必要がありました。

3. コミュニケーションコストの増大

4つの関係部門(A社、ビジネス部門、エンジニア部門、B社)間での調整コストが高く、以下のような非効率な事象が生じていました。

関係性

  • 作業開始・終了の都度、Slackでの連絡が必要。
  • エラー発生時の原因調査に複数部門が関与。

システムアーキテクチャの刷新

既存の業務フローの全体像

既存のデータ連携業務は、以下のような流れで実施されていました。A社から弊社ビジネス部門へのデータ連携、そしてビジネス部門からエンジニア部門への連携、エンジニア部門からB社への連携という形で行われています。 旧アーキテクチャ

新システムの全体構成

今回新たに構築したシステムではSlackを起点としたイベントドリブンなアーキテクチャを採用しました。Slack上での操作をトリガーに処理が自動実行されるため、エンジニア部門による手作業が不要になりました。 新アーキテクチャ

技術スタックの選定理由

なぜChatOpsを採用したのか

管理画面を新規開発すると、要件定義からUI設計・実装・権限管理・保守まで含めて工数が大きくなり、運用開始までに時間とコストがかかります。さらに、仕様変更や機能追加のたびにエンジニア部門による改修が必要になり、運用の柔軟性が損なわれる可能性もありました。

こうした背景から、SlackワークフローとAWS Lambdaを活用したChatOpsを採用しました。ChatOpsには以下のメリットがあります。

  • 短期間・低コストで構築可能
    既存のSlackとAWSリソースを活用し、新規UI開発が不要。

  • 作業ログ記録
    誰がいつ実行したかをSlackやAWS CloudWatchで記録。

  • セキュリティがシンプル
    AWS Chatbot経由で実行権限をローカルに保持せず安全。

  • 拡張性が高い
    Lambda関数やワークフロー追加により容易に機能拡張が可能。

  • ビジネス部門が操作可能
    エンジニア部門への依存が不要、ビジネス部門がワークフローを実行するだけで完結。

  • 社内ナレッジ活用
    他部署のChatOpsを活用している他パターンを流用し、短期間で構築が可能。

なぜSlackワークフローなのか

  • 既存インフラの活用 - 新規ツール導入が不要
  • 直感的なUI - ファイルドラッグ&ドロップで完結
  • リアルタイム性 - 処理結果を即座に通知可能

なぜAWS Lambdaなのか

  • コスト効率 - 月2回の実行に最適な従量課金
  • スケーラビリティ - データ量の増減に自動対応
  • 運用負荷の最小化 - サーバー管理が不要

なお、システム構成の詳細や最小権限のIAM例、運用フローの細部については、こちらのテックブログをご確認ください。

techblog.zozo.com

実装のポイント

1. Slackイベントからのファイル取得処理

Slack Workflowの制限への対処

Slack Workflowではファイルのアップロードはできますが、アップロードしたファイルのIDを変数として使えません。この制限により、ワークフロー内でファイルの内容を直接読み取ったり後続の処理に渡したりできないという課題に直面しました。

そこで、以下のような工夫を実施しました。

  1. ワークフローの変数のタイムスタンプとワークフロー実行者をAWS Lambdaのペイロードとして渡す
  2. Slack APIを使用して過去のメッセージを取得
  3. 取得したメッセージの中でタイムスタンプと実行者が一致するものを検索
  4. 一致したメッセージ内のファイルを取得

この手法により、ワークフローからファイルを取得することが可能になりました。

実装内容

func GetSlackFileByTimestampAndExecutor(
    client *slack.Client, 
    channelID, executor, timestamp string) (*slack.File, error) {

    // Slack会話履歴から該当メッセージを検索
    history, _ := client.GetConversationHistory(&slack.GetConversationHistoryParameters{
        ChannelID: channelID,
        Limit:     100,
    })

    for _, message := range history.Messages {
        // Lambda invokeコマンドのペイロードを抽出・解析
        if payload := extractPayload(message.Text); payload != nil {
            // 実行者とタイムスタンプが一致するメッセージを検索
            if ExtractUserID(payload.Executor) == ExtractUserID(executor) && payload.Timestamp == timestamp &&  {
                if len(message.Files) > 0 {
                    return &message.Files[0], nil
                }
            }
        }
    }

    return nil, fmt.Errorf("no matching message found")
}

ファイル取得処理の工夫ポイント

1. 正規表現による厳密なフィルタリング

正規表現により、以下を確実に識別します。

  • lambda invokeコマンドであること
  • 関数名がsync-data-to-externalであること
  • ワークフローから起動されたLambdaであることを保証
var regexLambdaInvoke = regexp.MustCompile(
    `lambda invoke.*--function-name\s+sync-data-to-external.*`)
2. JSON部分の切り出し

Slackメッセージ内のLambda invokeコマンドは以下のような形式です。

lambda invoke --function-name sync-data-to-external --payload {"timestamp":"1234567890.123456","executor":"U12345"}

このメッセージから--payload以降のJSON部分を正確に切り出す必要があります。

// --payloadの位置を検索
startIdx := strings.Index(messageText, "--payload")
afterPayload := messageText[startIdx+9:] // "--payload"の9文字分をスキップ

// JSONオブジェクトの終了位置を検索
endIdx := strings.Index(afterPayload, "}")
jsonStr := afterPayload[:endIdx+1]
3. ユーザーID形式の正規化

Slackでは同じユーザーIDが異なる形式で表示されることがあります。

func ExtractUserID(input string) string {
    // <@U12345> 形式から U12345 を抽出
    if strings.HasPrefix(input, "<@") && strings.HasSuffix(input, ">") {
        return input[2 : len(input)-1]
    }
    return input
}

これにより、<@U12345>U12345を同一視できます。

4. 二重の検証による確実性

実行者IDとタイムスタンプの両方を完全一致で検証します。

if ExtractUserID(payload.Executor) != ExtractUserID(executor) || payload.Timestamp != timestamp {
    continue
}

この二重チェックにより、偶然の一致や意図しないアクセスを防ぎます。

これらの手法がもたらすメリット

  1. Slack Workflowの制限を柔軟に回避

    • ファイルを直接変数として扱えない制限があっても既存のSlack API, AWS Lambdaで対処可能
  2. 高いセキュリティ

    • タイムスタンプと実行者の二重照合
    • 正規表現による厳密なメッセージフィルタリング
    • ファイルの直接URLを扱わない安全な取得
  3. 完全な監査証跡

    • 全ての操作がSlackの会話履歴に記録
    • 誰がいつどのファイルをアップロードしたか追跡可能
    • Lambda実行との紐付けが確実
  4. デバッグの容易さ

    • 詳細なログ出力により動作の検証が簡単
    • Slack画面で実際のメッセージを確認可能

この工夫により、Slack Workflowの制限を損なうことなく、セキュアで確実なファイル処理を実現しています。

2. 並列でのAPI実行

データの外部APIへの送信では、処理時間を短縮するため、Goのgoroutineを活用した並列処理を実装しました。

並列化の背景

月2回の定期データ連携とはいえ、データは数十〜百件を超える場合があります。順次処理では外部APIのレスポンス時間により数分を要するため、並列処理で高速化しました。

並列処理の実装詳細

func PostApi(payloads []DataPayload) ([]APILogs, error) {
    // 外部API設定の初期化(1度だけ実行)
    cfg, err := initRequestConfig(context.Background())
    if err != nil {
        return nil, fmt.Errorf("APIの設定初期化に失敗しました: %v", err)
    }

    // レスポンスを格納するスライス(順序を保持)
    responses := make([]APILogs, len(payloads))
    var wg sync.WaitGroup
    var mu sync.Mutex

    // 各ペイロードを並列で処理
    for i, payload := range payloads {
        wg.Add(1)
        go func(index int, p DataPayload) {
            defer wg.Done()

            // API実行
            result := executeAPIRequest(cfg, p)

            // 結果を元のインデックス位置に格納
            mu.Lock()
            responses[index] = result
            mu.Unlock()
        }(i, payload)
    }

    wg.Wait()
    return responses, nil
}

並列化の工夫ポイント

1. 設定の共有による効率化

外部API設定(認証情報、ヘッダー、HTTPクライアント)を事前に1度だけ初期化し、全てのgoroutineで共有しています。

// initRequestConfig で設定を一元管理
cfg, err := initRequestConfig(context.Background())

// RequestConfig 構造体
type RequestConfig struct {
    URL     string
    Headers http.Header
    Client  *http.Client
}

これにより、毎回AWS Secrets Managerから認証情報を取得するオーバーヘッドを削減しています。

2. エラーハンドリングの統一

API呼び出しのどの段階でエラーが発生しても、統一的な形式でログ記録できるよう設計しました。

func executeRequest(cfg *RequestConfig, payload DataPayload) APILogs {
    req, requestBody, err := cfg.CreateRequest(payload)
    if err != nil {
        // リクエスト作成エラーでもログ構造体を返す
        return APILogs{
            Message:     err.Error(),
            RequestData: requestBody,
            CreatedAt:   time.Now().In(time.FixedZone("JST", 9*60*60)),
        }
    }

    res, err := cfg.Client.Do(req)
    if err != nil {
        // HTTP通信エラーでもログ構造体を返す
        return APILogs{
            Message:     err.Error(),
            RequestData: requestBody,
            CreatedAt:   time.Now().In(time.FixedZone("JST", 9*60*60)),
        }
    }
    // ...
}

この設計により、一部のAPIリクエストでエラーが発生しても他の処理は継続され、全ての処理結果(成功・失敗含む)を追跡可能な形で保持できます。

3. メモリ効率を意識した実装

大量のデータ処理を想定し、レスポンスのスライスを事前確保する実装を採用しました。

// 事前に必要な容量を確保
responses := make([]APILogs, len(payloads))

この実装により、以下のような効果が得られました。

  • スライスの動的拡張による再アロケーションを防ぎ、メモリ断片化を回避。
  • 処理を開始する時点でメモリ使用量が確定し、予期しないメモリ不足を防止。
  • 特に100件規模の処理では、動的拡張と比較して安定したパフォーマンスを実現。

並列度の制限について

現在の実装では、全てのペイロードに対して同時にgoroutineを起動していますが、これは以下の理由から意図的に設計したものです。

  1. 処理頻度が低い:月2回の実行で、1回あたり最大でも100件程度
  2. 外部APIの安定性:エンタープライズ向けAPIで十分な処理能力がある

ただし、将来的に処理数が増加した場合に備え、セマフォによる並列度制限を追加することも検討しています。

// 将来的な拡張例
sem := make(chan struct{}, 10) // 同時実行数を10に制限

for i, payload := range payloads {
    sem <- struct{}{} // セマフォ取得
    go func(index int, p DataPayload) {
        defer func() { <-sem }() // セマフォ解放
        // 処理実行
    }(i, payload)
}

処理時間の改善効果

並列化を導入した結果、以下の改善が見られました。 グラフ

  • 50件の処理:約50秒 → 約2-3秒(95%削減)
  • 100件の処理:約100秒 → 約3-5秒(95%削減)

この高速化により、Lambdaのタイムアウトリスクが軽減され、安定した運用が可能になりました。

実装結果と運用効果

3つの課題がどのように解決されたか

1. 作業負荷と属人化 → 99.44%の作業時間を削減と標準化

項目 導入前 導入後 改善効果
作業時間 30分/回 10秒/回 99.44%削減
作業手順 特定担当者のみ把握 Slackワークフローで誰でも実行可能 属人化解消
引き継ぎ 詳細な手順書が必要 ワンクリックで完結 引き継ぎ不要

2. ヒューマンエラーのリスク → 自動化による完全排除

エラー種別 導入前のリスク 導入後の状況
連携データの不整合 Excel加工時のミスの可能性 Lambda内で自動処理されエラー発生ゼロ
S3アップロードのパス間違い 手入力によるタイプミス プログラムで固定パスへ自動アップロード
B社への連携通知時のファイルパス誤り 古いファイルパスを誤送信 自動生成されたパスを即座に通知

3. コミュニケーションコスト増大 → Slack一元化で大幅削減

コミュニケーション項目 導入前 導入後
作業開始の連絡 手動でSlack投稿 ワークフロー実行で自動通知
完了連絡 各部門へ個別連絡 処理結果を自動投稿
エラー時の調査 複数部門で原因調査 CloudWatchログで即座に特定

チームの生産性向上への貢献

上記3つの課題解決に加えて、以下のような波及効果によりチーム全体の生産性が向上しました。

  • 月間1時間の作業時間を削減し、より価値の高い開発業務へ転換
  • 心理的負担の軽減により、定期作業日のストレスが解消
  • 監査証跡の自動記録により、コンプライアンス対応工数も削減

まとめ

本記事では、ChatOpsを用いたデータ連携自動化システムの構築について紹介しました。業務の自動化は、単なる効率化を超えて、チームの働き方や価値提供のあり方を変える可能性を秘めています。今後もこの仕組みを発展させ、より多くの業務で最適化していきたいと考えています。

ZOZOでは、一緒にサービスを盛り上げてくれる仲間を募集しています。ご興味のある方は、以下のリンクからぜひご覧ください。

corp.zozo.com




以上の内容はhttps://techblog.zozo.com/entry/chatops-data-syncより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14