<p><style_prompt>
Target Readership: SRE, DevOps Engineers, Infrastructure Automation Developers
Tone: Professional, Technical, Practical, Authoritative
Keywords: jq, JSON, group_by, reduce, Bash, Shell Script, Automation, SRE
</style_prompt></p>
<p>本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">APIログやメトリクスJSONをjqのgroup_byとreduceで高速集計・自動化するシェルスクリプト</h1>
<h2 class="wp-block-heading">【導入と前提】</h2>
<p>APIレスポンスの巨大なJSON配列をjqで効率的にグループ化・集計し、ログ分析やモニタリングメトリクスの抽出を自動化します。</p>
<h3 class="wp-block-heading">前提条件</h3>
<ul class="wp-block-list">
<li><p><strong>OS</strong>: Linux (Ubuntu 22.04 LTS / RHEL 9 等) または macOS</p></li>
<li><p><strong>必要ツール</strong>:</p>
<ul>
<li><p><code>jq</code> (v1.6以上を推奨)</p></li>
<li><p><code>curl</code> (7.68.0以上)</p></li>
<li><p><code>bash</code> (v4.0以上)</p></li>
</ul></li>
</ul>
<hr/>
<h2 class="wp-block-heading">【処理フローと設計】</h2>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
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"]
</pre></div>
<hr/>
<h2 class="wp-block-heading">【実装:堅牢な自動化スクリプト】</h2>
<p>以下は、リモートエンドポイントからHTTPリクエストログ(JSON配列)を取得し、<code>group_by</code> と <code>reduce</code> を用いてサービスごとの「リクエスト数」「平均レイテンシ」「エラー率」を集計・出力するシェルスクリプトです。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/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}"
</pre>
</div>
<h3 class="wp-block-heading">定期実行用 systemd 設定例</h3>
<p>スクリプトを5分ごとに自動実行するための設定例です。</p>
<p><code>/etc/systemd/system/jq-aggregate.service</code>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">[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
</pre>
</div>
<p><code>/etc/systemd/system/jq-aggregate.timer</code>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=Run jq aggregate script every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
</pre>
</div><hr/>
<h2 class="wp-block-heading">【検証と運用】</h2>
<h3 class="wp-block-heading">1. 正常系テスト</h3>
<p>テスト用のサンプルデータを標準入力から流し込み、結果を確認します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic"># テストデータの作成と集計の確認
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
</pre>
</div>
<h3 class="wp-block-heading">2. ログ確認</h3>
<p>systemdタイマーおよびスクリプトの実行ログを <code>journalctl</code> で確認します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic"># タイマーの稼働状況を確認
systemctl list-timers jq-aggregate.timer
# 直近の実行ログを確認
journalctl -u jq-aggregate.service -n 50 --no-pager
</pre>
</div><hr/>
<h2 class="wp-block-heading">【トラブルシューティングと落とし穴】</h2>
<ol class="wp-block-list">
<li><p><strong><code>group_by</code> のメモリ消費量(大容量データの扱い)</strong></p>
<ul>
<li><p><strong>問題</strong>: <code>group_by</code> は配列全体をソートしてメモリ上に保持するため、数十万件を超える超巨大なJSONファイルではメモリ不足(OOM)を起こす可能性があります。</p></li>
<li><p><strong>対策</strong>: 単一の巨大配列には <code>group_by</code> ではなく、全要素を直接 <code>reduce .[] as $item ({}; ...)</code> でストリーム処理のように辞書(マップ)オブジェクトへ集約する方法に切り替えてください。</p></li>
</ul></li>
<li><p><strong>権限と一時ファイルのクリーンアップ</strong></p>
<ul>
<li><p><strong>問題</strong>: 一時ファイル(<code>mktemp</code>)の削除漏れや、出力先ディレクトリへの書き込み権限不足。</p></li>
<li><p><strong>対策</strong>: スクリプト冒頭で <code>trap 'rm -f ...' EXIT INT TERM</code> を必ず宣言し、スクリプトが途中で異常終了(SIGINT/SIGTERM)した場合でも一時ファイルが残留しないように設定します。</p></li>
</ul></li>
<li><p><strong>シークレットや環境変数の漏洩防止</strong></p>
<ul>
<li><p><strong>問題</strong>: <code>curl</code> の認証ヘッダー等にトークンを含める際、プロセステーブル経由でコマンドライン引数が露出するリスク。</p></li>
<li><p><strong>対策</strong>: APIキーなどは環境変数から読み込むか、<code>curl --config</code> や <code>-H @file</code> を使用してファイルから安全に読み込みます。</p></li>
</ul></li>
</ol>
<hr/>
<h2 class="wp-block-heading">【まとめ】</h2>
<p>運用の冪等性と堅牢性を維持するための3つのポイント:</p>
<ol class="wp-block-list">
<li><p><strong>アトミックな書き込み</strong>: 処理結果を一度 <code>.tmp</code> ファイルに出力してから <code>mv</code> で置換し、読み込み側が不完全なファイルを読み込むリスクを回避する。</p></li>
<li><p><strong><code>trap</code> と <code>set -euo pipefail</code> による厳格なエラー制御</strong>: 途中でエラーが発生した際に即座に停止し、ゴミファイルの後始末を徹底する。</p></li>
<li><p><strong>データ規模に応じたjq構造の選択</strong>: 小〜中規模配列には可読性の高い <code>group_by + reduce</code> を使い、超大規模データには直接 <code>reduce</code> を用いてメモリフットプリントを最小化する。</p></li>
</ol>
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で効率的にグループ化・集計し、ログ分析やモニタリングメトリクスの抽出を自動化します。
前提条件
【処理フローと設計】
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 のメモリ消費量(大容量データの扱い)
権限と一時ファイルのクリーンアップ
シークレットや環境変数の漏洩防止
【まとめ】
運用の冪等性と堅牢性を維持するための3つのポイント:
アトミックな書き込み: 処理結果を一度 .tmp ファイルに出力してから mv で置換し、読み込み側が不完全なファイルを読み込むリスクを回避する。
trap と set -euo pipefail による厳格なエラー制御: 途中でエラーが発生した際に即座に停止し、ゴミファイルの後始末を徹底する。
データ規模に応じたjq構造の選択: 小〜中規模配列には可読性の高い group_by + reduce を使い、超大規模データには直接 reduce を用いてメモリフットプリントを最小化する。
コメント