jq の group_by と reduce の使い分け(メモリ効率と可読性のトレードオフ)を軸に、一時ファイル処理やエラーハンドリングを含む、そのまま本番運用に使えるレベルのスクリプトを提示します。
分散システムのログ集計を効率化する:jqのgroup_byとreduceを用いた高度なJSON集計
【導入と前提】 APIレスポンスや構造化ログ等の巨大なJSON配列から、特定のキーに基づいた統計(ステータスコード別集計など)を抽出・自動化する手法を解説します。
実行環境:GNU/Linux (Ubuntu/RHEL)
必須ツール:
jq(1.6以上推奨)、curl
【処理フローと設計】
graph TD
A["JSON Input: Raw Logs"] --> B{"jq Processing"}
B -->|group_by| C["Sort & Cluster"]
B -->|reduce| D["Iterative Accumulation"]
C --> E["Aggregated Report"]
D --> E
E --> F["Standard Output/Alert"]
group_by は一度すべての要素をメモリに展開してソートするため直感的ですが、reduce は1パスで処理可能なため、大規模データにおいてメモリ効率に優れます。本稿ではこれらを組み合わせた堅牢なスクリプトを構築します。
【実装:堅牢な自動化スクリプト】 以下は、ダミーのAPIエンドポイント(またはログファイル)からデータを取得し、ステータスコードごとに件数を集計する実戦的なスクリプト例です。
#!/usr/bin/env bash
# --- 堅牢なシェル実行設定 ---
set -euo pipefail
# -e: エラー発生時に即座に終了
# -u: 未定義変数の参照時にエラー
# -o pipefail: パイプライン途中のエラーも検知
# --- 初期設定 ---
readonly WORK_DIR=$(mktemp -d) # 安全な一時ディレクトリの作成
readonly LOG_FILE="${WORK_DIR}/api_response.json"
# 終了時のクリーンアップ処理
trap 'rm -rf "${WORK_DIR}"' EXIT
# --- データ取得フェーズ ---
echo "Fetching data from API..."
# curlのオプション:
# -s: 進捗表示を抑制 (Silent)
# -f: HTTPエラー時に失敗として扱う (Fail)
# -L: リダイレクトを追跡 (Location)
# --retry: ネットワーク一時エラー時の再試行
if ! curl -sfL --retry 3 "https://api.example.com/v1/audit-logs" -o "${LOG_FILE}"; then
echo "Error: Failed to fetch data." >&2
exit 1
fi
# --- jqによる高度な集計処理 ---
echo "Analyzing status code distribution..."
# 手法1: group_by による集計 (可読性重視)
# group_by(.key) は事前にソートが必要だが、jq内部で処理される
echo "--- Group By Result ---"
jq -r '
group_by(.status) |
map({
status: .[0].status,
count: length
})
' "${LOG_FILE}"
# 手法2: reduce による集計 (メモリ効率・大規模データ向け)
# 初期値 {} に対して、配列の各要素を1つずつ反映させる
echo "--- Reduce Result ---"
jq -r '
reduce .[] as $item ({};
.[$item.status | tostring] += 1
)
' "${LOG_FILE}"
# --- systemd タイマー連携用サンプル ---
# 定期実行する場合のユニット定義イメージ:
# [Unit]
# Description=Daily API Status Aggregator
#
# [Service]
# Type=oneshot
# ExecStart=/usr/local/bin/aggregate_logs.sh
# User=ops-user
【検証と運用】
正常系確認: スクリプトを実行し、標準出力にJSON形式で集計結果が表示されることを確認します。
bash aggregate_logs.sh
ログ確認:
systemdタイマー等で実行している場合は、以下のコマンドで実行履歴と標準エラー出力を確認します。journalctl -u daily-api-aggregator.service
【トラブルシューティングと落とし穴】
巨大なJSONによるOOM (Out of Memory):
jqはファイルをメモリに読み込みます。数GB単位のJSONを処理する場合は、jq --streamを検討するか、splitコマンド等で事前分割が必要です。権限問題: 一時ディレクトリ
/tmpがnoexecでマウントされている環境では、スクリプトの実行場所に注意が必要です。本スクリプトではmktempを使用して安全な領域を確保しています。データ型の不一致:
.statusが文字列と数値で混在している場合、group_byで予期せぬ分割が発生します。必ずtostringでキャストしてから処理することを推奨します。
【まとめ】 運用の冪等性と堅牢性を維持するための3つのポイント:
一時ファイルの厳格な管理:
trapを使用してゴミを残さない。型の明示的な変換:
jq内でのtostring等による正規化。適切な手法の選択: 小〜中規模は
group_byで可読性を、大規模はreduceでパフォーマンスを優先する。
この記事の更新履歴
この記事は、生成AIを活用した自動レビュー・更新フローにより内容を見直し、必要な修正を反映しています。
2026年9月19日
- 削除Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)という未確定の記述を削除しました。
- 変更jqのgroup_byとreduceを用いたログ集計スクリプトの実装と解説を実運用に即した内容に更新しました。

