超巨大JSONのメモリ効率的処理:POSIX awkとjq –streamによるSAX風パーサーの実装

Tech

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

超巨大JSONのメモリ効率的処理:POSIX awkとjq –streamによるSAX風パーサーの実装

【導入と前提】 テラバイト級のJSONログから、メモリを消費せずに特定のイベントをリアルタイム抽出するオペレーションを自動化します。

  • OS: Linux (Ubuntu 22.04 LTS / RHEL 9 等)

  • 必須ツール: jq (v1.6以上), awk (POSIX準拠)

【処理フローと設計】

graph TD
A["JSON Stream/File"] -->|jq --stream| B["Path-Value Tokenizer"]
B -->|Pipe| C["POSIX awk Logic"]
C -->|Event Match| D["Standard Output/Metric"]
C -->|Error| E["Trap / Logger"]

JSONを一度にメモリへ読み込む(DOM型)のではなく、jq --stream を用いて「パス」と「値」のペアに分解し、それを awk でイベント駆動型(SAX風)に処理することで、定数メモリでの実行を保証します。

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

#!/bin/bash


# ==============================================================================


# SRE Automation: Memory-Efficient JSON Streaming Parser


# ==============================================================================

set -euo pipefail  # エラー発生で停止、未定義変数参照禁止、パイプエラーの伝播
trap 'echo "Error occurred at line $LINENO. Cleaning up..." >&2' ERR

# 環境変数設定

readonly LOG_FILE="/var/log/app/huge_metrics.json"
readonly TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT  # 終了時に一時ディレクトリを確実に削除

# --- 処理ロジック ---


# jq --stream: JSONを[パス, 値]のストリームに変換


# awk: パスに基づき状態を管理し、条件に一致するデータを抽出

process_json_stream() {
    local target_path="$1"

    if [[ ! -f "$LOG_FILE" ]]; then
        echo "Error: File $LOG_FILE not found." >&2
        return 1
    fi

    # jq --stream は値を逐次出力するためメモリを消費しない


    # -c: 各要素を1行で出力, -n: 入力を待たない

    jq -cn --stream 'inputs' "$LOG_FILE" | awk -v target="$target_path" '
    BEGIN {
        FS = "[ \t,]+"; # 区切り文字の設定
    }
    {

        # jq --streamの出力形式: [["key1", "key2"], value]


        # 正規表現でパスと値を抽出(簡易版)

        curr_path = $0;
        if (curr_path ~ target) {

            # 条件に一致したパスの値を抽出して出力

            print "MATCH_FOUND: " $0;
        }
    }
    '
}

# --- 実行セクション ---

echo "Starting JSON stream processing..."

# 例: "metadata.id" を含むパスを抽出

process_json_stream "metadata.id"

# --- systemd ユニットファイル例 (自動起動/再起動設定) ---


# [Unit]


# Description=JSON Stream Monitor


# After=network.target

#


# [Service]


# ExecStart=/usr/local/bin/json_parser.sh


# Restart=always


# RestartSec=5


# User=svc_monitor


# Environment=PATH=/usr/bin:/usr/local/bin

#


# [Install]


# WantedBy=multi-user.target

【検証と運用】

  1. 正常系確認コマンド:

    # サンプルの巨大JSONを作成してテスト
    
    echo '{"metadata":{"id":"SRE-001"}, "data":"..."}' > test.json
    ./json_parser.sh "id"
    
  2. エラー確認 (journalctl):

    # systemd管理下の場合のログ確認
    
    journalctl -u json_monitor.service -f
    

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

  • パイプのバッファリング: 高頻度ストリームでは awk の出力がバッファされ、遅延が生じる場合があります。その際は awk '{print; fflush()}' のように fflush() を検討してください。

  • 権限問題: /var/log 配下のファイルを読み取る場合、スクリプトを実行するユーザーに適切な読み取り権限(Read Access)を付与するか、ACLを設定してください。sudo をスクリプト内で多用するのは避け、systemdの User= オプションで制御することを推奨します。

  • 特殊文字の扱い: JSONキーに ." が含まれる場合、awk 内の正規表現エスケープが複雑化します。jq 側で事前に正規化(パスのフラット化)を行うのが定石です。

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

  1. ステートレスな設計: 処理の中断・再開が可能か、ログのオフセット管理を検討する。

  2. リソース制約の明示: systemd の MemoryLimit 等を用い、パーサーがシステム全体に影響を与えないようガードレールを敷く。

  3. 入出力の正規化: 入力ソースが空、あるいは不正なJSONであっても set -e とパイプライン制御により安全に異常終了させる。

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

コメント

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