<p><meta/>
{
“status”: “complete”,
“task”: “Advanced jq grouping and reduction techniques”,
“language”: “ja”,
“sre_level”: “senior”,
“verified”: false
}
</p>
<p>本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">JSONデータの集計を自動化する:jqのgroup_byとreduceを活用したログ解析パイプラインの構築</h1>
<h3 class="wp-block-heading">【導入と前提】</h3>
<p>APIレスポンスや構造化ログ(JSON)を読み込み、特定のキーでグループ化・集計する処理をシェルスクリプトで自動化・堅牢化します。</p>
<ul class="wp-block-list">
<li><strong>前提条件</strong>: Linux (Ubuntu/RHEL系), <code>jq</code> 1.6以上, <code>curl</code> インストール済み。</li>
</ul>
<h3 class="wp-block-heading">【処理フローと設計】</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["JSON Input"] --> B{Pre-process}
B -->|filter| C["group_by / reduce"]
C --> D["Aggregation Logic"]
D --> E["Structured Output"]
E --> F["Systemd/Timer for Automation"]
</pre></div>
<p>JSON配列を入力とし、<code>group_by</code>で同種データをまとめ、<code>reduce</code>で合計や平均といった統計量を算出します。その後、結果を構造化データとして出力し、継続的なモニタリングへ繋げます。</p>
<h3 class="wp-block-heading">【実装:堅牢な自動化スクリプト】</h3>
<p>以下は、HTTPステータスコードをグループ化し、レスポンス時間の合計を <code>reduce</code> で算出する実戦的なスクリプト例です。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/usr/bin/env bash
# ---------------------------------------------------------
# SRE/DevOps Script: JSON Aggregator
# ---------------------------------------------------------
set -euo pipefail # エラー発生時に停止、未定義変数参照禁止、パイプ内のエラー捕捉
IFS=$'\n\t'
# 一時ファイルの管理
TMP_DATA=$(mktemp /tmp/agg_data.XXXXXX.json)
trap 'rm -f "$TMP_DATA"' EXIT # 終了時に必ず削除
# 設定: 取得元URL(例: ダミーAPI)
TARGET_URL="https://api.example.com/v1/logs"
RETRY_COUNT=3
TIMEOUT_SEC=10
echo "--- Data Fetching ---"
# curlによるリトライ付きデータ取得
# -s: 進捗非表示, -S: エラー表示, -L: リダイレクト追従, -f: HTTPエラーで失敗扱い
if ! curl -sSLf --retry "${RETRY_COUNT}" --connect-timeout "${TIMEOUT_SEC}" "${TARGET_URL}" -o "$TMP_DATA"; then
echo "Error: Failed to fetch data from ${TARGET_URL}" >&2
exit 1
fi
echo "--- Aggregation Results ---"
# jqによる高度な集計
# 1. group_by(.status): ステータスコードごとに配列を分割
# 2. map(...): 各グループに対して集計を実行
# 3. reduce: 特定キー(例: response_time)の総和を算出
cat "$TMP_DATA" | jq '
group_by(.status) |
map({
status: .[0].status,
count: length,
total_response_time: (reduce .[] as $item (0; . + ($item.response_time // 0))),
average_response_time: (
if length > 0 then
(reduce .[] as $item (0; . + ($item.response_time // 0))) / length
else 0 end
)
})
'
# 補足: systemd-cat を使用して結果を journald に送る場合は、
# 上記の出力を | systemd-cat -t "json-aggregator" と連結する。
</pre>
</div>
<h4 class="wp-block-heading">systemd タイマーによる定期実行例</h4>
<p><code>/etc/systemd/system/json-aggregator.timer</code></p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=Run JSON Aggregation every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
</pre>
</div>
<h3 class="wp-block-heading">【検証と運用】</h3>
<ol class="wp-block-list">
<li><p><strong>正常系の確認</strong>:
スクリプトを手動実行し、期待されるJSON構造(<code>status</code>, <code>count</code>, <code>total_response_time</code>)が出力されるか確認します。
<code>bash script.sh | jq .</code></p></li>
<li><p><strong>ログの確認</strong>:
systemd経由で実行している場合、以下のコマンドで実行履歴と標準出力を確認します。
<code>journalctl -u json-aggregator.service</code></p></li>
<li><p><strong>空データ対応</strong>:
入力データが空の場合でも、<code>reduce</code> の初期値(<code>0</code>)や <code>if</code> 文によるゼロ除算回避が機能しているか検証します。</p></li>
</ol>
<h3 class="wp-block-heading">【トラブルシューティングと落とし穴】</h3>
<ul class="wp-block-list">
<li><p><strong>メモリ制限</strong>: <code>group_by</code> は内部的にソートを行うため、数GB単位の巨大なJSONファイルを扱うとメモリを消費します。その場合は <code>jq</code> のストリーム処理(<code>--stream</code>)を検討してください。</p></li>
<li><p><strong>環境変数の保護</strong>: APIキーなどを含むURLを使用する場合、スクリプトに直書きせず、<code>systemd</code> の <code>EnvironmentFile</code> または外部のシークレット管理(Vault等)から読み込んでください。</p></li>
<li><p><strong>パスの解決</strong>: <code>systemd</code> や <code>crontab</code> で実行する場合、<code>jq</code> や <code>curl</code> の実行パスが異なることがあります。必ずフルパス(<code>/usr/bin/jq</code>)で指定するか、スクリプト冒頭で <code>PATH</code> を明示してください。</p></li>
</ul>
<h3 class="wp-block-heading">【まとめ】</h3>
<p>運用の冪等性を維持するためのポイント:</p>
<ol class="wp-block-list">
<li><p><strong>ステートレスな設計</strong>: 一時ファイルは <code>trap</code> で確実に削除し、実行前後の環境差分を残さない。</p></li>
<li><p><strong>異常系の事前定義</strong>: <code>set -e</code> と <code>curl -f</code> により、不完全なデータに基づく誤った集計結果の出力を防ぐ。</p></li>
<li><p><strong>型安全性の考慮</strong>: <code>reduce</code> 処理時に <code>// 0</code>(デフォルト値)を使用し、欠落したキーによるNullエラーを回避する。</p></li>
</ol>
{
“status”: “complete”,
“task”: “Advanced jq grouping and reduction techniques”,
“language”: “ja”,
“sre_level”: “senior”,
“verified”: false
}
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
JSONデータの集計を自動化する:jqのgroup_byとreduceを活用したログ解析パイプラインの構築
【導入と前提】
APIレスポンスや構造化ログ(JSON)を読み込み、特定のキーでグループ化・集計する処理をシェルスクリプトで自動化・堅牢化します。
- 前提条件: Linux (Ubuntu/RHEL系),
jq 1.6以上, curl インストール済み。
【処理フローと設計】
graph TD
A["JSON Input"] --> B{Pre-process}
B -->|filter| C["group_by / reduce"]
C --> D["Aggregation Logic"]
D --> E["Structured Output"]
E --> F["Systemd/Timer for Automation"]
JSON配列を入力とし、group_byで同種データをまとめ、reduceで合計や平均といった統計量を算出します。その後、結果を構造化データとして出力し、継続的なモニタリングへ繋げます。
【実装:堅牢な自動化スクリプト】
以下は、HTTPステータスコードをグループ化し、レスポンス時間の合計を reduce で算出する実戦的なスクリプト例です。
#!/usr/bin/env bash
# ---------------------------------------------------------
# SRE/DevOps Script: JSON Aggregator
# ---------------------------------------------------------
set -euo pipefail # エラー発生時に停止、未定義変数参照禁止、パイプ内のエラー捕捉
IFS=$'\n\t'
# 一時ファイルの管理
TMP_DATA=$(mktemp /tmp/agg_data.XXXXXX.json)
trap 'rm -f "$TMP_DATA"' EXIT # 終了時に必ず削除
# 設定: 取得元URL(例: ダミーAPI)
TARGET_URL="https://api.example.com/v1/logs"
RETRY_COUNT=3
TIMEOUT_SEC=10
echo "--- Data Fetching ---"
# curlによるリトライ付きデータ取得
# -s: 進捗非表示, -S: エラー表示, -L: リダイレクト追従, -f: HTTPエラーで失敗扱い
if ! curl -sSLf --retry "${RETRY_COUNT}" --connect-timeout "${TIMEOUT_SEC}" "${TARGET_URL}" -o "$TMP_DATA"; then
echo "Error: Failed to fetch data from ${TARGET_URL}" >&2
exit 1
fi
echo "--- Aggregation Results ---"
# jqによる高度な集計
# 1. group_by(.status): ステータスコードごとに配列を分割
# 2. map(...): 各グループに対して集計を実行
# 3. reduce: 特定キー(例: response_time)の総和を算出
cat "$TMP_DATA" | jq '
group_by(.status) |
map({
status: .[0].status,
count: length,
total_response_time: (reduce .[] as $item (0; . + ($item.response_time // 0))),
average_response_time: (
if length > 0 then
(reduce .[] as $item (0; . + ($item.response_time // 0))) / length
else 0 end
)
})
'
# 補足: systemd-cat を使用して結果を journald に送る場合は、
# 上記の出力を | systemd-cat -t "json-aggregator" と連結する。
systemd タイマーによる定期実行例
/etc/systemd/system/json-aggregator.timer
[Unit]
Description=Run JSON Aggregation every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
【検証と運用】
正常系の確認:
スクリプトを手動実行し、期待されるJSON構造(status, count, total_response_time)が出力されるか確認します。
bash script.sh | jq .
ログの確認:
systemd経由で実行している場合、以下のコマンドで実行履歴と標準出力を確認します。
journalctl -u json-aggregator.service
空データ対応:
入力データが空の場合でも、reduce の初期値(0)や if 文によるゼロ除算回避が機能しているか検証します。
【トラブルシューティングと落とし穴】
メモリ制限: group_by は内部的にソートを行うため、数GB単位の巨大なJSONファイルを扱うとメモリを消費します。その場合は jq のストリーム処理(--stream)を検討してください。
環境変数の保護: APIキーなどを含むURLを使用する場合、スクリプトに直書きせず、systemd の EnvironmentFile または外部のシークレット管理(Vault等)から読み込んでください。
パスの解決: systemd や crontab で実行する場合、jq や curl の実行パスが異なることがあります。必ずフルパス(/usr/bin/jq)で指定するか、スクリプト冒頭で PATH を明示してください。
【まとめ】
運用の冪等性を維持するためのポイント:
ステートレスな設計: 一時ファイルは trap で確実に削除し、実行前後の環境差分を残さない。
異常系の事前定義: set -e と curl -f により、不完全なデータに基づく誤った集計結果の出力を防ぐ。
型安全性の考慮: reduce 処理時に // 0(デフォルト値)を使用し、欠落したキーによるNullエラーを回避する。
ライセンス:本記事のテキスト/コードは特記なき限り
CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。
コメント