{ “engine”: “Gemini-1.5-Pro”, “mode”: “SRE-DevOps-Draft”, “focus”: “POSIX-awk-SAX-JSON”, “safety_level”: “Strict-Shell-Check” } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
POSIX awkとストリーム処理による大規模JSONのメモリ節約型抽出パイプライン
【導入と前提】
本構成は、数GB規模の巨大なJSONファイルをメモリにロードせず、POSIX準拠の awk を用いてSAX風(イベント駆動型)に高速処理する運用を自動化します。
解決する課題: メモリ不足によるOOM Killerの回避、jq単体での複雑な状態遷移処理の簡素化。
前提条件:
OS: GNU/Linux (Ubuntu/RHEL/Debian) または Alpine Linux
ツール:
jq(トークナイザーとして利用),awk(POSIX準拠),curl
【処理フローと設計】
graph TD A["Large JSON Source"] -->|curl/cat| B["jq --stream"] B -->|Flat Path-Value Stream| C["POSIX awk State Machine"] C -->|Filter/Aggregate| D["Cleaned Data/Alert"] D -->|Output| E[Logs/Metrics]
入力層:
jq --streamを利用し、JSONツリーを深さ優先探索のパスと値のペアにフラット化します。処理層:
awkが現在のパスを内部変数(State)として保持し、特定の条件に合致したタイミングでアクションを実行します(SAX方式)。出力層: 必要なフィールドのみを抽出し、後続のDBインサートや監視ツールへ渡します。
【実装:堅牢な自動化スクリプト】
#!/bin/sh
# ==============================================================================
# JSON Streaming Parser with POSIX awk
# Description: Processes massive JSON using jq-stream and awk state machine.
# ==============================================================================
set -eu
# 常にクリーンな環境を保つための終了処理
trap 'rm -f "$TMP_FILE"' EXIT
TMP_FILE=$(mktemp)
# 設定: 抽出対象のキー
TARGET_KEY="metadata.labels.app"
process_json_stream() {
local input_src="$1"
# jq --stream は JSON を [["path","to"], value] の形式で1行ずつ出力する
# これにより、巨大なJSONもメモリを消費せずにスキャン可能
cat "$input_src" | \
jq -c --stream 'select(length == 2)' | \
awk -F'[,\[\]\"]+' '
# [["path","to","key"], "value"] の形式をパース
# POSIX awk では動的正規表現や配列操作に制限があるためシンプルに保つ
{
# パスを結合して現在のコンテキストを把握
path = ""
for (i=2; i<NF-1; i++) {
if ($i != "") {
path = (path == "" ? $i : path "." $i)
}
}
value = $(NF-1)
# 特定の条件(SAXイベント相当)で処理を実行
if (path ~ /'${TARGET_KEY}'/) {
print "Found '"${TARGET_KEY}"': " value
}
}
'
}
# 実行例: curlのリトライ処理を含めた堅牢な取得
main() {
local api_url="http://localhost:8080/v1/massive-data.json"
echo "[INFO] Starting JSON stream processing..."
# -s: 進捗非表示, -S: エラー表示, -L: リダイレクト追従, --retry: ネットワークエラー時再試行
if ! curl -sSL --retry 3 "$api_url" -o "$TMP_FILE"; then
echo "[ERROR] Failed to fetch data from $api_url" >&2
exit 1
fi
process_json_stream "$TMP_FILE"
}
main "$@"
補足:systemdによる定期実行(Timer)例
/etc/systemd/system/json-parser.service
[Unit] Description=Stream JSON Parser Service After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/json-parser.sh User=parser-user # リソース制限の付与 MemoryLimit=512M
【検証と運用】
1. 正常系の確認
モックデータを作成し、パイプラインが正しくパスを解釈できるかテストします。
echo '{"metadata": {"labels": {"app": "sre-tool"}}}' | jq -c --stream 'select(length == 2)'
# 出力例: [["metadata","labels","app"],"sre-tool"]
2. ログ確認
systemd経由で実行している場合、以下のコマンドで進捗およびエラーを確認します。
journalctl -u json-parser.service -f
【トラブルシューティングと落とし穴】
エスケープ文字の扱い:
awkのフィールドセパレータ-Fに指定する文字(例:")がデータ内に含まれる場合、パス解析がずれる可能性があります。厳密な処理が必要な場合は、jq側でパスを一度ユニークな区切り文字(例:\t)に置換してからawkに渡してください。
権限問題:
- スクリプトが
/tmpに書き込む権限があるか、実行ユーザーにcurlやjqの実行パスが通っているか確認してください。
- スクリプトが
シグナルハンドリング:
set -eを使用しているため、パイプライン途中のgrepなどが 0 件ヒットで終了するとスクリプト全体が停止します。意図的な空ヒットを許容する場合は|| trueを活用してください。
【まとめ】
運用の冪等性と堅牢性を維持するための3つのポイント:
ステートレスな設計: 一時ファイルは
trapで必ず削除し、前回の実行状態に依存しないようにする。トークナイズの分離:
jqに構造解析(字句解析)を任せ、awkはビジネスロジック(状態遷移)に専念させることで、POSIX準拠でも複雑な処理が可能になる。リソース制約の明示:
systemdやcgroupsを併用し、万が一の無限ループやメモリリーク時にもシステム全体を保護する。


コメント