9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

New Relic の Lookup Table を GitHub Actions で更新して運用を自動化

Last updated at Posted at 2026-02-06

New Relic で分析をしていると、ログやメトリクスには store_idapp_id といった無機質な値しか残っていないことがよくあります。しかし、障害発生時に本当に知りたいのは「どの地域の、どの店舗で?」「どのチームの、どのサービスで?」という 業務上のコンテキスト です。

これを解決するのが Lookup Table です。Lookup Table は「外部CSVを元に、イベントデータへ動的に JOIN できるマスタ」です。NRQL の LEFT JOIN を使用して、Transaction / Log / Metric などに紐付けられます。
さらにこのマスタデータをGitHubで管理し、GitHub Actions で自動更新する「GitOps運用」を構築することで、監視の柔軟性と信頼性を劇的に向上させることができます。

最新のアップデートの詳細はこちら
New Relic アップデート一覧

無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!

なぜ Lookup Table の運用は属人化・手動・不透明になりやすいのか

Lookup Table は便利な仕組みですが、運用を工夫しないと 属人化・更新漏れ・信頼性低下 を招きやすいという特徴があります。主な要因は以下の通りです。

属人化が発生する要因

Lookup Table の更新は、New Relic の管理画面から CSV をアップロードする必要があります。
この操作には権限が必要なため、更新できる人が限られ、「誰に頼めばよいか」が暗黙知になりやすい という問題があります。

また、店舗追加や組織変更などは発生頻度が低く、手順を毎回思い出す必要がある作業になりがちです。その結果、特定の人に作業が集中し、運用が属人化します。

手動運用になりやすい要因

Lookup Table の更新は、アプリケーション変更やインフラ変更とは独立して行われることが多く、更新のタイミングが仕組み化されていません。

そのため、

  • 新しい ID が追加されたのにマスタが更新されない
  • 障害発生後に「マスタが古い」ことに気づく

といった 更新漏れ が構造的に発生します。

不透明な運用になりやすい要因

管理画面からの手動アップロードでは、
「誰が・いつ・なぜ変更したのか」 が記録として残りません。

この状態では、

  • 過去のマッピングを再現できない
  • 障害分析時に数値の根拠が説明できない

といった問題が起こり、Lookup Table 自体が次第に信用されなくなります。

課題のまとめ

Lookup Table の運用課題は、
更新手段が人と画面操作に閉じていることに起因します。

その結果、

  • 属人化
  • 手動更新による反映漏れ
  • 変更履歴が追えない不透明な運用

が発生し、本来は分析や判断を助けるはずのマスタが、逆に不安要素になってしまうのです。

なぜ「GitOps」でマスタを管理するのか?

画面からCSVを手動アップロードする運用には、以下の課題があります。

  • 属人化: 「あのマスタ、最新だっけ?」と管理画面の権限を持つ誰かに確認しないとわからない
  • 不透明な変更履歴: 誰がいつ、なぜマッピングを変えたのか追跡できない
  • 反映漏れ: 新店舗や新サービスが追加されたのに、New Relic側の更新を忘れる

これらを GitHub 管理に集約することで、「PRでレビュー」「コミットログで履歴管理」「マージで自動反映」 という、エンジニアにとって馴染み深く安全なフローに乗せることができます。

活用のバリエーション(店舗・サービス・キャンペーン)

Lookup Tableは、あらゆる「ID」を「意味のある言葉」に変換できます。

ユースケース Key Lookupして得られる情報の例
店舗・拠点管理 store_id 店舗名、エリア、開店年次、店長連絡先
サービスオーナーシップ app_id 担当部署、Slackチャンネル、重要度(Tier 1-3)
キャンペーン/施策分析 campaign_id 施策名、割引率、対象ユーザーセグメント
インフラ管理 instance_id 所有者、コストセンター、プロジェクト名

前提条件:GitHub Secrets の設定

APIキーなどの機密情報をスクリプト内に直接記述したり、リポジトリにコミットしたりするのはセキュリティ上非常に危険です。GitHubの Secrets 機能を使い、安全に環境変数として利用できるように設定します。

必要な情報の取得

まず、New Relicから以下の2点を用意します。

  1. User API Key: API Keys UI から作成(※ NRAK- で始まるキー)
  2. Account ID: New Relicにログインした際のURLや、[API Keys UI] 画面で確認できる数値

GitHubへの登録手順

リポジトリの管理画面から、以下の手順で登録します。

  1. GitHubのリポジトリにアクセスし、上部の [Settings] タブをクリックします。
  2. 左サイドバーの [Secrets and variables] > [Actions] を選択します。
  3. [New repository secret] ボタンをクリックし、以下の2つを追加します。
Secret Name 説明
NEW_RELIC_USER_API_KEY New Relicの User API Key (NRAK-...)
NEW_RELIC_ACCOUNT_ID New Relicの Account ID(数値)

リージョンがEUの場合は、同様の手順で NEW_RELIC_REGION という名前のSecretを作成し、値に EU と入力してください(USの場合はデフォルトで動作するため不要です)。

image.png

実装ガイド:自動同期システムの構築手順

このセクションでは、堅牢な同期システムを「ディレクトリ構成」「アップロードするPythonスクリプト」「GitHub Actions の設定」の3ステップで構築します。

ディレクトリ構成

データ、スクリプト、ワークフローを分離して管理します。

.
├── .github/
│   └── workflows/
│       └── upload_lookup.yml    # GitHub Actions設定
├── scripts/
│   └── upload_lookup_table.py   # 同期用Pythonスクリプト
└── lookups/
    └── store_master.csv         # 同期対象のCSVデータ

アップロードするPythonスクリプト

このスクリプトは以下を担います。

  • Lookup Table の存在確認
  • 新規作成 or 更新の自動切り替え
  • GitHub Actions 実行時の安定アップロード

今回使用するスクリプトには、API連携を安定させるための以下の処理を組み込んでいます。

  • 存在確認によるメソッドの切り替え
    exists() メソッドで対象テーブルの有無を確認し、未作成なら POST、作成済みなら PUT を選択してリクエストを送信します。
  • HTTPリトライ処理
    urllib3.util.retry.Retry を使用し、一時的なネットワークエラーやサーバーエラー(429, 500, 502, 503, 504)が発生した際に、指数バックオフを伴う最大3回のリトライを行います。
  • multipart/form-dataによるアップロード
    APIの仕様に基づき、CSVファイルを multipart/form-data 形式で送信します。requests.Session を通じてヘッダーを一括管理しています。
scripts/upload_lookup_table.py
import os
import sys
import logging
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# ロギング設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class NewRelicLookupClient:
    """New Relic NRQL Lookups API クライアント"""
    def __init__(self, api_key, account_id, region="US"):
        self.api_key = api_key
        self.account_id = account_id
        # データセンターのリージョンに基づいてベースURLを切り替え
        self.base_url = (
            "https://nrql-lookup.service.eu.newrelic.com" if region.upper() == "EU"
            else "https://nrql-lookup.service.newrelic.com"
        )
        self.headers = {"Api-Key": self.api_key}
        self.session = self._create_session()

    def _create_session(self):
        """リトライロジック付きの HTTP セッションを作成"""
        session = requests.Session()
        # ネットワークエラーや一時的なサーバーエラー(429, 500, 502, 503, 504)に対してリトライ
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504]
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        session.mount("https://", adapter)
        return session

    def _get_url(self, table_name):
        """API エンドポイント URL を取得"""
        return f"{self.base_url}/v1/accounts/{self.account_id}/{table_name}"

    def exists(self, table_name):
        """
        Lookup Table の存在確認
        
        Args:
            table_name (str): 確認対象のテーブル名
        Returns:
            bool: 存在する場合は True、存在しない場合は False
        """
        url = self._get_url(table_name)
        try:
            response = self.session.get(url, headers=self.headers)
            if response.status_code == 200:
                return True
            if response.status_code == 404:
                return False
            # その他のエラーステータスは例外をスロー
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            logger.error(f"Error checking existence for '{table_name}': {e}")
            raise

    def upload(self, csv_path, table_name):
        """
        CSV ファイルを Lookup Table としてアップロード(新規作成または更新)
        
        Args:
            csv_path (str): アップロードする CSV ファイルのパス
            table_name (str): 登録先のテーブル名
        """
        url = self._get_url(table_name)
        # テーブルの存在確認を行い、HTTP メソッド(POST または PUT)を選択
        is_update = self.exists(table_name)
        
        method = "PUT" if is_update else "POST"
        logger.info(f"{'Updating' if is_update else 'Creating'} Lookup Table '{table_name}' using {csv_path}...")

        try:
            with open(csv_path, "rb") as f:
                # New Relic API はフィールド名 'table' で CSV のバイナリデータを期待する
                files = {"table": (os.path.basename(csv_path), f, "text/csv")}
                response = self.session.request(method, url, headers=self.headers, files=files)
                response.raise_for_status()
                
            logger.info(f"Successfully {'updated' if is_update else 'created'}: {table_name}")
        except requests.exceptions.RequestException as e:
            logger.error(f"Failed to upload '{table_name}': {e}")
            if hasattr(e.response, 'text'):
                logger.error(f"Response details: {e.response.text}")
            raise

def main():
    api_key = os.environ.get("NEW_RELIC_USER_API_KEY")
    account_id = os.environ.get("NEW_RELIC_ACCOUNT_ID")
    region = os.environ.get("NEW_RELIC_REGION", "US")

    if not api_key or not account_id:
        logger.error("Environment variables NEW_RELIC_USER_API_KEY and NEW_RELIC_ACCOUNT_ID is required.")
        sys.exit(1)

    if len(sys.argv) < 3:
        print("Usage: python upload_lookup_table.py <csv_path> <table_name>")
        sys.exit(1)

    csv_path, table_name = sys.argv[1], sys.argv[2]

    client = NewRelicLookupClient(api_key, account_id, region)
    try:
        client.upload(csv_path, table_name)
    except Exception:
        sys.exit(1)

if __name__ == "__main__":
    main()

GitHub Actions 設定による柔軟な自動更新

マスタデータの運用では「特定のファイルだけ更新したい」ケースや「過去の履歴を含めて一括反映したい」ケースがあります。これらに対応するワークフローの実装例です。

1. ワークフローの構成

この設定では、以下の2つのモードをサポートしています。

  • 自動実行(Pushトリガー):
    lookups/ ディレクトリ内の CSV が変更された際、そのファイルのみを特定して New Relic へ反映します。
  • 手動実行(workflow_dispatch):
    GitHub の画面からテーブル名を指定して、任意のタイミングで特定のファイルをアップロードできます。

2. ワークフロー定義 (.github/workflows/upload_lookup_table.yml)

このワークフローは「ファイル名がそのままテーブル名になる」というルールを課しているため、CSVファイル名は New Relic 上で作成したいテーブル名と一致させてください。

.github/workflows/upload_lookup_table.yml
name: Upload New Relic Lookup Tables

on:
  push:
    paths:
      - 'lookups/*.csv'
  workflow_dispatch:
    inputs:
      table_name:
        description: 'Upload a specific table (filename without .csv)'
        required: false
        default: ''

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.x'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install requests

      - name: Upload changed files
        env:
          NEW_RELIC_USER_API_KEY: ${{ secrets.NEW_RELIC_USER_API_KEY }}
          NEW_RELIC_ACCOUNT_ID: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
          NEW_RELIC_REGION: ${{ secrets.NEW_RELIC_REGION || 'US' }}
        run: |
          if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ github.event.inputs.table_name }}" != "" ]; then
            # 手動実行でテーブル名が指定された場合
            python scripts/upload_lookup_table.py lookups/${{ github.event.inputs.table_name }}.csv ${{ github.event.inputs.table_name }}
          else
            # 変更されたCSVファイルを特定してアップロード
            # 初回プッシュや多数のファイル変更も考慮し、lookups内の全ファイルを対象とするか
            # もしくは git diff で変更分のみに絞る
            FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '^lookups/.*\.csv$' || true)
            
            # FILESが空(初回プッシュなど)の場合はディレクトリ内の全CSVを対象にする
            if [ -z "$FILES" ]; then
              FILES=$(ls lookups/*.csv)
            fi

            for file in $FILES; do
              if [ -f "$file" ]; then
                TABLE_NAME=$(basename "$file" .csv)
                python scripts/upload_lookup_table.py "$file" "$TABLE_NAME"
              fi
            done
          fi

3. 実装のポイント

① 差分ファイルの特定

git diff を活用して、今回のプッシュで変更があったファイルのみを抽出しています。これにより、大量のマスタを抱えているリポジトリでも、API への不要なリクエストを最小限に抑えることができます。

  • git diff --name-only ${{ github.event.before }} ${{ github.sha }}
    プッシュ前後の差分からファイル名のみを取得します。

② 初回プッシュへの対応

新規リポジトリ作成時や、差分が取得できない特殊なケースを考慮し、FILES が空の場合はディレクトリ内の全 CSV を対象とする処理を入れています。

③ 入力フォーム(inputs)の活用

workflow_dispatchinputs を定義することで、エンジニア以外のメンバーでも GitHub UI 上から「どのテーブルを更新するか」を直感的に選択できるようになります。

4. 運用上の留意点

  • fetch-depth: actions/checkoutfetch-depth: 0 を指定しているのは、git diff で過去のコミット履歴を参照するために必要だからです
  • ファイル名 = テーブル名: このワークフローでは、CSV のファイル名(拡張子なし)が New Relic 上のテーブル名として自動的に使用されます

Lookup Table 活用術:データに「意味」を持たせる

CSV をアップロードしただけでは、まだデータが New Relic 内に蓄積されただけです。これを活用して、無機質なIDを「生きた情報」に変える方法を解説します。

事例:店舗マスタを紐付けたエリア別パフォーマンス分析

以下のような店舗マスタ(store_master.csv)が同期されているとします。

store_id (Key) store_name location manager
STORE-001 渋谷店 東京都 田中
STORE-002 新宿店 東京都 佐藤
STORE-003 梅田店 大阪府 鈴木
STORE-004 栄店 愛知県 高橋

lookup_table.png

カスタム属性を活用することで店舗ごとのパフォーマンスを確認することはできます。しかし、store_idをそのまま表示すると下記のようにそれぞれの店舗がどこなのかという情報はぱっと見では分かりません。

no_lookup_table.png

Lookup Table を活用することで店舗名だけではなく、付加情報を合わせて表示して誰がみてもわかる表示にカスタマイズすることが可能です。
image.png

現場で役立つTips & 注意点

API Keyの権限に注意

使用するAPIキーは "User API Key" である必要があります。ライセンスキー(Ingest Key)では動作しません。

NRQLでの一工夫:デフォルト値の設定

マスタに登録されていない新しいIDが含まれていた場合、lookup()null を返します。これを見やすくするためにデフォルト値を設定しておくと便利です。

NRQL
-- マスタにない場合に 'Unknown' と表示する
FROM Transaction 
SELECT count(*) 
LEFT JOIN (
 FROM lookup(store_master)
 SELECT store_id, store_name, location, manager 
) ON storeId = store_id 
FACET if(store_name IS NULL, 'Unknown', store_name) AS `店舗名`

Lookup Table の制約条件と留意事項

Lookup Tableを導入する前に、以下の制約を把握しておく必要があります。これらは運用設計(マスタをどの粒度で分けるかなど)に影響します。

項目 上限・制約 備考
最大テーブル数 1 アカウントあたり 250 不要になったテーブルは適宜削除を推奨
最大行数 最大20,000行 これを超える場合は分割や集約を検討
最大ファイルサイズ 4 MB CSVファイル1つあたりの容量

まとめ

New RelicのLookup Tableは、APIとGitHub Actionsを組み合わせることで、「生きたマスタ」 として機能します。無機質なテレメトリデータに「ビジネスの文脈」を注入し、誰が見ても一目で状況がわかるオブザーバビリティ環境を構築しましょう!

 New Relicでは、新しい機能やその活用方法について、QiitaやXで発信しています!
無料でアカウント作成も可能なのでぜひお試しください!

New Relic株式会社のX(旧Twitter)Qiita OrganizationOrganizationでは、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。

無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!

image.png

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?