Using GitLab API to create a DORA metrics dashboard

Alex O'Callaghan

GitLab APIを使ってDORAメトリクスのダッシュボードを作る

原文は Alex O'Callaghan により に公開されました。 このブログを購読する

ソフトウェア開発チームの生産性を測るのは困難です。メトリクスを計測し、それを評価に使い始めると、意図せぬ形で行動に影響を与えてしまうことがあります。意図せぬ影響を与える形で行動に影響を与えかねません。Jiraのようなプロジェクト管理ツールに代表される、ソフトウェアデリバリーを代理的に表すものに頼ると、成果を改善することなくメトリクスだけを良く見せようとする行動を助長しかねません。たとえば、ストーリーポイントの見積もりを水増ししたり、チケットをクローズするタイミングを操作したりといった行動です。

グッドハートの法則

大手テック企業がどのように生産性を計測しているかについてのPragmatic Engineerの記事に触発され、Jiraのワークフローから得られるメトリクスのみに頼った生産性評価から、実際にプロジェクトをどのように変更し、デプロイしているかという現実に基づいた評価へ移行できないか模索してみました。GitLab APIを使って、Mintelのチーム向けにデプロイ頻度と変更のリードタイムを追跡するダッシュボードを作成することができました。

何を計測するか

現在、エンジニアリングの生産性目標として主に使っている指標はサイクルタイムです。これは、チケットがJiraのワークフローで「進行中」から「完了」へ進むまでにかかる時間のことです。すべてのエンジニアリングチームがJiraを使っているため、このデータはすでに手元にあります。Jiraのレポート機能を使えば、このデータを追跡・可視化するためのさまざまなレポートを作成できます。

しかし、これらのメトリクスは実際に起きていることを代理的に表したものに過ぎず、実際のコードの変更やリリースとは手作業で同期を保っている(あるいは保てていない)状態です。2020年にDevOps Research and Assessment(DORA)は、ソフトウェア開発チームのパフォーマンスを示す4つの主要メトリクスを特定しました

  • デプロイ頻度 - 組織が本番環境へどれくらいの頻度で正常にリリースしているか
  • 変更のリードタイム - コミットが本番環境に反映されるまでにかかる時間
  • 変更失敗率 - 本番環境で障害を引き起こしたデプロイの割合
  • サービス復旧時間 - 組織が本番環境の障害から復旧するまでにかかる時間

デプロイ頻度変更のリードタイムは、コードベースのワークフローから比較的容易に導き出すことができ、生産性の指標になりうると感じました。

デプロイ頻度

デプロイ頻度を把握する最初のステップは、デプロイが実行されたことを示す手がかりを見つけることでした。GitLabにはデプロイ機能があり、最初から用意されているDORAメトリクスのダッシュボードと連携できます。これで解決です!

ただ、私たちのプロジェクトはそのデプロイ機能を使っていませんでした。チームに余計な作業を強いるのではなく、すぐに役立つデータを得たいと考えました。

幸い、ほとんどのプロジェクトでは共通のGitLab CIテンプレートを使っており、mainブランチにタグをプッシュすることでデプロイがトリガーされるという共通のワークフローがありました。これらのタグは共通のSemVer形式(vX.X.X)に沿っており、プロジェクトのすべてのタグを取得するためのGitLab APIも存在します。

チームが所有する各プロジェクトのタグを取得するシンプルなスクリプトをTypeScriptで書きました。

import { Gitlab } from "@gitbeaker/rest";

const api = new Gitlab({
  token: process.env.GITLAB_TOKEN,
});

const { id, name } = project;

const tags = await api.Tags.all(id);

// Filter to just release tags
const releaseTags = tags.filter((tag) => tag.name.match(/^v\d+\.\d+\.\d+$/));

GitLab APIのリクエストには、使いやすいGitLab SDKであるgitbeakerを使いました。そしてcsv-stringifyを使ってリリース情報をCSVファイルに書き出しました。

そこからCSVデータをGoogleスプレッドシートにインポートし、いくつか可視化を作成できました。

週ごとのデプロイ数を示すグラフ

このアプローチでは、デプロイパイプラインの完了にかかる時間やデプロイが成功したかどうかまでは考慮していませんが、デプロイ頻度の傾向を掴むには十分な出発点になりました。

変更のリードタイム

変更のリードタイムの算出は、もう少し手がかかりました。変更のリードタイムとは、変更がmainブランチにコミットされてから本番環境にデプロイされるまでにかかる時間です。私たちのプロジェクトでは、変更はマージリクエストを通じてmainにマージしており、プロジェクトのマージリクエストを取得するためのGitLab APIが用意されています。

マージされた各変更について、変更のリードタイムを次のように計算しました。

Lead time = Time of release - Time of merge

そのためには、各マージリクエストを、それが含まれたリリースと紐付ける必要がありました。そこで各マージリクエストについて、merged_atの時刻より後に行われた最も早いリリースを探すようにしました。

import moment from "moment";

const { id, name } = project;

// Get all merged merge requests in descending time order
const merges = await api.MergeRequests.all({
  projectId: id,
  state: "merged",
  sort: "desc",
});
// Get all releases in ascending time order
const tags = await api.Tags.all(id, {
  orderBy: "updated",
  sort: "asc",
});
const releaseTags = tags.filter((tag) => tag.name.match(/^v\d+\.\d+\.\d+$/));

const leadTimes = [];

merges.forEach((merge) => {
  if (merge.merged_at) {
    // Find the next release
    const mergeTime = moment(merge.merged_at);
    const release = releaseTags.find((tag) => {
      return moment(tag.commit.committed_date).utc().isAfter(mergeTime);
    });
    if (release && release.commit.committed_date) {
      // Get the time (in hours) between merge and release
      const leadTime = moment
        .duration(moment(release.commit.comitted_date).utc().diff(mergeTime))
        .asHours();
      leadTimes.push({
        project: name,
        title: merge.title,
        merged_at: merge.merged_at,
        released_at: release.commit.committed_date,
        lead_time: leadTime,
      });
    }
  }
});

そこから各変更のリードタイムをまとめた別のCSVファイルを作成し、一定期間における変更リードタイムの平均を算出できるようになりました。

定期的に更新されるダッシュボード

このデータを何人かのエンジニアリングマネージャーと共有したところ、過去のデータから有用なインサイトが得られましたが、これらのメトリクスをスプリントの振り返りに取り入れ、経時的な変化を定期的に確認することにも価値があると感じました。手作業でスクリプトを実行してCSVから可視化を作る作業をずっと担い続けたくはなかったですし、フルスケールのサービスやデータベースをデプロイせずに、素早く動くものを作りたいとも思っていました。

そこでReactViteTypeScriptshadcn/uiを使って手早くダッシュボードを作ることにしました。スクリプトを修正してGitLabのJSONレスポンスをpublicディレクトリ内のファイルに書き出すようにし、ビルド時に一度だけデータを取得して、メトリクスの計算はダッシュボード側でクライアントサイドで行うようにしました。変更とリリースの紐付けやリードタイムの計算には時間がかかるため、その処理はビルド時のスクリプト側に残しました。

そしてスケジュールされたGitLabパイプラインを設定し、毎日最新のデータを取得してサイトを再ビルドし、GitLab Pagesでデプロイするようにしました。

こうして、チームリードが活用できる、これらのメトリクスを表示するダッシュボードをすぐに用意できました。

生産性メトリクスのダッシュボード

どのように活用しているかと今後の改善

現時点では、このダッシュボードを4つの異なるエンジニアリングチームに展開し、チームの振り返りにどう役立てられるかを模索しているところです。チームの働き方についての議論や、変更がもたらす影響の評価において、より客観的な土台を提供し始めています。

このダッシュボードが、チーム外のステークホルダーとのコミュニケーションや、開発者体験の改善が生産性に与える影響を示すためのツールにもなればと考えています。

引き続きより多くのチームへの導入を進めながら、これらのメトリクスをどう活用するのが最も効果的か、また他に有益な示唆をもたらすメトリクスはないかを理解しようとしています。長期的には、このカスタムソリューションはそれほどスケーラブルでも保守しやすいものでもありません。価値を発揮できると分かれば、GitLab deploymentsやDXAtlassian Compassなど、同様のダッシュボードを提供してくれるプラットフォームの導入を検討する予定です。

もちろん、定量化できるデータがすべてではありません。現在のツールやプロセスに対してエンジニアがどれだけ生産的だと感じているかをより深く理解するため、部門全体での開発者満足度調査の実施も計画しています。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント