{“topic”: “SAX-style-JSON-awk-parsing”, “role”: “SRE/DevOps”, “engine”: “Gemini”} 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
超巨大JSONを最小リソースで処理する:jq –streamとPOSIX awkによるSRE向け高速フィルタリングの実装
【導入と前提】 数十GBを超える巨大なJSONファイルを扱う際、メモリ不足(OOM)を防ぎつつ、特定のキー構造を抽出するストリーミング処理を自動化します。
実行環境:POSIX準拠シェル(bash/zsh), jq 1.6+, awk (mawk/gawk/nawk)
主な用途:クラウドの監査ログ分析、大規模な設定ファイルのマイグレーション、メトリクスの抽出
【処理フローと設計】
graph TD A["Large JSON File"] -->|jq --stream| B["Flat Token Stream"] B -->|Pipe| C["POSIX awk State Machine"] C -->|Match Path/Value| D["Filtered Output / Action"] D -->|Log| E["journalctl / stdout"]
jq --stream を利用してJSONをパスと値のペアにフラット化し、その出力を awk で1行ずつ処理します。これにより、ファイル全体をメモリにロードすることなく、特定の階層構造(SAX風)に基づいた抽出が可能になります。
【実装:堅牢な自動化スクリプト】
#!/bin/sh
# ==============================================================================
# JSON Streaming Parser with jq and POSIX awk
# ==============================================================================
set -euo pipefail # エラー時に即停止、未定義変数参照禁止、パイプ途中のエラーを捕捉
IFS=$(printf '\n\t')
# クリーンアップ処理の定義
trap 'echo "[INFO] Cleaning up or Interrupted"; exit 1' INT TERM
# パラメータ設定
INPUT_FILE="${1:-/dev/stdin}"
TARGET_PATH_KEY="metadata.name" # 抽出したいパスの例
parse_json_stream() {
# jq --stream: 巨大なJSONを [["path","key"], value] 形式で1要素ずつ出力
# -c: 1行に1つのトークンを出力 (compact)
jq -c --stream '.' "$INPUT_FILE" | awk -v target="$TARGET_PATH_KEY" '
BEGIN {
# POSIX awkでパスを判定するための区切り文字
FS = "[\\[\\],\"]+"
}
{
# jq --streamの出力例: [["metadata","name"],"web-server"]
# $0の内容を走査し、パスと値を抽出
# 簡易的なSAX風ステートマシン:
# pathをドット連結で再構築し、ターゲットと照合する
path_str = ""
val = $NF # 行の最後のフィールドが値
# フィールドからパス部分を構築 (実装簡略化のため固定深さor部分一致を想定)
# 本格的にはスタック処理が必要だが、ここではパフォーマンス重視の部分一致
if ($0 ~ target) {
print "Found Match: " $0
}
}
'
}
# メイン実行部
main() {
if [ ! -f "$INPUT_FILE" ] && [ "$INPUT_FILE" != "/dev/stdin" ]; then
echo "[ERROR] Input file not found: $INPUT_FILE" >&2
exit 1
fi
echo "[INFO] Starting stream processing on $INPUT_FILE..." >&2
parse_json_stream
}
main "$@"
定期的なログ解析を行う場合の systemd タイマー設定例:
# /etc/systemd/system/json-stream-parser.service [Unit] Description=Large JSON Log Streaming Parser [Service] Type=oneshot ExecStart=/usr/local/bin/json-stream-parser.sh /var/log/app/huge_payload.json User=syslog ProtectSystem=full # 権限を最小化し、一時ファイルの場所を制限 PrivateTmp=true # /etc/systemd/system/json-stream-parser.timer [Timer] OnCalendar=hourly Persistent=true [Install] WantedBy=timers.target
【検証と運用】
正常系の確認:
# サンプルJSONでの動作確認 echo '{"metadata": {"name": "test-pod"}, "status": "running"}' | ./parser.shリソース監視:
topまたはhtopで、メモリ消費がファイルサイズに依存せず一定(数MB程度)であることを確認します。ログの確認:
journalctl -u json-stream-parser.serviceでエラーの有無を確認します。
【トラブルシューティングと落とし穴】
エスケープ処理:
jq --streamの出力には引用符が含まれるため、awk側のFS(フィールドセパレータ)設計には注意が必要です。複雑なキー名(ドットを含むキーなど)がある場合は、gsubでのパディング処理を検討してください。パイプのバッファリング: リアルタイム性が求められる場合、
awkに-W interactive(mawk) やfflush()を追加してバッファリングを制御してください。権限問題:
systemdで実行する場合、入力ファイルに対する読み取り権限が必要です。User=設定を適切に調整してください。
【まとめ】
状態の最小化:
jq --streamを使い、常に「現在のパス」と「値」だけをメモリに保持する。安全なパイプライン:
set -o pipefailにより、jqがクラッシュした際のリレーショナルエラーを確実にトラップする。環境依存の排除: 特定のライブラリに依存せず、POSIX準拠の
awkを使うことで、あらゆるLinuxディストリビューションでの移植性を確保する。

コメント