TalentX Tech Blog

Tech Blog

デプロイ頻度を改善した話

はじめまして!バックエンドエンジニアの中山です。 今回はMyReferチームでFour Keysの1つであるデプロイ頻度を改善した話をご紹介いたします。

Four Keysとは

Four Keysとは、Google社が提唱するソフトウェア開発チームの生産性を測る指標であり、以下の4つの要素で構成されています。 cloud.google.com

デプロイの頻度: 組織による正常な本番環境へのリリースの頻度

変更のリードタイム: Commitから本番環境稼働までの所要時間

変更失敗率: デプロイが原因で本番環境で障害が発生する割合(%)

サービス復元時間: 組織が本番環境での障害から回復するのにかかる時間

Four Keysの導入

CTO籔下の記事の通り、TalentXは事業の成長に伴ってエンジニアの人数もここ数年で3〜4倍になりMyReferチーム、MyTalentチーム、Myシリーズの管理画面開発チームといった複数のチームに分かれるまでに成長を続けてきました。組織の規模は年々大きくなっていく一方、弊社のValueの1つであるGale(疾くあれ)に基づき、これまで以上に疾く安定した品質で市場に価値を届けていきたいと考えています。

自分たちが開発したものをいかに「疾く」、またいかに「障害を起こすことなく」市場に届けられているか。これらを客観的な指標として可視化し継続的に改善していきたいため、今年の4月からFour Keysの測定を開始しました。

この記事では主にFour Keysの1つであるデプロイ頻度にフォーカスし、MyReferチームがいかに改善をしていったかについてご紹介します。

改善前のデプロイ頻度

改善前のMyReferチームのデプロイ頻度は 5回 / 月でした。これは週に1回の頻度での定常リリースを行ってきたためで、開発した複数のPRをまとめてリリースするフローとなっていました。しかし、このリリースフローには後述の運用上の課題があるため、まずはこれを最低でも8回 / 月(週に2回)に増やすことを目標に定め改善に取り組みました。

なぜデプロイ頻度を改善するのか?

改めて考えてみるとなぜデプロイ頻度を改善する必要があるのでしょうか?

無論、自分たちが開発したものをよりスピーディーにリリースし市場に価値を届けていきたいという目的が第一にあります。それに加えて多くのPRを1度にまとめてリリースすることによって発生する以下の運用上の課題を解消したいというのも大きな狙いの一つです。

  • デプロイする前に想定していない変更がリリース用のブランチに含まれていないかの確認が必要。実際、Gitのコミット履歴とタスクを管理するチケットとの突き合わせを都度行っていたため確認に時間がかかっていた

  • テストフェーズにて、あるPRのバグ修正に時間がかかる場合、それに伴って他のPRのリリースも後ろ倒しになっていく

  • バグが発生したときの原因の切り分けが難しい

  • リリースした結果、障害が発生してすぐにでも切り戻しを行いたい場合に他のPRも切り戻さなければならない。特にRDBのスキーマにテーブル削除やカラム削除等の後方互換性のない変更が含まれていると切り戻し自体が困難になる

上記の課題はリリース対象が多ければ多いほど猛威をふるいます。逆により小さい粒度でこまめにリリースするようにすれば影響をより限定することができるため、まずは1PRにつき1回のデプロイを目指して改善を進めることにしました。

とにかくまずは自動化

よし、早速明日から毎日デプロイしていこう!と思っても、そういうわけにはいきません。MyReferチームが週一でのデプロイをしてきたのにはもちろん理由があります。それは、デプロイ作業が「大変で時間が掛かるから」です。

リリース対象の確認から始まり、手作業を含んだデプロイ作業、デプロイ後のリリースノートの作成まで含めると、毎回1時間ほどの時間を要していました。

作業の負荷を軽減しない状態で毎日デプロイを強行するとむしろ生産性を下げてしまうため、ファーストステップとして以下の技術的改善を行うことにしました。

  • デプロイの自動化
  • デプロイの並列実行
  • リリースノートの自動生成
  • デプロイ頻度と変更のリードタイムの自動集計

デプロイの自動化

デプロイ作業は概ね自動化されていたものの、一部、手動で行われている作業がありました。

  • cronで稼働しているバッチサーバーの更新のために一時的にcronを停止する作業
  • デプロイ後にCDNのキャッシュを削除する作業

これらの作業により、単純にデプロイのワークフローを起動して後は待つだけ...というわけにはいきませんでした。また、本番環境に影響を与えかねない作業のため、デプロイに対する心理的ハードルも高まります。

よって1ボタンでデプロイ完了をコンセプトに、デプロイの中で発生する全ての手作業を洗い出し、それら全てを自動化しました。

デプロイの並列実行

次はデプロイの待ち時間の短縮です。 デプロイのワークフローではアプリケーションサーバーだけでなく、バッチサーバーなど複数のサーバーをデプロイする必要があります。 以前は、バッチサーバーのデプロイが完了してからアプリケーションサーバーのデプロイを行っていたため、デプロイの実行だけで合計40分程度の時間がかかっていました。

当社ではGitHub ActionsでCI/CDを行っています。 GitHub Actionsではジョブを並列に実行できるため、以下のように複数のコンテナイメージのビルドを同時に行うワークフローを新たに導入しました。

name: deploy

jobs:
  build-api-container-image:
    runs-on: ubuntu-latest
    steps:
    ...

  build-batch-container-image:
    runs-on: ubuntu-latest
    steps:
    ...

  deploy:
    runs-on: ubuntu-latest
    needs: [build-api-container-image, build-batch-container-image]
    steps:
    ...

これにより、最終的には半分程度の時間でデプロイが完了するようになりました。

リリースノートの自動生成

加えてリリースノートの生成も自動化しました。 デプロイが完了したタイミングでGithubのCreate a release APIをコールしリリースノートを自動生成するようにしました。リリースノートのテンプレートはGithubのリリースノート自動生成機能で設定可能なテンプレートを使うことにしました。 .github配下に以下のようなrelease.yamlファイルを配置し、PR作成時にラベルを付与することでラベルに応じたPRのカテゴライズを自動で行ってくれます。

changelog:
  exclude:
    labels:
      - release
  categories:
    - title: '🚀 Features'
      labels:
        - 'feature'
    - title: '💡 Improvements'
      labels:
        - 'improvement'
    - title: '🐛 Bug Fixes'
      labels:
        - 'bug'
    - title: '💼 Tasks'
      labels:
        - 'task'
        - 'chore'
    - title: 'Others'
      labels:
        - '*'

このようなちょっとした自動化でも、リリース手順がより簡素化され、開発メンバーのデプロイに対する心理的ハードルが下がることが期待されます。

デプロイ頻度と変更のリードタイムの自動集計

更に毎週のレトロスペクティブでFour Keysの振り返りを行うために、デプロイ頻度と変更のリードタイムの集計を自動化しています。 リリースノートの公開をトリガーにしてGithub Actionsを起動しGoogle Apps Script(GAS)で構築したAPIを呼び出すことで、スプレッドシートにデータを記録する仕組みです。

Four Keysの集計

デプロイ頻度の更新に関してはシンプルにデプロイ回数をインクリメントしていますが、変更のリードタイムの集計は今回リリースしたタグと、前回リリースしたタグとの間に生まれたPRの差分を登録しています。

差分の抽出にはGithubのGraphQL APIのComparisonを使用しており、以下のようなクエリを実行することで2つのタグのコミットの差分とそれに紐づくPRを取得できます。

  query($owner:String!, $repository:String!, $baseTag:String!, $headTag:String!, $endCursor:String) {
    repository(owner: $owner, name: $repository) {
        ref(qualifiedName: $baseTag) {
          compare(headRef: $headTag) {
            commits(first: 100, after: $endCursor) {
              nodes{
                message
                associatedPullRequests(first: 1) {
                  nodes {
                    number
                    title
                    mergedAt
                    timelineItems(first: 1, itemTypes: READY_FOR_REVIEW_EVENT) {
                      nodes {
                        ... on ReadyForReviewEvent {
                          createdAt
                        }
                      }
                    }
                  }
                }
              }
              totalCount
              pageInfo {
                hasNextPage
                endCursor
              }
            }
          }
        }
      }
    }

弊社では、レビュー、テスト、デプロイが迅速に行われているかをより重点的に評価するために、変更のリードタイムの定義を「PRがオープンになってから本番環境にリリースされるまでの日数」としています。

PRにはドラフトとオープンの2つのステータスがありますが、PRがドラフトからオープンになった時刻はassociatedPullRequestsオブジェクトからは取得できないため、PullRequestTimelineItemsReadyForReviewEventオブジェクトから取得しています。

上記のクエリによってコミットの差分およびそれに紐づくPRの一覧が取得できるため、後はGASのコード上でPRがユニークになるように整形し、以下のようにPRごとのリードタイムをスプレッドシートに記録しています。

リードタイムの集計

改善結果

上記の改善を行うことで1時間程度かかっていた作業が最終的に20分程度に短縮され、デプロイを開始したら後は待つだけの状態になり、作業の負荷を大幅に下げることができました。

リリースフローを整備する

自動化が完了したらリリースフローの設計に入ります。まずリリースを「どのタイミングで」「誰が」行うかを以下のように定めました。

Before After
どのタイミングで 週に1回 リリース可能なものがあれば毎日
誰が リリース担当者(持ち回り) 実装者

これまでリリース担当者は持ち回りで決められていましたが、実装者が最後まで実装したものに責任を持つという意味で実装者が自ら行うように変更しました。 ※ただし、デプロイ可能なものが複数ある場合はリリース担当者を別途決定しています。

次は品質との両立です。デプロイを急ぐあまり品質が疎かになってしまっては改悪となってしまうため、デプロイ頻度を上げながら品質を維持するための方針についてチームメンバーと議論し以下を取り入れることにしました。

小さなPRを作る

これまでPRの大きさに明確な決まりはありませんでしたが、Four Keys導入をきっかけに、より小さなPRを高速に回すことを意識し以下のルールを設けました。

  • ファイルの差分は極力10ファイル以内に収めること。※ただし、1つのファイルの修正量が多い場合もあるため、将来的には変更したコードの行数を指標としていく方針です。

  • 20ファイルを超えたらPRを分けること。(一括置換系の実装は除く)

  • レビュワーは原則24H以内にレビューを行うこと。難しい場合はその旨をレビューイに伝えること。

巨大なPRはレビューの負荷が高く、バグの発見が難しくなる傾向があります。コードの詳細な確認には時間がかかり、レビューの終盤では「とりあえず動いていそうだからApproveしとくか...」という心理が働きやすくなります。 現に、Four Keys導入前に発生した障害の多くは巨大なPRに起因するものでした。 逆に、PRが小さいとレビュワーの負荷が軽減され、レビューの質と速度が向上します。その結果、デプロイの頻度だけでなく、変更のリードタイムと変更障害率の改善も期待できます。

リリースフロー

以下が最終的な実装からリリースまでのフローです。

フロー
1 実装者がfeatureブランチを作成し実装を行う
2 実装者がPRを作成しレビュー依頼する
3 承認されたらdevelopブランチにマージする
4 リリース対象とテスト内容を確認してテスターを割り当てる
5 developブランチからリリース用のブランチを作成しテスト環境にデプロイする
6 テストを実施する
7 リリースブランチをmainブランチにマージするためのPRを作成してレビュー依頼
8 承認後、本番環境にデプロイ

改善結果 〜脱・定常リリース〜

新しいリリースフローをチームに導入し、実際にその運用を開始しました。初めは混乱も見られましたが、週次のレトロスペクティブを通じてチームメンバーと一緒に振り返りと改善を積み重ねた結果、運用開始から3ヶ月間のデプロイ頻度は平均9.3回 / 月に達し、目標として掲げていた8.0回/月を達成することができました。変更失敗率は0%であり、品質との両立もできています。

まとめ

デプロイ頻度の改善を通じて、チームがこれまで抱えていた定常リリースという概念を消し去ることができました。新しいリリースフローの導入により、それぞれのメンバーが主体的にデプロイを行える状態になりました。

今後の展望

目標は達成したものの、期待していたほどデプロイ頻度が伸びなかったというのが本音です。これは、運用改善系のタスクでは新しいリリースフローが効果的であった一方で、大型の新規機能開発においてはビッグバンリリースとなりがちであったためです。今後は大規模プロジェクトでもリリースの粒度を適切に見極め、こまめなリリースを実現するためのフローをより整備していきたいと考えています。

また、ブランチ運用上の課題もあります。PRが承認されたらfeatureブランチを一度developブランチにプールしていく運用のため、developブランチから作成したリリース用のブランチには複数のPRによる変更が含まれている可能性があります。そのため、理想とする1PRにつき1回のデプロイを実施できず、他のPRのテストの進捗に影響される場面が多く発生しました。変更のリードタイムにも影響を与えるため、チームメンバーと一緒に議論を重ねて改善していく予定です。

最後に

長文にも関わらずここまで読んでいただきありがとうございました。TalentXでは一緒に働く仲間を募集しております。 カジュアル面談も行っていますので気になる方はぜひご応募いただければ幸いです! i-myrefer.jp