本記事は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_by と reduce を用いてサービスごとの「リクエスト数」「平均レイテンシ」「エラー率」を集計・出力するシェルスクリプトです。
#!/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
【トラブルシューティングと落とし穴】
group_byのメモリ消費量(大容量データの扱い)問題:
group_byは配列全体をソートしてメモリ上に保持するため、数十万件を超える超巨大なJSONファイルではメモリ不足(OOM)を起こす可能性があります。対策: 単一の巨大配列には
group_byではなく、全要素を直接reduce .[] as $item ({}; ...)でストリーム処理のように辞書(マップ)オブジェクトへ集約する方法に切り替えてください。
権限と一時ファイルのクリーンアップ
問題: 一時ファイル(
mktemp)の削除漏れや、出力先ディレクトリへの書き込み権限不足。対策: スクリプト冒頭で
trap 'rm -f ...' EXIT INT TERMを必ず宣言し、スクリプトが途中で異常終了(SIGINT/SIGTERM)した場合でも一時ファイルが残留しないように設定します。
シークレットや環境変数の漏洩防止
問題:
curlの認証ヘッダー等にトークンを含める際、プロセステーブル経由でコマンドライン引数が露出するリスク。対策: APIキーなどは環境変数から読み込むか、
curl --configや-H @fileを使用してファイルから安全に読み込みます。
【まとめ】
運用の冪等性と堅牢性を維持するための3つのポイント:
アトミックな書き込み: 処理結果を一度
.tmpファイルに出力してからmvで置換し、読み込み側が不完全なファイルを読み込むリスクを回避する。trapとset -euo pipefailによる厳格なエラー制御: 途中でエラーが発生した際に即座に停止し、ゴミファイルの後始末を徹底する。データ規模に応じたjq構造の選択: 小〜中規模配列には可読性の高い
group_by + reduceを使い、超大規模データには直接reduceを用いてメモリフットプリントを最小化する。

