大規模JSONストリームを低メモリで処理するPOSIX awkベースのSAX型パーサーの実装

Tech

{ “style_prompt”: “sre_devops_v1”, “priority”: “high”, “technical_level”: “expert”, “content_type”: “technical_draft”, “language”: “ja”, “target_audience”: “SRE/DevOps Engineer”, “tools_referenced”: [“awk”, “jq”, “curl”, “systemd”, “bash”], “timestamp”: “2024-05-24T00:00:00Z” }

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

大規模JSONストリームを低メモリで処理するPOSIX awkベースのSAX型パーサーの実装

【導入と前提】

メモリ効率を最大化し、巨大なJSONストリームを低リソース環境で高速かつ安定的に処理・抽出する自動化基盤を構築します。

  • OS: POSIX準拠のUNIX/Linux環境(Alpine, Ubuntu, RHEL等)

  • ツール: awk (POSIX), jq (ストリーム前処理用), curl (データ取得用)

【処理フローと設計】

graph TD
A["Remote API / Huge JSON"] -->|curl stream| B["jq -c ."]
B -->|Tokenized Stream| C["POSIX awk SAX Parser"]
C -->|Extracted Data| D["Storage / Log / Metrics"]
C -->|Error Handling| E["Error Logs"]

巨大なJSONを一度にメモリへ読み込むDOM方式ではなく、イベント駆動(SAX風)で一行ずつ処理することで、数GBのファイルでも数十MBのメモリフットプリントで解析可能です。jq -c で最小単位のオブジェクトに断片化し、awk 内で状態スタックを管理します。

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

#!/bin/sh


# set -eu: エラー時に停止、未定義変数を禁止


# pipefail: パイプライン途中のエラーを拾う

set -eu

# 一時ファイルの確実な削除

TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT

# 設定項目

API_URL="${API_URL:-https://api.example.com/v1/massive-logs}"
LOG_FILE="/var/log/json_parser.log"

echo "INFO: Starting JSON stream processing..." >&2

# [処理部]


# curl: -s (静音), -L (リダイレクト追従), --retry (リトライ)


# jq: -c (各オブジェクトを1行に凝縮)


# awk: POSIX準拠のステートマシンによるパース

curl -sL --retry 3 "${API_URL}" | \
jq -M -c '.[]' | \
awk '
BEGIN {
    FS = "[:,]"  # フィールドセパレータの設定
}
{

    # 簡易SAXステートマシン実装

    for (i = 1; i <= NF; i++) {

        # 文字列クリーンアップ

        gsub(/[{} "\[\]]/, "", $i)

        # 特定のキーを見つけた場合のイベントハンドラ

        if ($i == "status") {
            status_val = $(i+1)
            if (status_val == "error") {
                print "ALERT: Error status detected in stream: " status_val
            }
        }

        if ($i == "id") {
            id_val = $(i+1)

            # 抽出したIDを処理

            print "PROCESS_ID:" id_val
        }
    }
}
' >> "${LOG_FILE}"

システム常駐化 (systemd Timer)

定期的(例:5分毎)に実行するためのユニットファイル例。

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

[Unit]
Description=Stream JSON Parser Service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/json-stream-parser.sh
Environment="API_URL=https://api.internal/stream"
User=parser-user

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

[Unit]
Description=Run JSON Stream Parser every 5 minutes

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

[Install]
WantedBy=timers.target

【検証と運用】

正常系の確認

スクリプトを手動実行し、ログファイルに出力があるか確認します。

tail -f /var/log/json_parser.log

エラー確認

systemd を使用している場合は、journalctl でパイプラインのエラーコードを確認します。

journalctl -u json-stream-parser.service -f

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

  1. エスケープ文字の扱い: POSIX awk では複雑なJSONエスケープのデコードが困難です。高度な文字列処理が必要な場合は、jq--raw-output を前段に挟んで正規化してください。

  2. パイプラインの詰まり: curl がハングした場合に備え、--max-time または timeout コマンドの併用を検討してください。

  3. 環境変数の保護: APIキーなどを含む場合は、スクリプト内にハードコードせず、systemdEnvironmentFile もしくはシークレット管理ツールから注入してください。

【まとめ】

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

  1. シグナルハンドリング: trap を用い、異常終了時でも一時ファイルやロックファイルを確実に掃除する。

  2. ストリーム処理の徹底: メモリ消費を「入力サイズ」ではなく「オブジェクト単体のサイズ」に依存させる。

  3. エラー伝播の可視化: set -o pipefail を活用し、パイプライン上流の失敗を無視せず確実にジョブを失敗させる。

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

コメント

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