【実務・中級編】【上級プロフェッショナル向け】FSOでの巨大テキストパース時に処理途中の「行番号・バイト位置」を保存し障害発生後に再開(レジューム)できるチェックポイント機構 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

巨大なデータという「深淵」を制する:VBScriptにおけるレジューム(再開)機構の設計思想

業務自動化の現場において、我々が直面する真の課題は「数行のデータ処理」ではない。「数千万行、数十GBに及ぶ、失敗が許されない巨大なデータストリームをいかに制御するか」である。

数時間に及ぶバッチ処理の最中に、ネットワークの瞬断、メモリ不足、あるいは予期せぬシステム再起動が発生したとする。その時、君のスクリプトはどう動く? ゼロからやり直すのか? それとも、数時間の労力をドブに捨てるのか?

もし後者なら、君の設計はプロフェッショナルとは呼べない。

本稿では、FileSystemObject (FSO) を用いた巨大テキストパースにおいて、処理の「中断」と「再開(レジューム)」を可能にするチェックポイント機構の実装手法を伝授する。これは単なるテクニックではない。計算資源と時間を守るための、アーキテクトとしての責務である。

1. 設計思想:なぜ「行番号」を追うのか

巨大なファイルを扱う際、初心者はよく「ファイルをメモリに一括で読み込む(`ReadAll`)」という致命的なミスを犯す。これはメモリ不足(Out of Memory)への最短ルートだ。

プロフェッショナルは、FSOの `TextStream` を用いて、一行ずつストリームとして読み込む。しかし、これだけでは「中断」に対応できない。そこで必要になるのが、「どこまで処理したか」という状態(State)の外部保存である。

チェックポイントの設計原則

1. 軽量性: チェックポイントファイル自体が巨大であってはならない。保存すべきは「最後に成功した行番号」という最小限の情報のみとする。
2. アトミック性(原子性): 処理中のエラーでチェックポイントが壊れることを防ぐため、書き込みは「処理の区切り(一定行数ごと)」に行う。
3. 副作用の分離: データの処理ロジックと、進捗の記録ロジックを分離し、保守性を高める。

2. 実装:レジューム機能付きテキストパーサー

以下に、プロダクション環境での利用を想定した、堅牢なVBScriptのコード例を示す。このコードは、処理中にエラーが発生しても、次回実行時に前回の終了地点から自動的に再開する。

' ==============================================================================
' Module: ResumableTextProcessor
' Description: 巨大テキストファイルを一行ずつ処理し、中断時にレジューム可能にする
' Author: Chief Architect
' ==============================================================================
Option Explicit

' --- 設定定数 ---
Const TARGET_FILE      = "C:\Data\huge_log_file.txt"    ' 対象の巨大ファイル
Const CHECKPOINT_FILE  = "C:\Data\process_state.ckpt"   ' 進捗保存ファイル
Const BATCH_SIZE       = 1000                           ' 何行ごとにチェックポイントを保存するか

' --- メインルーチン ---
Main

Sub Main()
    Dim fso, targetStream, lastProcessedLine, currentLine, lineCount
    Dim isResuming, errCode
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' 1. チェックポイントの読み込み(レジューム判定)
    lastProcessedLine = 0
    isResuming = False
    
    If fso.FileExists(CHECKPOINT_FILE) Then
        lastProcessedLine = ReadCheckpoint(fso)
        isResuming = True
        WScript.Echo ">>> レジュームモード: 行 " & lastProcessedLine & " から再開します。"
    Else
        WScript.Echo ">>> 通常モード: 最初から処理を開始します。"
    End If

    ' 2. 対象ファイルのオープン
    If Not fso.FileExists(TARGET_FILE) Then
        WScript.Echo "Error: 対象ファイルが見つかりません。"
        WScript.Quit 1
    End If

    Set targetStream = fso.OpenTextFile(TARGET_FILE, 1) ' ForReading
    
    On Error Resume Next ' エラーハンドリング開始

    lineCount = 0
    
    ' 3. 処理ループ
    Do Until targetStream.AtEndOfStream
        lineCount = lineCount + 1
        Dim currentLineText
        currentLineText = targetStream.ReadLine
        
        ' --- レジュームロジック: 既処理の行はスキップ ---
        If lineCount <= lastProcessedLine Then
            ' スキップ中は負荷を最小限にする
        Else
            ' ---------------------------------------------------------
            ' ここに実際のビジネスロジックを記述する
            ' ---------------------------------------------------------
            ProcessBusinessLogic currentLineText, lineCount
            ' ---------------------------------------------------------
            
            ' 4. 定期的なチェックポイント保存 (Batching)
            ' 毎回保存するとI/O負荷が高すぎるため、一定行数ごとに書き出す
            If lineCount Mod BATCH_SIZE = 0 Then
                WriteCheckpoint fso, lineCount
            End If
        End If

        ' エラー検知
        If Err.Number <> 0 Then
            errCode = Err.Number
            WScript.Echo "!!! 致命的なエラーが発生しました (Line: " & lineCount & ", Error: " & Err.Description & ")"
            ' エラー時は現在の行までを保存して終了(次回はこの行から再開)
            WriteCheckpoint fso, lineCount
            targetStream.Close
            WScript.Quit errCode
        End If
    Loop

    ' 5. 正常終了処理
    targetStream.Close
    If fso.FileExists(CHECKPOINT_FILE) Then fso.DeleteFile(CHECKPOINT_FILE) ' 完了したらチェックポイントを削除
    WScript.Echo ">>> 処理が正常に完了しました。"
    On Error GoTo 0
End Sub

' --- ビジネスロジックの器 ---
Sub ProcessBusinessLogic(ByVal text, ByVal lineNum)
    ' ここに加工・DB登録などの処理を記述
    ' テスト用に、極めて稀にエラーを発生させるシミュレーション
    ' If lineNum = 5500 Then Err.Raise 999 
End Sub

' --- チェックポイント読み込み ---
Function ReadCheckpoint(fso)
    Dim ts, val
    val = 0
    On Error Resume Next
    Set ts = fso.OpenTextFile(CHECKPOINT_FILE, 1)
    If Not ts.AtEndOfStream Then
        val = CLng(ts.ReadLine)
    End If
    ts.Close
    ReadCheckpoint = val
    On Error GoTo 0
End Function

' --- チェックポイント書き込み ---
Sub WriteCheckpoint(fso, lineNum)
    Dim ts
    ' 書き込み時は一時ファイルを作成して置換する手法が最も安全(アトミック性)だが、
    ' VBScriptの標準範囲では、上書きモードでシンプルに実装する。
    Set ts = fso.CreateTextFile(CHECKPOINT_FILE, True)
    ts.WriteLine lineNum
    ts.Close
    WScript.Echo "--- Checkpoint saved at line: " & lineNum & " ---"
End Sub

3. アーキテクトの視点:堅牢性を高めるための深層解説

このコードを単なる「動くスクリプト」で終わらせてはいけない。以下の設計意図を理解せよ。

A. スキップ・アルゴリズムの計算量

`If lineCount <= lastProcessedLine` によるスキップは、一見すると非効率に見えるかもしれない。しかし、FSOにおいて「ファイル内の特定の行へジャンプする(Seek)」ことは、行の長さが可変である以上、不可能である。 したがって、「読み飛ばす(ReadLineを繰り返す)」ことが、VBScriptにおける最も確実で、かつメモリ効率の高いスキップ手法となる。

B. バッチ・コミット(`BATCH_SIZE`)の重要性

もし、1行ごとにチェックポイントを書き出していたらどうなるか?
数千万行の処理において、ディスクI/Oは致命的なボトルネックとなる。`BATCH_SIZE` を導入することで、「パフォーマンス(I/O負荷の軽減)」と「堅牢性(失われる作業量の最小化)」のトレードオフを最適化している。実務では、処理の重さに応じてこの値を 1,000 〜 10,000 程度で調整せよ。

C. エラーハンドリングと状態の整合性

`On Error Resume Next` を使用する場合、エラーが発生した瞬間に「現在の状態を即座に保存して、安全にプロセスを終了させる」ことが不可欠だ。
本実装では、エラー検知時に `WriteCheckpoint` を呼び出している。これにより、再起動した際には「エラーが起きた行」から再開でき、無限ループや、同じエラー箇所での停止を回避できる。

D. 完了後のクリーンアップ

処理が完全に終了した際、チェックポイントファイルを削除することを忘れてはならない。これを忘れると、次に「全く新しいファイル」を処理しようとした際、古い進捗が残っているために、処理がスキップされるという、悪夢のようなバグを引き起こす。

4. まとめ:プロフェッショナルの流儀

自動化エンジニアの価値は、コードの短さではなく、「例外に対する予見性」で決まる。

「もし停電したら?」「もしネットワークが切れたら?」
この問いに対して、ロジカルなレジューム機構を用意できているか。この設計思想こそが、現場の信頼を勝ち取るための武器となる。

次に巨大なファイルに向き合う時は、このチェックポイント機構を、君の標準的なテンプレートとして組み込んでおくべきだ。

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