DB設計レビューの負荷を7割削減 ── Slack × Bedrockで実現した自動化の仕組み

DB設計レビューの負荷を7割削減 ── Slack × Bedrockで実現した自動化の仕組み

はじめに

こんにちは、SRE部カート決済SREブロックの伊藤(@_itito_)です。普段はZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。また、データベース(以下DB)領域でのテックリードを担っており、DBREとしてDB周りの運用・保守・構築に関わっています。

弊社のDBRE活動については、以前次の記事で紹介しました。

techblog.zozo.com

この活動の中で、DBのテーブル定義の設計レビューを行っています。この運用にAWSのBedrockを用いて自動化を組み込んだ取り組みを紹介します。

目次

背景・課題

弊社では2020年頃から、ZOZOTOWNに関わるDBのテーブルや列の「追加・更新・削除」を行う際には、設計レビューを必須としています。これは、主に以下の3つの目的で開始されました。

  • リプレイスが進むZOZOTOWNのDB周りの全体像把握のため
  • 秘密情報の含まれたデータがマスク化などのアクセス制御が行われずにリリースされることを防ぐため
  • 開発ガイドラインに沿った設計をしているかを確認するため

秘密情報の取扱いルールについては以下で紹介しています。

techblog.zozo.com

また、弊社では各プラットフォームごとに開発ガイドラインが定められています。例えば以前本ブログで紹介されたものとしてはバッチの開発ガイドラインがあります。

techblog.zozo.com

DBの設計においては、命名規約やテーブル設計時の注意点、データ保護のためのルールなどが定められています。

DB設計レビューの課題

上記のような目的で行われているDB設計レビューですが、運用する中でいくつか課題を抱えていました。

レビュー工数と「トイル化」の問題

レビュー依頼は週に多くても10件程度と、件数自体は多くありません。しかし、そのほとんどが「開発ガイドラインに沿っているか」を確認する定型的な作業であり、一種のトイル(手作業による非効率な繰り返し作業)となっていました。

開発者によるガイドライン遵守度のばらつき

ZOZOTOWNの開発には、多くの社員やパートナー会社のエンジニアが関わっています。これは私たちの強みである一方、開発ガイドラインへの遵守度に大きく差が生まれる側面もありました。ガイドラインにまだ慣れていないメンバーの設計では、基本的な指摘が多くなるケースもあり、レビューにかかる工数・負荷の増加につながっていました。

DBレビューフローの変更方針

これまでDB設計レビューを次のようなフローで行っていました。

  1. 開発者がConfluence上に設計書を作成
  2. Slackの専用チャンネルにてワークフローからレビュー依頼を投稿
  3. DBREメンバーがレビューを実施
  4. 設計レビュー承認後に実装を開始

このフローの改善を考える中で、根本から仕組みを見直すことも検討しました。例えば、設計書をMarkdownでGit管理してプルリクエストでレビューするフローへと切り替えるとCopilotによる自動レビューなどが可能になります。

しかしER図のような視覚的な表現が難しくなる点や、大きな変更はかえって開発者側の負担を増やしてしまう懸念がありました。

そこで、既存フローの途中でBotによる自動レビューを組み込むことにしました。

  1. 開発者がConfluence上に設計書を作成
  2. Slackの専用チャンネルにてワークフローからレビュー依頼を投稿
  3. BotがConfluenceからテーブル構造を読み取り、レビューを実施
  4. DBREメンバーがBotで判断できなかった部分を追加でレビュー
  5. 設計レビュー承認後に実装を開始

実際に手順3の自動レビューの流れを、実装後のキャプチャを用いて図示したのが次の画像です。

自動レビューの流れ

この仕組みを構築するにあたって使用した技術や実装の詳細について、次の章で紹介します。

自動レビューBotの設計・実装

技術選定

システムを構築するにあたり、必要となる技術の調査および選定をしました。

作成するレビューシステムとSlackとの連携

上記のレビューフローを実現するためにはSlack上での投稿をトリガーとして処理を開始し、結果をSlack上に投稿する仕組みが必要です。

このためにSlack Appを作成し、Botとして動作させることにしました。開発にはSlack公式のPython向けフレームワークである「Slack Bolt for Python」を使用することにしました。

Slack Boltではリクエスト署名の検証といったセキュリティ対策がデフォルトで有効になっている点も大きなメリットでした。

Confluenceとの連携方法

Botがレビューをするには、まずConfluenceから設計書の内容の取得が必要です。

ここでポイントとなったのが、弊社が利用しているConfluenceがServer版である点です。Cloud版であればMCPのようなモダンな連携方法も考えられますが、Server版では利用できません。さらに、将来的にはCloud版へ移行するという話もあったため、移行後も動き続ける仕組みが必要でした。

この状況から、技術選定では以下の2点を重視しました。

  • Cloud版とServer版の両方に対応できること
  • 最低限の機能として、テーブル設計の記載されたページが取得できること

この方針のもと、最終的にConfluence REST APIを直接利用するシンプルな方法を選びました。実装には、両方のバージョンをサポートしているライブラリ「Atlassian Python API」を活用しています。

LLMモデルの選定

私たちのインフラはAWSをメインで利用していること、セキュリティの観点からモデルの学習にデータが使われないようオプトアウト設定も可能なことから、実行環境にはAWS Bedrockを選択しました。

Bedrockでは多くのモデルが利用できますが、以下の3つのモデルを候補としました。

Titan Text G1 - Express Claude 3.5 Sonnet Claude 3 Haiku
開発元 Amazon Anthropic Anthropic
モデルの位置づけ 速度とコスト効率を重視した汎用モデル 知能、速度、コストのバランスに優れた主力モデル 最速・最軽量・低コストなエントリーモデル
入力トークン 1,000 個あたりの価格 USD 0.000275 USD 0.003 USD 0.00025
出力トークン 1,000 個あたりの価格 USD 0.000825 USD 0.015 USD 0.00125
レビュー精度 ×
検証結果 指摘すべき点を見逃した ガイドラインに沿った指摘ができた 一部、誤った指摘があった

モデルのレビュー精度を確かめるため、意図的にアンチパターンを含んだテーブル定義と設計ガイドラインを渡し、各モデルにレビューさせてみました。

結果は表の通り、Anthropic Claude 3.5 Sonnetが最も的確な指摘をしてくれました。価格は3つの中で最も高価ですが、Botの利用頻度は1日数回程度です。そのため、コストは数円から数十円の範囲に収まり、精度の高さを考えれば十分に許容できると判断しました。現在はその後継である最新版のSonnetモデルを利用しています。

AWS Bedrockのナレッジベースの使用是非

Botの回答精度を高めるには、私たちのDB設計用の開発ガイドラインをLLMに伝える必要があります。その方法として、AWS Bedrockの「ナレッジベース」機能の利用を検討しました。

これは、S3などにあるドキュメントを読み込ませるだけで、簡単にRAG(検索拡張生成)を実現できる便利な機能です。

しかし、調査を進めるとコスト面が課題となることがわかりました。ナレッジベースの裏側では、ベクトルデータベースとしてAmazon OpenSearch Serviceが利用されます。これにより、最低でも月数万円の固定費が発生する見込みとなりました。

今回Botに与えたい知識は「開発ガイドライン」の、DB項目のさらに一部だけです。データ量はそこまで多くなく、更新頻度も高くありません。このためだけに常時稼働のデータベースを用意するのは、費用対効果が見合わないと判断しました。

結果として、よりシンプルに必要なガイドライン情報を、都度プロンプトに直接埋め込む方式を採用しました。シンプルながら、私たちのユースケースにはこれが最適なアプローチでした。

システムの全体構成

上記を踏まえ、以下のようなアーキテクチャとしました。

アーキテクチャ

Confluenceから取得したデータはそのままだとHTML形式の文字列のため、これをパースして情報を一度まとめてからJSON形式に変換しています。

パース部分の詳細抜粋

この変換はレビューの精度を向上させるために行っています。検証の結果、HTMLをそのまま渡すよりも、整形済みのデータを渡すことで、モデルが意図した情報を正確に理解しやすくなりました。

また、パースには当初PythonのライブラリのBeautiful Soupを使用する考えでしたが、自由な構造に対応できるように、ここでもBedrockを使用することとしました。

AWSリソースについて

AWSリソースはCloudFormationで管理しており、主に以下のリソースを作成しています。

  • S3 Bucket
    • ソースコードを格納するためのバケット
  • API Gateway
    • Slackからのリクエストを受け取るためのエンドポイント
  • AWS Lambda
    • Pythonで実装したSlackアプリケーションを実行するためのLambda関数
  • Secrets Manager
    • 各種トークン情報を安全に管理するためのシークレット
  • Inference Profile
    • Bedrockでモデルを利用する際に使用するプロファイル

Bedrockアクセス用のInference Profileについて

AWS Bedrockでは、有効化した時点でそのモデルを利用可能となります。ただし、デフォルトの推論プロファイルを使用すると、どのアプリケーション・ユーザーがコストを発生させたのかを追跡できません。これにより、コストの配分や分析が難しくなります。

そのため次のようなコードでコスト配分タグを付与したアプリケーション推論プロファイルを作成して使用することにしました。

Resources:
  InferenceProfileDBDesignReviewBot:
    Type: AWS::Bedrock::ApplicationInferenceProfile
    Properties:
      InferenceProfileName: !Sub "${AppNameDBDesignReviewBot}-inference-profile"
      ModelSource:
        CopyFrom: !Sub "arn:aws:bedrock:ap-northeast-1:${AWS::AccountId}:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0"
      Tags:
        - Key: Name
          Value: !Sub "${AppNameDBDesignReviewBot}-inference-profile"
        - Key: CostEnv
          Value: !Ref CostEnv
        - Key: CostTeam
          Value: !Ref CostTeam

Lambda上で動かすPythonコードの実装について

ここからは、このBotを実現しているAWS LambdaのPythonコードについて、ポイントを絞って紹介します。

  • Pythonと主要ライブラリのバージョン
    • Python : 3.13
    • atlassian-python-api : 4.0.4
    • slack_bolt : 1.23.0

重複実行対策

SlackからのリクエストをLambdaで処理する際、注意したいのがタイムアウトによるリトライです。AWS Lambdaのコールドスタートなどで処理に3秒以上かかると、Slackは「応答がない」と判断し、同じリクエストを再送してきます。これにより意図せず処理が重複して実行される可能性があります。

そのため、リトライを表すヘッダーが付与されている場合はSkipすることで重複処理を回避するようにしました。

def lambda_handler(event, context):
    """
    Lambda Functionのハンドラーに設定する関数
    """

    headers = event.get("headers", {})
    if "X-Slack-Retry-Reason" in headers:
        logger.info("Slackリトライリクエストを無視します")
        return {
            "statusCode": 200,
            "body": ""
        }
    return slack_request_handler.handle(event, context)

Bedrockの呼び出しコード

AWSが提供しているBedrockのAPI呼び出しの方法として、「InvokeModel」と「Converse API」の2種類が存在しています。

docs.aws.amazon.com

両者の大きな違いの1つとして、柔軟性の違いがあります。実際にテストコードを基にした例が次の画像です。InvokeModelを使用した場合は、リクエストボディやレスポンスの解析をモデルごとに行う必要があります。

InvokeModelでモデルが異なる場合の実装例

一方、Converse APIを使用するとこれが共通のコードで実装できるようになります。

ConverseAPIでモデルが異なる場合の実装例

LLMの進化は速いため、将来のモデル変更に備えて、柔軟性の高いConverse APIを選択しました。

Slackのイベントに応じた処理の実装

Slackで発生する様々なイベントに応じた処理は、Boltのデコレータを使って実装します。例えば、Botへのメンションをきっかけに入力フォームを開く処理は、以下のコードで実現しています。

@app.event("app_mention")
def handle_app_mentions(body, say):
    thread_ts = body.get("event", {}).get("thread_ts") or body.get("event", {}).get("ts")
    send_review_request_message(say, thread_ts)


def send_review_request_message(say, thread_ts):
    """
    自動レビューに必要な情報を送信するためのフォームをスレッドに送信します。
    """
    say(
        blocks=[
            {
                "type": "header",
                "text": {
                    "type": "plain_text",
                    "text": "DB設計自動レビュー依頼",
                }
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"お疲れ様です。DB設計自動レビューBotです。\n設計書の自動レビューを行いたい場合、以下を入力して送信ボタンを押してください。\n\n 利用マニュアル: <{BOT_MANUAL_URL}|{BOT_MANUAL_TITLE}> \n *注意: テンプレートに沿ったConfluenceのURLのみレビュー可能です。* \n"
                }
            },
...

上記コードによって、前述の自動レビューの全体像のこの部分が実行されます。 Slackでイベントに応じた入力フォームの送信

Slack上でのメッセージの見た目を整え、入力欄やボタンの配置には、Slack公式のBlock kit Builderを活用しています。

Bedrockに渡すプロンプト

レビュー時に使用するプロンプトは以下のように定義しています。Confluenceから取得した「テーブル設計情報」と、「開発ガイドライン」をこのプロンプト内に動的に埋め込めるようにしました。なお、プロンプトエンジニアリングについての知見がまだ少ない状態での実装のため、あくまで一例としてご覧ください。

    prompt_data = f"""
    あなたはデータベースのテーブル設計の専門家です。
    テーブル設計情報と、開発ガイドラインの情報を渡しますのでレビューしてください。
    ガイドラインに沿っていない項目に関しては「指摘箇所」として、
    注意事項リストに該当する項目の場合は「注意箇所」として、
    その他のコメントがあれば「その他」として出力してください。
    出力フォーマットは以下の通りです。
    ```
    提供されたテーブル設計情報に基づいて、ガイドラインに沿ってレビューを行いました。

    ーーーーーーーーーーーーーーーーーーー
    :rotating_light:指摘箇所:rotating_light:
    【1】<指摘対象>
    ・ 指摘内容: <指摘内容>
    ・ ガイドラインの対応項目: <ガイドラインの対応項目>
    ・ 修正案: <修正案>
    2. <指摘対象>
    ...
    ーーーーーーーーーーーーーーーーーーー
    :warning:注意箇所:warning:
    【1】<注意内容>
    ・注意理由: <注意理由>
    【2】<注意内容>
    ーーーーーーーーーーーーーーーーーーー
    :memo:その他:memo:
    ・ <その他のコメント>

    ```
    指摘内容がある場合の出力例
    ```
    :rotating_light:指摘箇所:rotating_light:
    【1】テーブル名: Member
    ・ 指摘内容: テーブル名が単数形になっている
    ・ ガイドラインの対応項目: テーブルおよびカラムにプレフィックスをつけず、テーブル名は複数形にする(MUST)
    ・ 修正案: テーブル名をMembersに変更する
    【2】カラム名: MemberID
    ...
    ```

    指摘内容がない場合の出力例
    ```
    :rotating_light:指摘箇所:rotating_light:
    特に指摘箇所はありませんでした。
    ```

    テーブル設計情報は次のようにJSON形式で提供されます。

    テーブル設計情報: {table_design_json}

    ガイドラインは以下の通りです。
    {BASE_GUIDELINES}
    {guidelines_by_db_engine}

    注意事項リストは以下の通りです。
    {WARNING_LIST}

現在弊社でレビュー依頼のあるDBは、「SQL Server」と「MySQL」の2種類が多くを占めていますが、それぞれでガイドラインに違いがあります。そのためSlack上での入力フォームでどちらか選択できるようにし、選択されたDBに応じたガイドラインが使われるようにしています。

また、これまでレビュー時に注意喚起を行なっていた項目なども、注意リストとして定義し、プロンプトへと埋め込むようにしました。例えば秘密情報が含まれたカラムを追加する場合には、運用フローに則って別途対応が必要となるため、そちらの対応を促すようにしています。

ガイドラインや注意リストを渡すだけでもレビューはできますが、それだけでは指示された項目しかチェックできません。そこで、プロンプトの冒頭で「あなたはデータベースのテーブル設計の専門家です」という役割を設定しています。これにより、私たちが明示的に定義していない事柄についても、LLMが持つ知識を活かし、プラスアルファの指摘をしてくれます。

リリース後の効果

レビュー時の指摘件数の減少

リリース前後10件のレビューにおける、レビュアーからの指摘件数を比較した結果が以下のグラフです。

レビューBot導入による効果

Botにより必要な指摘の約7割が自動で行われるようになり、レビュアーの負荷が軽減されました。

ガイドラインには「ローマ字表記や省略形の英単語は使わず、完全な英単語を使用する」という項目もあり、見落としがちなスペルミスなども自動的に拾われて修正されるなど嬉しい効果もありました。

リリース後のコスト

運用開始後のコストを、以下にまとめました。

コスト

レビューBotを動かす環境は、「開発者からのレビュー依頼を受ける環境」と、「Botの改修時に使用する環境」として2環境を用意しています。当初想定していなかった規模の大きなテーブルがレビュー依頼となり、改修とリトライを繰り返したことで最初の2日間は他の日と比べてコストが高くなっています。その後は安定しており、1日あたり数十セント程度のコストで運用できています。

AWSのサービスで見た場合には、次のようになっています。Secrets Managerへの保存によって若干のコストがかかっている他は、ほとんどがBedrockのコストです。

サービス毎のコスト

約1か月運用してかかった実際のコストとしては約7.62ドルであり、コストを大きく抑えて運用できました。

まとめと展望

本記事では、長年手動で対応していたDB設計レビューのフローに自動化を組み込んだ事例について紹介しました。

Slackを起点としてPythonのアプリを動かし、Confluenceからテーブル設計情報を読み取り、Bedrockを用いて開発ガイドラインに基づいて自動レビューする仕組みとしています。

今回の仕組みのターゲットは設計書のレビューでしたが、その設計書通りに実装できているかプルリクエストに対してレビューする仕組みなどにも応用は効きそうです。LLMを活用することによる運用負荷軽減を実現するための取り組みは引き続き進めていきたいと思います。

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

hrmos.co

カテゴリー