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

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

Beyond Simple Macros: プロの現場で通用する「スロットリング付きファイル転送エンジン」をVBScriptで構築する

こんにちは。技術の深淵へようこそ。

「マクロの記録」や、ネットで見つけた「`FileCopy` を使っただけの単純なコピーコード」で、なんとか業務を回している……。もしあなたが今、そんなフェーズにいるとしたら、今日このページに辿り着いたことは、エンジニアとして大きな転換点になるはずです。

プロの現場では、「動けばいい」という考え方は通用しません。
例えば、数GB、数TBという大容量ファイルをコピーするスクリプトを、業務時間中に実行したとしましょう。単純なコピー命令は、ストレージ(HDD/SSD)の性能を限界まで引き出し、ディスクI/O(読み書きの負荷)を100%に張り付かせます。その結果、同じサーバー上で動いている他の重要な業務システムが、ディスクの応答待ちでストップしてしまう……。これこそが、初級者が陥る「リソース枯渇」という罠です。

今日は、そんな事態を防ぐための高度な技術、「スロットリング(流量制御)」を組み込んだファイル転送エンジンを、VBScriptで構築する方法を伝授します。

1. なぜ「単純なコピー」ではいけないのか?(IOPSの概念)

コードを書く前に、まずは「なぜ制御が必要なのか」という本質を理解しましょう。

コンピュータのストレージには、IOPS (Input/Output Operations Per Second) という指標があります。これは「1秒間にどれだけの読み書き命令を処理できるか」という能力です。

単純な `FileSystemObject.CopyFile` メソッドは、いわば「全力疾走」です。

  • メリット: 転送速度が最速。
  • デメリット: ディスクの帯域を独占し、他のプロセスが「ディスクが重くて動けない!」と悲鳴を上げる。

プロのエンジニアが作るべきは、全力疾走ではなく、「周囲の状況を見ながら、一定のペースを保って歩く」ような制御されたプログラムです。これをスロットリング(Throttling)と呼びます。

2. 武器を揃える:FSOとADODB.Stream

VBScriptで高度な制御を行うには、標準的な `FileSystemObject (FSO)` だけでは少し力不足です。FSOの `CopyFile` は「全部まとめてコピー」という命令しか持っていないからです。

そこで、私たちは「データを細切れ(チャンク)にして読み込み、少し休んでから書き込む」という戦略を取ります。このために、以下の2つのオブジェクトを組み合わせて使います。

1. FileSystemObject (FSO): ファイルの存在確認やフォルダ操作を行う「司令塔」。
2. ADODB.Stream: ファイルを「バイナリ(データの塊)」として、細かく読み書きするための「高機能な蛇口」。

3. 実装:スロットリング付き転送エンジンのコード

それでは、魂を込めたコードを見ていきましょう。このコードは、指定したデータ量(MB)を転送するたびに、あえて「休憩(Sleep)」を入れることで、ディスクへの負荷を平滑化します。

'==============================================================================
' Professional Throttled File Transfer Engine
' 
' 【概要】
' 大容量ファイルの転送時に、一定量ごとにSleepを挟むことで
' ディスクI/Oのスパイク(急激な負荷上昇)を抑制する。
'==============================================================================

Option Explicit

' --- 設定値(ここを調整して制御する) ---
Const CHUNK_SIZE = 1048576      ' 1回に読み込むサイズ (1MB)
Const THROTTLE_THRESHOLD = 52428800 ' 負荷を抑えるタイミング (50MBごとに休憩)
Const SLEEP_DURATION = 500      ' 休憩時間 (ミリ秒) : 500ms = 0.5秒

Dim fso, srcStream, destStream
Dim sourcePath, destPath
Dim bytesRead, totalBytesCopied, currentBatchBytes

' 実行例
sourcePath = "C:\Temp\LargeData.zip"  ' コピー元
destPath   = "D:\Backup\LargeData.zip" ' コピー先

' 1. 準備フェーズ
Set fso = CreateObject("Scripting.FileSystemObject")

' ファイルが存在するかチェック(プロの鉄則:いきなり処理しない)
If Not fso.FileExists(sourcePath) Then
    WScript.Echo "Error: コピー元ファイルが見つかりません。"
    WScript.Quit
End If

' コピー先のフォルダが存在しない場合は作成する
Dim destFolder
destFolder = fso.GetParentFolderName(destPath)
If Not fso.FolderExists(destFolder) Then
    fso.CreateFolder(destFolder)
End If

' 2. ストリームのオープン
' ADODB.Streamを使用してバイナリモードで開く
Set srcStream = CreateObject("ADODB.Stream")
srcStream.Type = 1 ' adTypeBinary
srcStream.Open
srcStream.LoadFromFile sourcePath

Set destStream = CreateObject("ADODB.Stream")
destStream.Type = 1 ' adTypeBinary
destStream.Open

' 3. 転送ループ(ここがエンジンの心臓部)
WScript.Echo "転送を開始します..."

totalBytesCopied = 0
currentBatchBytes = 0

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

Do Until srcStream.EOS
    ' データをチャンクサイズ分だけ読み込む
    Dim buffer
    buffer = srcStream.Read(CHUNK_SIZE)
    
    ' 読み込んだデータを書き込む
    destStream.Write buffer
    
    ' 統計情報の更新
    bytesRead = LenB(buffer)
    totalBytesCopied = totalBytesCopied + bytesRead
    currentBatchBytes = currentBatchBytes + bytesRead
    
    ' --- スロットリング・ロジック ---
    ' 設定した閾値(例: 50MB)を超えたら、あえて「休む」
    If currentBatchBytes >= THROTTLE_THRESHOLD Then
        WScript.Echo "負荷抑制のため、" & (SLEEP_DURATION/1000) & "秒間スリープします..."
        WScript.Sleep SLEEP_DURATION
        currentBatchBytes = 0 ' バッチカウンターをリセット
    End If
    
    ' 進捗表示(コンソール用)
    ' WScript.Echo "Progress: " & Int(totalBytesCopied / 1048576) & " MB"
Loop

' エラーチェック
If Err.Number <> 0 Then
    WScript.Echo "Critical Error: 転送中にエラーが発生しました -> " & Err.Description
Else
    ' 4. 後処理(クローズと保存)
    destStream.SaveToFile destPath, 2 ' adSaveCreateOverWrite
    WScript.Echo "転送完了! 合計サイズ: " & Int(totalBytesCopied / 1048576) & " MB"
End If

' オブジェクトの解放(メモリリーク防止の極意)
srcStream.Close
destStream.Close
Set srcStream = Nothing
Set destStream = Nothing
Set fso = Nothing

'==============================================================================

4. コードの深層解説:何を、なぜしているのか?

このコードを単なる「コピペ」で終わらせないために、重要なポイントを解説します。

① `ADODB.Stream` によるバイナリ制御

通常の `FSO.OpenTextFile` はテキスト用です。画像やZipファイルなどのバイナリデータを扱う場合、文字コード変換が走るとデータが破損します。`Type = 1 (adTypeBinary)` を指定することで、データを「生(なま)」のまま、正確に扱うことができます。

② チャンク(塊)による分割読み込み

`srcStream.Read(CHUNK_SIZE)` の部分が重要です。一度にファイル丸ごとをメモリに読み込むと、数GBのファイルの場合、メモリ不足(Out of Memory)でクラッシュします。
「1MBずつ読み込む」という動作を繰り返すことで、メモリ使用量を一定に保ちつつ、制御可能な単位で処理を進めることができます。

③ スロットリングのアルゴリズム

If currentBatchBytes >= THROTTLE_THRESHOLD Then
    WScript.Sleep SLEEP_DURATION
    currentBatchBytes = 0
End If

ここがこのエンジンの「知性」です。
「50MB書き込んだら、0.5秒休む」というルールを作ることで、ディスクへの書き込み命令が連続して発生し続けるのを防ぎます。これにより、ディスクのキュー(待ち行列)がパンクするのを防ぎ、他のアプリケーションにディスクI/Oの隙間を与えます。

④ オブジェクトのライフサイクル管理

コードの最後にある `Set … = Nothing`。これは初学者が見落としがちですが、プロにとっては必須の作法です。
VBScriptにおいて、作成したオブジェクトは明示的に解放しないと、スクリプト終了後もメモリやファイルハンドルを掴み続けてしまうことがあります。これは「ファイルが使用中のため削除できません」といったエラーの元になります。

5. 陥りやすいエラーと回避策

プロを目指すなら、以下のトラブルを想定しておきましょう。

  • 「書き込み権限がない」エラー:

コピー先のフォルダに対して、実行ユーザーに書き込み権限があるか。`fso.FolderExists` で事前にチェックする癖をつけましょう。

  • 「ファイルが他のプロセスに使われている」エラー:

コピー元がExcelファイルなどで開かれている場合、エラーになります。`On Error Resume Next` を使いつつ、エラーが発生した際に「どのファイルで起きたか」を表示できるように設計するのがスマートです。

  • 「ディスク容量不足」エラー:

転送前に `fso.GetFolder(destFolder).Size` と `fso.GetFile(sourcePath).Size` を比較し、空き容量が足りるか計算するロジックを追加すると、さらに完璧になります。

最後に

ここまで読んでくれたあなたは、もう単なる「マクロの記録者」ではありません。
「リソースの枯渇」を予見し、「負荷を制御する」という設計思想(アーキテクチャ)を持った、一歩先のエンジニアへの階段を登り始めています。

VBScriptは古い言語かもしれません。しかし、その仕組みを深く理解することは、現代の高度なプログラミング言語を扱う上での強固な基礎となります。

ここをクリアすれば、VBScriptの基本、そして「システムを安定させる考え方」の基本はバッチリですよ。
さあ、自信を持って、次のコードを書き進めてください!

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