APIログやメトリクスJSONをjqのgroup_byとreduceで高速集計・自動化するシェルスクリプト

Tech

Target Readership: SRE, DevOps Engineers, Infrastructure Automation Developers Tone: Professional, Technical, Practical, Authoritative Keywords: jq, JSON, group_by, reduce, Bash, Shell Script, Automation, SRE

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

APIログやメトリクスJSONをjqのgroup_byとreduceで高速集計・自動化するシェルスクリプト

【導入と前提】

APIレスポンスの巨大なJSON配列をjqで効率的にグループ化・集計し、ログ分析やモニタリングメトリクスの抽出を自動化します。

前提条件

  • OS: Linux (Ubuntu 22.04 LTS / RHEL 9 等) または macOS

  • 必要ツール:

    • jq (v1.6以上を推奨)

    • curl (7.68.0以上)

    • bash (v4.0以上)


【処理フローと設計】

graph TD
    A["API Endpoint / Log Source"] -->|curl with retry| B["Raw JSON Array"]
    B -->|jq group_by & reduce| C["Aggregated JSON Metrics"]
    C -->|Output| D["Prometheus Textfile Exporter / Log File"]

【実装:堅牢な自動化スクリプト】

以下は、リモートエンドポイントからHTTPリクエストログ(JSON配列)を取得し、group_byreduce を用いてサービスごとの「リクエスト数」「平均レイテンシ」「エラー率」を集計・出力するシェルスクリプトです。

#!/usr/bin/env bash

set -euo pipefail

# ==============================================================================


# 設定情報


# ==============================================================================

readonly API_URL="https://api.example.com/v1/metrics/logs"
readonly OUTPUT_FILE="/var/log/metrics_summary.json"
readonly TIMEOUT_SEC=10

# 一時ファイルの生成と安全な破棄設定

TMPFILE="$(mktemp /tmp/json_aggregate.XXXXXX.json)"
trap 'rm -f "${TMPFILE}"' EXIT INT TERM

# ==============================================================================


# メイン処理


# ==============================================================================

# 1. APIからのデータ取得 (リトライ処理付き)


# -s: サイレントモード (プログレスバー非表示)


# -S: エラー時のみエラーメッセージを表示


# -f: HTTPエラー (4xx, 5xx) 時に失敗扱いにする


# -L: リダイレクトを追跡


# --retry: 通信失敗時の再試行回数

curl -sSfL --retry 3 --retry-delay 2 --connect-timeout "${TIMEOUT_SEC}" \
    "${API_URL}" -o "${TMPFILE}"

# 2. jq による高機能集計処理


#   - group_by(.service) でサービス毎に配列を分割


#   - reduce で単一オブジェクトへと集約しメモリ効率と可読性を両立

jq '

  # group_by でキー単位にグループ化

  group_by(.service)
  | map(

      # reduce を活用して各グループ内の要素を集計

      reduce .[] as $item (
        { service: .[0].service, count: 0, total_latency: 0, errors: 0 };
        .count += 1
        | .total_latency += $item.latency
        | if $item.status >= 400 then .errors += 1 else . end
      )
    )
  | map(

      # 平均値・エラー率の算出(ゼロ除算防止付き)

      . + {
        avg_latency_ms: (if .count > 0 then (.total_latency / .count | round) else 0 end),
        error_rate_pct: (if .count > 0 then ((.errors / .count) * 100 | round) else 0 end)
      }
      | del(.total_latency) # 不要になった中間キーを削除
    )
' "${TMPFILE}" > "${OUTPUT_FILE}.tmp"

# アトミックなファイル書き換え

mv "${OUTPUT_FILE}.tmp" "${OUTPUT_FILE}"

echo "[INFO] Aggregation completed successfully. Output saved to ${OUTPUT_FILE}"

定期実行用 systemd 設定例

スクリプトを5分ごとに自動実行するための設定例です。

/etc/systemd/system/jq-aggregate.service:

[Unit]
Description=Aggregate API JSON Logs using jq
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/aggregate_logs.sh
User=nobody
Group=nogroup

/etc/systemd/system/jq-aggregate.timer:

[Unit]
Description=Run jq aggregate script every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

1. 正常系テスト

テスト用のサンプルデータを標準入力から流し込み、結果を確認します。

# テストデータの作成と集計の確認

cat <<'EOF' | jq 'group_by(.service) | map(reduce .[] as $item ({service: .[0].service, count: 0, total_latency: 0}; .count += 1 | .total_latency += $item.latency) | . + {avg_latency: (.total_latency / .count)})'
[
  {"service": "auth", "status": 200, "latency": 100},
  {"service": "auth", "status": 500, "latency": 300},
  {"service": "payment", "status": 200, "latency": 250}
]
EOF

2. ログ確認

systemdタイマーおよびスクリプトの実行ログを journalctl で確認します。

# タイマーの稼働状況を確認

systemctl list-timers jq-aggregate.timer

# 直近の実行ログを確認

journalctl -u jq-aggregate.service -n 50 --no-pager

【トラブルシューティングと落とし穴】

  1. group_by のメモリ消費量(大容量データの扱い)

    • 問題: group_by は配列全体をソートしてメモリ上に保持するため、数十万件を超える超巨大なJSONファイルではメモリ不足(OOM)を起こす可能性があります。

    • 対策: 単一の巨大配列には group_by ではなく、全要素を直接 reduce .[] as $item ({}; ...) でストリーム処理のように辞書(マップ)オブジェクトへ集約する方法に切り替えてください。

  2. 権限と一時ファイルのクリーンアップ

    • 問題: 一時ファイル(mktemp)の削除漏れや、出力先ディレクトリへの書き込み権限不足。

    • 対策: スクリプト冒頭で trap 'rm -f ...' EXIT INT TERM を必ず宣言し、スクリプトが途中で異常終了(SIGINT/SIGTERM)した場合でも一時ファイルが残留しないように設定します。

  3. シークレットや環境変数の漏洩防止

    • 問題: curl の認証ヘッダー等にトークンを含める際、プロセステーブル経由でコマンドライン引数が露出するリスク。

    • 対策: APIキーなどは環境変数から読み込むか、curl --config-H @file を使用してファイルから安全に読み込みます。


【まとめ】

運用の冪等性と堅牢性を維持するための3つのポイント:

  1. アトミックな書き込み: 処理結果を一度 .tmp ファイルに出力してから mv で置換し、読み込み側が不完全なファイルを読み込むリスクを回避する。

  2. trapset -euo pipefail による厳格なエラー制御: 途中でエラーが発生した際に即座に停止し、ゴミファイルの後始末を徹底する。

  3. データ規模に応じたjq構造の選択: 小〜中規模配列には可読性の高い group_by + reduce を使い、超大規模データには直接 reduce を用いてメモリフットプリントを最小化する。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました