安定したシステム運用を実現するには、ただアラートを設定するだけでは不十分です。それを信頼できることが重要です。 オブザーバビリティ環境を拡張する際に組織がよく直面するのは、「膨大なアラート条件をどう管理するか」、そして「テレメトリデータの送信が途絶えた場合でも、いかにしてアラートの信頼性を維持するか」という課題です。

多くの現場では、何百ものNRQLアラートを運用しているものの、「データ途絶時の検知(シグナルロス)」や「データの欠損埋め(ギャップフィリング)」といった、信頼性を担保する重要な設定が漏れてしまっているのが実情です。これらの対策を怠ると、いざという時にアラートが鳴らない検知漏れや不要なアラートが何度も発報され、アラート疲れを生み出すことになります。

ユーザーインタフェースはきめ細かな調整や調査には最適ですが、何百もの条件にわたって変更を加えるには、より合理化されたプログラムによるアプローチが必要です。大規模な環境で一貫性を確保するために、自動化を活用して既存のアラートを一括して特定、収集、変更できます。このソリューションでは、New Relic GraphQL APIであるNerdGraphを使用します。

シグナルロスとギャップフィリングについて

自動化の具体的な手順に入る前に、これら2つの設定がなぜ重要なのかを整理しておきましょう。この2つを正しく設定することで、無駄なアラート(アラート疲れ)や肝心な時に鳴らないアラート(アラート漏れ)を、本当に信頼できる確実な通知へと変えることができるからです。
 

1.シグナルロスの危険性

標準のNRQLアラートは、入ってくるデータを評価します。サービスが完全にクラッシュしたり、ホストが停止したり、ネットワークが切断されたりすると、データストリームは停止します。シグナルロスが設定されていない場合、閾値が実際に違反されていないため、アラート条件では正常として認識します。

これにより、「アラート漏れ」、つまりシステムがクラッシュしているにも関わらず、アラートが発報されないという事象が発生します。これは、システムが問題の存在を認識しないため、平均検出時間(MTTD)が無制限に増加するという重大なビジネスリスクをもたらします。シグナルロスを設定すると、テレメトリーデータが停止するとすぐに通知が届くため、停止したサービスも障害中のサービスと同じ緊急度で対応できるようになります。

  • シグナルロスの設定なし:サービスが停止 -> テレメトリーデータが停止 -> アラート発報されない
  • シグナルロスの設定がある場合:サービスが停止 -> テレメトリーデータが停止 -> New Relicが信号損失違反をトリガー -> すぐに通知

2. スパースデータによる不安定性を解消 (ギャップフィリング)

信号損失は完全なシステム停止に対処しますが、ギャップフィリングは現代の分散環境に見られる「ノイズ」と不安定性に対処します。データストリームは必ずしも完璧な連続した線ではありません。データが若干の遅延やギャップを伴って到着する場合があります。

ギャップフィリング戦略がなければ、エンジンは継続的な傾向ではなくnull値を評価するため、こうした小さなレポートの問題によってアラートが急速に発報されたり、解除されたりすることがあります。その結果、アラート疲れが発生し、システムが不安定だったり信頼性に欠けたりすると感じたエンジニアは通知を無視し始めます。ギャップフィリングにより、アラートエンジンは欠落したデータポイントを静的な値(「0」など) または最後の既知の値で置き換え、アラートロジックの安定性を維持します。これにより、企業は不要なインシデントの追跡に貴重な開発工数を浪費することなく、チームが本当に重要なパフォーマンスの問題に集中できるようになります。

 

解決策:2段階の自動化ワークフロー

既存のアラートでこれらの機能を現在使用していない場合は、次のスクリプトを使用してすぐに改善できます。これらの機能を現在の環境に大規模に導入するには、Bashスクリプトを使用してNerdGraphと連携する2段階のワークフローを導入できます。このプロセスにより、手動で各アラートを確認することなく、数百件のアラートを編集できます。

フェーズ1:Condition IDの収集

最初のステップは、更新対象のアラートコンディション情報を取得することです。UI を手動でクリックせずに、NerdGraph の nrqlConditionsSearch クエリを利用しすることで、更新が必要なアラートコンディションの一覧を効率的に見つけることができます。

下記のサンプルスクリプトを実行すると 引数に指定した 名前パターン(nameLike フィルターを使用)でアラートコンディションが検索され、合致するアラートコンディション情報を取得することができます。名前パターンに合致したアラートコンディションの コンディションID と名前が CSV ファイルとして出力されます。

#!/bin/bash

#
# スクリプト1:条件の収集
# 名前のLIKEパターンマッチを使用して、すべてのNRQLアラート条件を検索します。
#
# 検索パターンには必ずSQLスタイルのワイルドカード(%)を含めてください。
#
# 前提条件:curl、jq
#
# --- 使用例 ---
# ./collect_conditions.sh --condition-name "%"
# ./collect_conditions.sh --condition-name " %prod"
#

# --- 設定 ---
API_KEY="YOUR_NEW_RELIC_API_KEY" # 重要:これはライセンスキーではなく、ユーザーキーです。
ACCOUNT_ID="YOUR_ACCOUNT_ID"
OUTPUT_FILE="conditions.csv"

# --- 実行前チェック ---
if ! command -v jq &> /dev/null; then echo "エラー:'jq'が見つかりません。インストールしてください。" && exit 1; fi
if [ "$API_KEY" == "YOUR_NEW_RELIC_API_KEY" ]; then echo "エラー:このスクリプトを編集し、API_KEYを設定してください。" && exit 1; fi
if [ $# -ne 2 ] || [ "$1" != "--condition-name" ]; then
    echo "Usage: $0 --condition-name <name_pattern>" && exit 1
fi

# --- 引数から検索パターンを取得 ---
SEARCH_PATTERN="$2"

# --- GraphQL Query定義 ---
read -r -d '' GQL_QUERY <<EOF
query(\$accountId: Int!, \$conditionNameLike: String!, \$cursor: String) {
  actor {
    account(id: \$accountId) {
      alerts {
        nrqlConditionsSearch(searchCriteria: {nameLike: \$conditionNameLike}, cursor: \$cursor) {
          nextCursor
          nrqlConditions {
            id
            name
          }
        }
      }
    }
  }
}
EOF

# --- ループ初期化 ---
CURSOR="" # 最初のページは空のカーソルで開始
echo "Searching for conditions where name is LIKE: \"${SEARCH_PATTERN}\"..."
echo "id,name" > "$OUTPUT_FILE" # ヘッダー付きでファイルを作成

# --- ページネーションループ ---
# このループはAPIが'nextCursor'を返す限り継続
while true; do
    if [ -z "$CURSOR" ]; then
        echo "Fetching first page..."
    else
        echo "Fetching next page..."
    fi

    # シェル変数が空の場合、JSON内ではcursorをnullに設定
    JSON_PAYLOAD=$(jq -n \
      --arg q "$GQL_QUERY" \
      --arg id "$ACCOUNT_ID" \
      --arg val "$SEARCH_PATTERN" \
      --arg cursor "$CURSOR" \
      '{query: $q, variables: {accountId: $id | tonumber, conditionNameLike: $val, cursor: ($cursor | if . == "" then null else . end)}}')

    # APIコール実行
    RESPONSE=$(curl -s -X POST https://api.newrelic.com/graphql \
         -H "Content-Type: application/json" \
         -H "API-Key: ${API_KEY}" \
         -d "${JSON_PAYLOAD}")

    # APIエラーチェック
    if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
      echo "Error: The API returned an error:"
      echo "$RESPONSE" | jq .
      exit 1
    fi

    # 現在のページの結果をCSVファイルに追加
    echo "$RESPONSE" | jq -r '.data.actor.account.alerts.nrqlConditionsSearch.nrqlConditions[] | [.id, .name] | @csv' >> "$OUTPUT_FILE"

    # 次のページのカーソルを取得
    CURSOR=$(echo "$RESPONSE" | jq -r '.data.actor.account.alerts.nrqlConditionsSearch.nextCursor')

    # カーソルがnullまたは空の場合は、それ以上のページが存在しないため、ループを終了
    if [ -z "$CURSOR" ] || [ "$CURSOR" == "null" ]; then
        break
    fi
done

# --- 最終概要 ---
COUNT=$(($(wc -l < "$OUTPUT_FILE") - 1))
echo "✅ 完了。合計${COUNT}件の条件が見つかりました。完全なリストについては'${OUTPUT_FILE}'をご覧ください。"

フェーズ2:収集したすべての条件を更新

IDのリストを取得したら、2番目のスクリプトを使ってCSVを反復処理して、更新済みの設定を適用します。新しい標準を強制するために、alertsNrqlConditionStaticUpdate Mutationを使用します。

conditionブロック内で次のロジックを適用します。

  1. シグナルロス:expirationDuration90秒に設定し、openViolationOnExpirationtrueに設定します。これにより、データストリームが90秒間停止した場合、直ちにインシデントがオープンされることが保証されます。
     
  2. ギャップフィリング:fillOptionSTATICに設定し、fillValue0(またはメトリクスに適した値)に設定します。これにより、データのギャップが埋められ、評価ロジックがそのまま維持されます。
#!/bin/bash

#
# スクリプト2:更新条件
# CSVを読み取り、更新を適用し、正確な最終概要を提供します。
#
# 前提条件:curl、jq
#
# 使用法:
# ./update_conditions.sh conditions.csv
#

# --- 設定 ---
API_KEY="YOUR_NEW_RELIC_API_KEY" # 重要:これはライセンスキーではなく、ユーザーキーです。
ACCOUNT_ID="YOUR_ACCOUNT_ID"

# --- パラメーター --- 
EXPIRATION_DURATION=90 # データ受信が停止してから信号損失を宣言するまでの待機時間(秒)。
FILL_VALUE=0 # データストリーム内のギャップを埋める際に使用する静的な値。

# --- 実行前チェック ---
if ! command -v jq &> /dev/null; then echo "エラー: 'jq'が見つかりません。インストールしてください。" && exit 1; fi
if [ "$API_KEY" == "YOUR_NEW_RELIC_API_KEY" ]; then echo "エラー:このスクリプトを編集し、API_KEYを設定してください。" && exit 1; fi

INPUT_FILE="$1"
if [ -z "$INPUT_FILE" ]; then echo "Usage: $0 <path_to_csv_file>" && exit 1; fi
if [ ! -f "$INPUT_FILE" ]; then echo "エラー:'$INPUT_FILE'にファイルが見つかりません。" && exit 1; fi

# --- GraphQL Mutation 定義 ---
read -r -d '' GQL_MUTATION <<EOF
mutation(\$accountId: Int!, \$conditionId: ID!) {
  alertsNrqlConditionStaticUpdate(
    accountId: \$accountId,
    id: \$conditionId,
    condition: {
      expiration: {
        expirationDuration: ${EXPIRATION_DURATION}, 
        openViolationOnExpiration: true
      },
      signal: {
        fillOption: STATIC,
        fillValue: ${FILL_VALUE}
      }
    }
  ) {
    id
    name
  }
}
EOF

# --- カウンターとログファイルの初期化 ---
SUCCESS_COUNT=0
FAILURE_COUNT=0
FAILED_LOG_FILE="failed_updates.log"
TOTAL_LINES=$(($(wc -l < "$INPUT_FILE") - 1))
CURRENT_LINE=0

# 以前の実行ログをクリア
> "$FAILED_LOG_FILE"
echo "Starting bulk update... Any failures will be logged to ${FAILED_LOG_FILE}"
echo "Applying updates: Expiration=${EXPIRATION_DURATION}s, Fill Value=${SIGNAL_FILL_VALUE}"
echo ""

# --- メインループ ---
while IFS=, read -r id name; do
    ((CURRENT_LINE++))
    
    # CSVから読み込んだidとnameのダブルクォートを削除
    id=$(echo "$id" | tr -d '"')
    clean_name=$(echo "$name" | tr -d '"')

    echo "Updating (${CURRENT_LINE}/${TOTAL_LINES}): \"${clean_name}\" (ID: ${id})"

    # この特定の条件IDの変数を使用してJSONペイロードを構築
    JSON_PAYLOAD=$(jq -n \
      --arg q "$GQL_MUTATION" \
      --arg accountId "$ACCOUNT_ID" \
      --arg conditionId "$id" \
      '{query: $q, variables: {accountId: $accountId | tonumber, conditionId: $conditionId | tonumber}}')

    RESPONSE=$(curl -s -X POST https://api.newrelic.com/graphql \
         -H "Content-Type: application/json" \
         -H "API-Key: ${API_KEY}" \
         -d "${JSON_PAYLOAD}")

    # エラーをチェックし、カウンターを増やす
    if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
      echo "  -> ❌ 条件${id}の更新中にエラーが発生しました。詳細はログを参照してください。"
      echo "Failed to update ID: ${id}, Name: \"${clean_name}\"" >> "$FAILED_LOG_FILE"
      echo "API Response: $(echo "$RESPONSE" | jq .errors)" >> "$FAILED_LOG_FILE"
      echo "--------------------------------------------------" >> "$FAILED_LOG_FILE"
      ((FAILURE_COUNT++))
    else
      echo "  -> ✅ 更新成功"
      ((SUCCESS_COUNT++))
    fi

done < <(tail -n +2 "$INPUT_FILE")


# --- Final Summary Block ---
echo ""
echo "--------------------"
echo "Bulk Update Summary"
echo "--------------------"
echo "Total conditions to process: ${TOTAL_LINES}"
echo "✅ 更新成功: ${SUCCESS_COUNT}"
echo "❌ 更新失敗:     ${FAILURE_COUNT}"
echo ""

if [ "$FAILURE_COUNT" -gt 0 ]; then
    echo "Details for all failed updates have been saved to: ${FAILED_LOG_FILE}"
fi

echo "Process complete."

結論:手作業からスケーラブルな信頼性へ

このアプローチは、数百に及ぶアラート設定を標準化するという緊急の問題を解決するだけでなく、プログラムで設定を管理するNerdGraphの威力も実証します。この自動化を実装することで、手動で時間のかかる作業を、シンプルで繰り返し可能なプロセスに変換できます。

これには明らかなビジネス上のメリットがあります。エンジニアリングチームはアラート疲れを解消して機能開発に集中することで効率性を高め、経営チームはインシデントMTTDの削減を実現できます。その結果、顧客体験が確保され、財務リスクが軽減され、「検知漏れ」と「アラート疲れ」のない堅牢なアラート体制を構築できます。