超巨大JSONを最小メモリで走査する:POSIX awkによるSAX型ストリーミング解析の自動化

Tech

{ “research”: [ “POSIX awk specifications”, “JSON streaming parsing techniques (SAX)”, “systemd timer/service units”, “Safe shell scripting (set -euo pipefail, trap)” ], “plan”: [ “Implement a character-based or delimiter-based tokenizer in POSIX awk”, “Design a robust Bash wrapper with error handling”, “Provide a systemd example for automation”, “Ensure portability and idempotency” ], “style_applied”: “SRE/DevOps professional tone, structured metadata, clear Mermaid diagrams” } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

超巨大JSONを最小メモリで走査する:POSIX awkによるSAX型ストリーミング解析の自動化

【導入と前提】

ギガバイト単位のJSONログを、システムメモリを圧迫せずに高速抽出・フィルタリングするストリーム処理基盤を構築します。

  • OS/実行環境: Linux (Debian/RHEL系)

  • 必須ツール: POSIX準拠 awk (mawk/gawk), bash 4.0+, curl, jq (バリデーション用)

【処理フローと設計】

データの全体をメモリにロードせず、1文字または1トークンずつ読み進めるSAX(Simple API for XML)ライクなアプローチを採用します。

graph TD
A["JSON Data Source/API"] -->|curl --silent| B["Stream Tokenizer"]
B -->|Token Pipeline| C["POSIX awk SAX Logic"]
C -->|Event: Match Key/Value| D["Data Extraction"]
D -->|Output| E["Local Log / Dashboard"]
C -->|Error State| F[Trap/Alert]

awk 内部では、現在のネスト深度(Depth)とパスをスタックとして管理し、特定のキーが出現した際のみアクションをトリガーします。

【実装:堅牢な自動化スクリプト】

#!/bin/bash


# stream_json_parser.sh - Memory-efficient JSON streaming parser

set -euo pipefail
trap 'echo "[ERROR] Line $LINENO: Command failed with exit code $?" >&2' ERR

# --- 設定項目 ---

API_URL="${1:-'http://localhost:8080/large-data.json'}"
TEMP_DIR="/tmp/json_parser"
mkdir -p "$TEMP_DIR"

# --- POSIX awk によるSAX風パーサー本体 ---


# このスクリプトはJSONをトークン化し、特定のパス(例: .items[].id)を抽出する

parse_json_stream() {
    awk -v target_key="id" '
    BEGIN {
        FS = "\"" # 文字列セパレータとしてダブルクォートを使用
        depth = 0
    }
    {

        # シンプルなトークナイズ(簡略化版)


        # 実際の実装では文字列内のエスケープ処理等を考慮する必要がある

        for (i = 1; i <= NF; i++) {
            token = $i
            if (token ~ /\{/) { depth++; }
            if (token ~ /\}/) { depth--; }

            # 前のフィールドがキー名で、現在が値であるか判定


            # 例: "id": "123" -> $i-1 が id

            if ($(i-1) == target_key) {

                # 次のフィールド(またはセパレータ後の値)を出力


                # セパレータ ":" を飛ばして値を取得

                sub(/^[[:space:]]*:[[:space:]]*/, "", $i)
                print $i
            }
        }
    }'
}

# --- メイン処理 ---

main() {
    echo "[INFO] Starting stream parsing from: $API_URL"

    # curlのオプション解説:


    # -f: HTTPエラー時に失敗させる


    # -L: リダイレクトを追跡


    # -s: 進捗表示を非表示


    # -S: エラー時はメッセージを表示

    curl -fLSs "$API_URL" | \
    jq -c . | \
    parse_json_stream

    echo "[INFO] Stream processing completed successfully."
}

main

systemd ユニットファイル設定例

定期的なログ収集と解析を自動化する場合のタイマー設定です。

# /etc/systemd/system/json-parser.service

[Unit]
Description=Stream JSON Parser Service
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/stream_json_parser.sh "https://api.example.com/metrics"
User=parser-user
Group=parser-user

# /etc/systemd/system/json-parser.timer

[Unit]
Description=Run JSON Parser every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

  1. 正常系の確認:

    # テスト用JSONを生成してパイプ渡し
    
    echo '{"status":"ok", "items":[{"id":"A1"}, {"id":"B2"}]}' | ./stream_json_parser.sh /dev/stdin
    
  2. ログの確認: systemd 経由で実行している場合は journalctl を使用します。

    journalctl -u json-parser.service -f
    

【トラブルシューティングと落とし穴】

  • 権限問題: /tmp/json_parser などの書き込み権限を確認してください。systemd で実行する場合は ProtectSystem=full 等のセキュリティ設定との整合性に注意が必要です。

  • 環境変数の保護: APIキーなどを含む場合は、スクリプト内にハードコードせず EnvironmentFile= を使用して systemd から読み込んでください。

  • JSONのエスケープ: POSIX awk で複雑なJSON(値の中にエスケープされた " がある場合など)を処理するのは限界があります。その場合は jq -c で一行形式に整形してから処理する、あるいは sed でトークナイズの前処理を行うのが定石です。

【まとめ】

運用の冪等性を維持するための3つのポイント:

  1. ステートレスな設計: 処理の成否をファイルの状態に依存させず、パイプラインの終了ステータスで判断する。

  2. リソース制限の強制: 巨大JSONを扱う際は curl --max-timeawk のメモリ制限を意識し、システム全体の死滅(OOM killer)を防ぐ。

  3. クリーンアップの徹底: trap コマンドにより、異常終了時でも一時ファイルや残存プロセスを確実に消去する。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました