⏱️

アラート・問い合わせの初動対応までの時間を計測してみた(Jira、New Relic Event API、GAS)

2024/07/08に公開

はじめに

こんにちは!私は現在のプロジェクトでは、主にフロントエンドの開発に従事しています。

私のチームでは日々システムの品質向上や運用改善に向けた取り組みをおこなっています。特にアラート・問い合わせの初動対応までの時間は目標を決めて達成するためにチームで改善に取り組んでいます。

しかし、目標はあるものの定量的に計測できておらず、「このままだと目標を達成するのが厳しそうだから改善しよう」、「まだまだ余裕だから目標を引き上げよう」といった次のアクションがすぐにできないことが課題でした。

そこで、ダッシュボード上に計測した数値を表示して、チームで共通の認識を持って次のアクションを決められることを目指しました。

この記事では、運用データの収集と可視化のために行ったチームでの取り組みや、JiraとGoogle App Script(GAS)とNew Relicを活用したシステムの構築方法について紹介します。

出来上がったもの

システム構成

私のプロジェクトでは障害の管理をJiraでおこなっており、APIを通じてJiraからデータを取得することにしました。

運用のデータは非開発者やインフラに詳しくない開発者も管理できることを想定しました。なので例えばGoogle Cloud PlatformのCloud FunctionsやCloud Schedulerを利用した構成と比べて、GUIを通じたスクリプトの作成・編集・実行ができて、特別なインフラ管理や設定が不要なGASを採用しました。

データの格納・閲覧にNew Relicを採用したのは、私のプロジェクトではNew Relic利用してAPMのデータをダッシュボード上に表示しています。運用のデータも同じダッシュボードに表示することで、複数のツールを利用する必要がなくなり、運用の手間や学習コストも低減すると考えました。さらにアラートも設定できるので、目標値を下回りそうな場合対応の遅れを防げそうです。

やったこと

その1:初動時間を定義する

「平日アラート・問い合わせの初動対応までの時間」の目標は決まってはいるものの、「初動対応まで」の認識がチーム内で齟齬がある状態でした。なのでメンバー間で認識を合わせ定義することにしました。

私たちのプロジェクトで初動対応の定義は、「アラート・問い合わせがSlackに投稿された時間から障害対応のスレッドを作成するまで」としました。アラートの検知やユーザーとのやりとりや開発者のコミュニケーションはSlackを通して行われているので計測したい数値はSlackの投稿時間見れば取得できそうです。

その2:アラート・問い合わせ対応を標準化

初動時間を定義したものの、アラート・問い合わせ対応を細かく定めていませんでした。その為、メンバーによっては、障害対応のスレッドを立てる前に原因調査などを始めてしまい、定義した初動時間をうまく計測できない可能性がありました[1]。なのでアラート・問い合わせ対応を標準化し、メンバーが同じ手順で対応をできるようにしました。

その3:Jiraから更新ログを取得

GAS上でJiraのAPIを叩き更新ログを取得します。コードのイメージは以下です。

function fetchJiraIssues() {
  var url = buildSafeUrl()
  var options = {
    'method': 'get',
    'headers': {
      'Authorization': 'Basic ' + Utilities.base64Encode(`${email}:${jiraApiKey}`),
      'Content-Type': 'application/json'
    }
  };
  
  try {
    var response = UrlFetchApp.fetch(url, options);
    var json = response.getContentText();
    var data = JSON.parse(json);
    return data; // 取得したデータを返す
  } catch (e) {
    Logger.log('Error: ' + e.message);
    return null; // エラーの場合はnullを返す
  }
}

function buildSafeUrl() {
  var baseUrl = 'https://buysell-tech.atlassian.net/rest/api/2/search';
  var params = {
    jql: `project = "${YOUR_PROJECT_ID}" AND updated > startOfDay("-1")`,//1日1回定期実行するので、1日前までの更新データを取得します。
    fields: 'customfield_102,customfield_101,customfield_103,summary'
  };

  var query = Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
  }).join('&');

  var url = baseUrl + '?' + query;
  Logger.log(url); // 結果をログに出力
  return url;
}

ここでのポイントは、JQLをクエリパラメータに含めることで柔軟な検索ができることです。例えば、Jiraのプロジェクト内で複数のプロダクトの障害を扱っていて特定のプロダクトのデータのみ取得したい場合、カスタムフィールドで識別できるプロパティがあれば欲しいデータのみフィルタすることができます。

詳しいJQLの仕様はこちらでご確認できます。

その4:App Scriptでデータ加工

ここでは、New Relicにデータを送信する際に必要なプロパティを追加したり、NRQL(New Relic Query Language)で扱うことが難しいようなプロパティを追加します。

function formatJiraIssues(jiraIssues) {
  var eventType = "IncidentReport"
  try{
    var transformedData = [];
    for (var i = 0; i < jiraIssues.issues.length; i++) {
            var issue = jiraIssues.issues[i];
            var fields = issue.fields;
            var rowTimeStamp = fields.customfield_101 || fields.customfield_102
            var weekday = getWeekday(rowTimeStamp)
            var isJapaneseHoliday = getIsJapaneseHoliday(rowTimeStamp)
            // 新しいオブジェクトを作成
            var newObject = {
              eventType: eventType,
              id: issue.id,
              key: issue.key,
              isJapaneseHoliday: isJapaneseHoliday,//boolean
              detectionUnixEpochMillis: timestamp,//unix epoch millis
              initialResponseEpochMillis: toUnixEpochMillis(fields.customfield_103)//unix epoch millis
            };
            // 新しいオブジェクトを結果の配列に追加
            transformedData.push(newObject);
    }
    return transformedData
  }catch(e){
    Logger.log('Error: ' + e.message);
    return null
  }
}

function getWeekday (dateString) {
  const timestamp = new Date(dateString)
  var rest_or_work = ["sun","mon","tue","wed","thu","fri","sat"]; 
  return rest_or_work[timestamp.getDay()]
}

function getIsJapaneseHoliday(dateString){
  const timestamp = new Date(dateString)
  // 祝日カレンダーを確認する
  var calJpHolidayUrl = "ja.japanese#holiday@group.v.calendar.google.com";
  var calJpHoliday = CalendarApp.getCalendarById (calJpHolidayUrl);
  if (calJpHoliday.getEventsForDay(timestamp).length === 0) {
    return false;
  } 
  return true;
}

eventTypeを追加しているのは、データ送信時に必須パラメーターになっているからです(詳しくはこちらを参照)。eventTypeは送信したデータを検索時に利用されます。

SELECT * FROM IncidentReport

計測する範囲が平日なので、祝日と土日を除く必要があります。NRQL上で日本の祝日を判別するのが難しいのでisJapaneseHoliday属性を追加してNRQLでデータをフィルタできるようにします。GAS上で日本のカレンダーを取得し、アラート・問い合わせを検知した時間が祝日である場合フラグをtrueにする仕組みです。

その5:New Relicにデータを送信

New RelicのEvent APIを利用して先ほど加工したデータを送信します(詳しい仕様はこちらを参照)。

function sendJiraIssuesToNewRelic(formattedJiraIssues) {
  var url = `https://insights-collector.newrelic.com/v1/accounts/${YOUR_ACCOUNT_ID}/events`;
  var APIKey = newRelicApiKey;

  var jsonData = JSON.stringify(formattedJiraIssues);
  var blob = Utilities.newBlob(jsonData, 'application/json');
  var gzipData = Utilities.gzip(blob);

  var options = {
    method: "post",
    contentType: "application/json",
    headers: {
      "Api-Key": APIKey,
      "Content-Encoding": "gzip"
    },
    payload: gzipData,
    muteHttpExceptions: true // これによりエラーが発生しても例外がスローされない
  };


  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log(response.getContentText());
    Logger.log(response.getResponseCode()); // レスポンスコードをログ上に記録する
  } catch (e) {
    Logger.log("Error: " + e.message);
  }
}

その6:ダッシュボード上にデータを表示

New Relicに送信したデータをダッシュボード上に表示します。以下はNRQLのイメージです。

WITH (numeric(initialResponseEpochMillis) - numeric(detectionUnixEpochMillis)) / 60000 as Diff FROM IncidentReport SELECT average(Diff) AS '分' WHERE numeric(initialResponseEpochMillis) >= numeric(detectionUnixEpochMillis) AND toDateTime(numeric(detectionUnixEpochMillis)) >= '2024-01-01 00:00:00' AND weekday != 'sun' AND weekday != 'sat' AND isJapaneseHoliday = false AND timestamp IN (SELECT MAX(timestamp) FROM IncidentReport WHERE numeric(initialResponseEpochMillis) >= numeric(detectionUnixEpochMillis) FACET key LIMIT MAX) SINCE this year

initialResponseEpochMillisが初動対応開始時間、detectionUnixEpochMillisが検知時間です。二つの差分から初動対応までの時間を求めています。WHERE句では主に、土曜日や日曜日、祝日のデータをフィルタします。timestamp IN (SELECT MAX(timestamp)としているのは、日々更新データが流入するので、流入した日付が新しいものを選択するようにしています。

結果

計測した数値を出してみると「意外に速いな。」とメンバーから声が上がったり、「目標をもっと引き上げてもいいんじゃないか」といった議論ができるようになりました。さらにダッシュボード上に表示し定期的に確認することで、早めの改善アクションにつなげることが期待できます。

運用のデータを収集するのは意外と大変で後回しにされがちですが、取り組んで可視化してみると上記のようなメリットがありました。皆さんも取り組んでみるのはいかがでしょうか。

脚注
  1. 標準化したのは計測しやすくするというより、アラート・問い合わせ対応時の品質が均一になり対応のミスや抜け漏れを防止する側面の方が強いです。 ↩︎

株式会社BuySell Technologies

Discussion