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

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

巨大なファイルに挑む:VBScriptで実現する「中断・再開(レジューム)」の極意

「マクロの記録」から一歩踏み出し、プログラミングの深淵へようこそ。

エンジニアとして現場に立つと、必ずと言っていいほど直面する壁があります。それは「巨大なデータ」との戦いです。数万行、数百万行、時には数千万行というテキストファイルを処理しようとしたとき、メモリ不足でエラーが出たり、処理の途中でPCがスリープしてしまったり……。

「あぁ、また最初からやり直しだ……」

そんな絶望を感じたことはありませんか? 今日、私はあなたにその絶望を希望に変える、「チェックポイント機構」という武器の授け方を伝授します。

これは単なるコードの書き方ではありません。プログラムの「状態(ステート)」をどう管理し、いかに効率的にリソースを扱うかという、アーキテクトとしての思考法です。

なぜ「普通」の読み込みでは失敗するのか?

初学者の多くは、ファイルを読み込むときにこう考えます。
「とりあえず全部読み込んで(`ReadAll`)、配列に入れてから処理すればいいよね」

これは、巨大ファイルにおける最大の禁忌(タブー)です。

1. メモリの爆発

`ReadAll` はファイルの内容をすべてメモリ上に展開しようとします。1GBのファイルがあれば、それだけでメモリを圧迫し、OSは悲鳴を上げ、最終的には `Out of Memory` エラーで強制終了します。

2. 「全か無か」の脆弱性

一度にすべてを読み込む手法では、99%まで進んだところでエラーが起きれば、その数時間の努力は水の泡です。

プロフェッショナルの流儀は、「一行ずつ(`ReadLine`)、少しずつ、着実に」進むことです。

解決策:チェックポイント機構の設計図

今回実装するのは、「どこまで処理したか」をメモしておく仕組みです。

1. チェックポイントファイル:現在の「行番号」を記録しておく小さなテキストファイル。
2. スキップ・ロジック:再開時、記録された行番号まで `ReadLine` で高速に読み飛ばす。
3. 定期的な保存:処理が進むごとに、一定間隔でチェックポイントを更新する。

処理フローのイメージ

[開始]
  ↓
[チェックポイントファイルを確認]
  ├─ なければ:0行目から開始
  └─ あれば :記録された行数まで「読み飛ばし」を実行
  ↓
[一行ずつ読み込み & 処理] ───┐
  ↓                         │ (一定行ごとに)
[チェックポイントを更新] <─────┘
  ↓
[終了]

実戦:レジューム機能付き・巨大テキストパース・エンジン

それでは、実際に動くコードを見ていきましょう。このコードは、そのままコピーして `.vbs` ファイルとして保存すれば動作するように設計しています。

' **
' 巨大ファイル・レジューム処理エンジン
' 
' 【概要】
' 巨大なテキストファイルを一行ずつ処理し、中断しても
' 前回終了時の行番号から再開できる仕組みを提供します。
' **

Option Explicit

' --- 設定エリア ---
Const TARGET_FILE    = "huge_data.txt"    ' 処理対象の巨大ファイル
Const CHECKPOINT_FILE = "checkpoint.txt"  ' 行番号を保存するファイル
Const SAVE_INTERVAL  = 1000               ' 何行ごとにチェックポイントを保存するか

' --- メイン処理 ---
Dim fso, targetFile, checkpointFile, ts, currentLine, lineCount, lastSavedLine
Dim startLine, i

Set fso = CreateObject("Scripting.FileSystemObject")

' 1. 処理対象ファイルの存在確認
If Not fso.FileExists(TARGET_FILE) Then
    MsgBox "エラー: ターゲットファイルが見つかりません: " & TARGET_FILE, vbCritical
    WScript.Quit
End If

' 2. チェックポイントの読み込み(再開位置の特定)
startLine = 0
If fso.FileExists(CHECKPOINT_FILE) Then
    Set checkpointFile = fso.OpenTextFile(CHECKPOINT_FILE, 1)
    If Not checkpointFile.AtEndOfStream Then
        startLine = CLng(checkpointFile.ReadLine) ' 記録された行番号を取得
    End If
    checkpointFile.Close
    ' WScript.Echo "レジュームを検知しました。行番号 " & startLine & " から再開します。"
End If

' 3. ファイルのオープン
Set targetFile = fso.OpenTextFile(TARGET_FILE, 1) ' 1 = ForReading
lineCount = 0
lastSavedLine = startLine

' 4. スキップ・フェーズ(再開時のみ実行)
' チェックポイントがある場合、その行までReadLineで読み飛ばします
If startLine > 0 Then
    Do While lineCount < startLine And Not targetFile.AtEndOfStream
        targetFile.ReadLine
        lineCount = lineCount + 1
    Loop
End If

' 5. メイン・プロセッシング・フェーズ
WScript.Echo "処理を開始します..."

On Error Resume Next ' エラーハンドリングの準備

Do While Not targetFile.AtEndOfStream
    currentLine = targetFile.ReadLine
    lineCount = lineCount + 1

    ' ---------------------------------------------------------
    ' 【ここに実際のパース処理を書く】
    ' 例: 特定の文字列が含まれているかチェックするなど
    ' ---------------------------------------------------------
    ' (デモ用に何もしないが、ここが心臓部です)
    ' ---------------------------------------------------------

    ' エラーチェック
    If Err.Number <> 0 Then
        WScript.Echo "致命的なエラーが発生しました。行番号: " & lineCount & vbCrLf & "エラー内容: " & Err.Description
        ' エラーが起きても、その時点の行番号を保存して終了する
        SaveCheckpoint checkpointFile, lineCount
        targetFile.Close
        WScript.Quit
    End If

    ' 6. チェックポイントの定期保存
    ' 毎回保存するとディスクI/Oがボトルネックになるため、一定間隔で行うのがプロの技
    If lineCount Mod SAVE_INTERVAL = 0 Then
        SaveCheckpoint checkpointFile, lineCount
    End If
Loop

' 7. 完了処理
targetFile.Close
' 正常終了時はチェックポイントを削除(または0にする)
If fso.FileExists(CHECKPOINT_FILE) Then fso.DeleteFile(CHECKPOINT_FILE)

WScript.Echo "すべての処理が正常に完了しました。"

' --- サブルーチン ---

' チェックポイントをファイルに書き出す
Sub SaveCheckpoint(fsoObj, lineNum)
    Dim ts
    ' 2 = ForWriting (上書きモード)
    Set ts = fsoObj.CreateTextFile(CHECKPOINT_FILE, True)
    ts.WriteLine lineNum
    ts.Close
End Sub

プロフェッショナルのための技術解説

このコードには、単に動くだけではない「設計の意図」がいくつか隠されています。

1. なぜ `Mod` を使って保存間隔を制御するのか?

`lineCount Mod SAVE_INTERVAL = 0` という条件式を見てください。
もし、1行ごとにチェックポイントファイルを書き出していたらどうなるでしょう?
ファイルを開く、書き込む、閉じる……という「ディスクI/O(入出力)」の負荷が膨大になり、処理速度が劇的に低下します。
「安全性(頻繁な保存)」と「速度(間隔を空ける)」のトレードオフを、この `SAVE_INTERVAL` でコントロールするのがアーキテクトの仕事です。

2. `On Error Resume Next` の使い所

VBScriptにおいて、エラーが発生した瞬間にプログラムが止まってしまうのは、レジューム機構においては致命的です。
エラーを検知した瞬間に、「今、どこまで終わったか」を即座に保存して、安全にプロセスを終了させる。 これが、次に再開したときの「確実なスタート地点」を保証します。

3. メモリ管理の極意

`targetFile.ReadLine` を使うことで、メモリ上には常に「現在処理中の1行」しか存在しません。これにより、ファイルが100GBあろうが1TBあろうが、このスクリプトのメモリ消費量はほぼ一定に保たれます。これこそが、スケーラブルな設計です。

まとめ:ここをクリアすれば、VBScriptの基本はバッチリですよ

今回の実装で学んだのは、以下の3点です。

1. リソースの限界を知る:巨大ファイルに `ReadAll` は厳禁。
2. 状態を外部に持たせる:チェックポイントによる「再開」の概念。
3. コストを意識する:ディスクI/Oを抑えるための定期保存。

これらの考え方は、VBScriptに限らず、PythonやJava、C++といったあらゆる言語に通じる「不変のエンジニアリング原則」です。

最初は難しく感じるかもしれません。でも、一度この「状態を管理する」感覚を掴んでしまえば、あなたの書くコードは「単なるマクロ」から「信頼性の高いシステム」へと進化します。

一歩ずつ、着実に進んでいきましょう。応援していますよ!

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