【実務・中級編】【上級プロフェッショナル向け】ディスクIOPS過負荷を防止するスロットリング(Sleep制御)付き大容量ファイル転送エンジン – VBScript (Visual Basic Scripting Edition)解析バイブル

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

【極限の知見】FSOの「CopyFile」を捨てろ。IOPS負荷を制御するスロットリング型転送エンジンの設計

業務自動化の現場で、多くのエンジニアが陥る「致命的な罠」がある。それは、「FSO(FileSystemObject)の `CopyFile` メソッドを、大規模なデータ移行やバックアップ処理にそのまま使ってしまうこと」だ。

小規模な設定ファイルのコピーなら、それで十分だろう。しかし、数GB、数十GBのログファイルやデータベースのダンプファイルを、共有ストレージ(NAS)や高負荷なサーバー環境でコピーした瞬間、そのコードは「自動化ツール」から「システム障害の引き金」へと変貌する。

なぜか? 答えは単純だ。`FSO.CopyFile` は「ブラックボックスなアトミック操作」だからだ。

OSレベルで全力のI/Oリソースを使い切り、ディスクIOPS(Input/Output Operations Per Second)をスパイクさせる。その結果、同じストレージを参照している基幹データベースのレスポンスが低下し、システム全体がハングアップする。これが、プロフェッショナルが最も忌み嫌う「リソース競合(Resource Contention)」である。

本稿では、単なるファイルコピーを超えた、「システムの平穏を保ちながら、確実にデータを運ぶ」ためのスロットリング(流量制御)付き転送エンジンの設計思想と実装を伝授する。

1. なぜ「CopyFile」ではいけないのか:設計思想の転換

プロフェッショナルな設計において、重要なのは「目的(ファイルをコピーすること)」だけではない。「副作用(I/O負荷)をどう制御するか」が設計の成否を分ける。

標準的なFSOの限界

  • 制御不能なスループット: OSのスケジューラに任せきりであり、スクリプト側から「秒間10MBに抑える」といった制御が一切できない。
  • 進捗管理の欠如: コピーが始まってから終わるまで、スクリプトは「待ち」の状態になり、進捗率(%)を算出できない。
  • エラーリカバリの困難さ: 巨大なファイルのコピー中にネットワークが瞬断した場合、最初からやり直しとなる。

本稿が提示する「ストリーム・スロットリング」戦略

我々が構築すべきは、ファイルを巨大な塊として扱うのではなく、「小さなチャンク(塊)に分割し、各チャンクの書き込み後に意図的な待機(Sleep)を挟む」という、ストリームベースの制御モデルである。

2. アーキテクチャ・ブループリント

堅牢な転送エンジンを構築するために、以下の3層構造で設計する。

1. I/Oストリーム層: `ADODB.Stream` を活用し、バイナリデータをチャンク単位で読み書きする。
2. スロットリング層: 読み書きしたデータ量に基づき、`WScript.Sleep` を動的に制御する。
3. レジリエンス層: エラーハンドリングとログ出力を分離し、異常時に「どこまで完了したか」を特定可能にする。

3. プロダクション・コード例:ThrottledFileTransferEngine

以下に、実務でそのまま利用可能なレベルまで磨き上げたVBScriptコードを示す。
注意点として、VBScriptのFSO単体ではバイナリの細かなチャンク操作が困難なため、Windows環境で最も信頼性の高い `ADODB.Stream` をエンジンとして採用している。

'==============================================================================
' ThrottledFileTransferEngine (VBScript)
' 
' 【概要】
' I/O負荷を制御(スロットリング)しながら大容量ファイルをコピーする。
' 
' 【設計思想】
' - ADODB.Streamによるバイナリチャンク転送
' - チャンクごとのSleep制御によるIOPSスパイクの抑制
' - 進捗表示とエラーハンドリングの統合
'==============================================================================

Option Explicit

' --- 設定値 ---
Const CHUNK_SIZE = 1048576 ' チャンクサイズ: 1MB (メモリ負荷とI/O効率のバランス)
Const SLEEP_MS  = 50       ' チャンクごとの待機時間 (ミリ秒)
' --------------

Dim objFSO, objShell
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")

' 実行テスト
Dim src, dest
src  = "C:\Temp\LargeData.zip"   ' コピー元
dest = "D:\Backup\LargeData.zip" ' コピー先

Call ExecuteThrottledCopy(src, dest)

'------------------------------------------------------------------------------
' メインルーチン: スロットリング付きコピー実行
'------------------------------------------------------------------------------
Sub ExecuteThrottledCopy(strSrc, strDest)
    Dim objSrcStream, objDestStream
    Dim lngFileSize, lngBytesRead, lngTotalBytesCopied
    Dim lngProgress, strProgressMsg

    On Error Resume Next

    ' 1. 事前チェック
    If Not objFSO.FileExists(strSrc) Then
        WScript.Echo "Error: Source file not found -> " & strSrc
        Exit Sub
    End If

    lngFileSize = objFSO.GetFile(strSrc).Size
    
    ' 2. ストリームの初期化 (バイナリモード)
    Set objSrcStream = CreateObject("ADODB.Stream")
    Set objDestStream = CreateObject("ADODB.Stream")

    objSrcStream.Open
    objSrcStream.Type = 1 ' adTypeBinary
    objSrcStream.LoadFromFile strSrc

    objDestStream.Open
    objDestStream.Type = 1 ' adTypeBinary
    ' 書き込み先が存在する場合の処理(新規作成モード)
    objDestStream.Open

    ' 3. 転送ループ
    lngTotalBytesCopied = 0
    
    Do Until objSrcStream.EOS
        ' チャンクの読み込み
        ' ※ADODB.StreamのReadは、残りのデータがCHUNK_SIZEより小さければその分だけ返す
        Dim binChunk
        binChunk = objSrcStream.Read(CHUNK_SIZE)
        
        ' 書き込み
        objDestStream.Write binChunk
        
        ' 進捗計算
        lngTotalBytesCopied = lngTotalBytesCopied + LenB(binChunk)
        lngProgress = (lngTotalBytesCopied / lngFileSize) * 100
        
        ' 進捗表示 (コンソール/GUIへのフィードバック)
        strProgressMsg = "Progress: " & Int(lngProgress) & "% (" & _
                         FormatBytes(lngTotalBytesCopied) & " / " & _
                         FormatBytes(lngFileSize) & ")"
        WScript.Echo strProgressMsg ' 実際の実装ではログファイルやDBへ記録推奨

        ' --- スロットリング制御 ---
        ' ここで意図的にCPU/IOリソースを解放する
        WScript.Sleep SLEEP_MS
        
        ' エラーチェック
        If Err.Number <> 0 Then
            WScript.Echo "Critical Error during transfer: " & Err.Description
            Call Cleanup(objSrcStream, objDestStream)
            Exit Sub
        End If
    Loop

    ' 4. 後処理
    objDestStream.SaveToFile strDest, 2 ' adSaveCreateOverWrite
    
    WScript.Echo "Transfer Completed Successfully."
    
    Call Cleanup(objSrcStream, objDestStream)
    On Error GoTo 0
End Sub

'------------------------------------------------------------------------------
' ユーティリティ: リソース解放
'------------------------------------------------------------------------------
Sub Cleanup(objSrc, objDest)
    On Error Resume Next
    If Not objSrc Is Nothing Then
        If objSrc.State = 1 Then objSrc.Close
    End If
    If Not objDest Is Nothing Then
        If objDest.State = 1 Then objDest.Close
    End If
    Set objSrc = Nothing
    Set objDest = Nothing
End Sub

'------------------------------------------------------------------------------
' ユーティリティ: バイト数フォーマット
'------------------------------------------------------------------------------
Function FormatBytes(byVal lngBytes)
    Dim arrUnits, intUnitIdx
    arrUnits = Array("B", "KB", "MB", "GB", "TB")
    intUnitIdx = 0
    
    Do While lngBytes >= 1024 And intUnitIdx < UBound(arrUnits)
        lngBytes = lngBytes / 1024
        intUnitIdx = intUnitIdx + 1
    Loop
    
    FormatBytes = Round(lngBytes, 2) & " " & arrUnits(intUnitIdx)
End Function

4. プロフェッショナルが守るべき運用上の注意点

このコードをプロダクション環境に投入する際、以下の3点を必ず考慮せよ。

① チャンクサイズとスリープ時間の「黄金比」

コード内の `CHUNK_SIZE` と `SLEEP_MS` は、環境によって最適解が異なる。

  • チャンクが大きすぎる場合: I/Oスパイクが発生し、スロットリングの効果が薄れる。
  • チャンクが小さすぎる場合: メソッド呼び出しのオーバーヘッド(CPU負荷)が増大し、転送効率が著しく低下する。
  • 推奨: 通常は 1MB〜4MB。ネットワーク越しなら 4MB 程度まで上げても良い。

② データベース連携における「トランザクション」の考え方

もしこのスクリプトが「ファイルの転送完了」をDBに記録する仕様なら、「転送完了」を記録する前に、必ずファイルの整合性チェック(Hash値の確認など)を行うこと。
不完全な転送を「成功」としてDBに書き込むことは、後続のプロセス(集計や解析)をすべて汚染する、エンジニアとしての致命的なミスである。

③ ログ出力の設計(Auditing)

`WScript.Echo` はデバッグ用だ。実務では、以下の情報を構造化ログ(CSVやDB)に記録せよ。

  • `Start_Timestamp` / `End_Timestamp`
  • `Source_Path` / `Dest_Path`
  • `File_Size`
  • `Throughput_Avg` (平均スループット)
  • `Result_Status` (Success/Fail)

結論

「動けばいい」という考え方は、小規模なスクリプトの世界では通用する。しかし、エンタープライズな業務自動化においては、「システム全体への影響を最小化しながら、いかに堅牢にタスクを遂行するか」こそが、エンジニアの真価を問う指標となる。

FSOの `CopyFile` という甘い誘惑を断ち切り、ストリーム制御による「制御されたI/O」を実装せよ。それが、信頼されるアーキテクトへの第一歩である。

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