【実務・中級編】STREAMファイルにおけるBUFFERED/UNBUFFERED属性の性能差とバッファ管理 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近配属された若手が「STREAM入出力のバッファなんて、どっちでも動くんだから一緒っしょ?」なんて軽口を叩いているのを聞いてね、思わず冷や汗が出ちまったよ。

いいかい? 24時間365止まらない勘定系や、夜間バッチで何千万件ものデータをぶん回すメインフレームの世界じゃ、その「どっちでもいいや」という甘い認識が、一晩でCPU使用率を天井知らずに跳ね上げさせ、ジョブをABEND(異常終了)に追い込む最大の凶器になるんだ。

今回は、PL/IのSTREAM入出力における `BUFFERED` と `UNBUFFERED` 属性の性能差とバッファ管理の泥臭い実態 について、現場のノウハウを余すところなく叩き込んでやる。耳の穴かっぽじってよく聞きな。

1. なぜPL/IのSTREAM入出力でバッファが命取りになるのか

そもそもPL/IのSTREAM(ストリーム)入出力は、人間にとって読みやすい文字形式(キャラクター)としてデータを扱う仕組みだ。レコード入出力(RECORD I/O)が物理的なレコード構造をそのまま扱うのに対し、STREAMは文字のストリーム(流れ)として読み書きするため、内部で膨大な編集・変換処理が発生する。

ここで問題になるのが バッファリング戦略 だ。

`BUFFERED` 属性:システム任せの省エネ優等生

`BUFFERED`(デフォルトまたは明示的指定)を指定すると、OS(BSAMやQSAMなどのアクセス法)がメモリ上にバッファプールを確保し、複数レコードをまとめてI/O処理する。

  • メリット: 物理的なI/O回数が激減するため、チャネルビジー率が下がり、結果としてCPUの待ち時間が減る。
  • デメリット: メモリを消費する。また、バッファがフラッシュされるタイミングを意識しないと、障害時のデータロスト範囲が広がる。

`UNBUFFERED` 属性:一途だが燃費の悪い暴れ馬

対して `UNBUFFERED` を指定すると、OSのバッファリング機構をバイパスするか、あるいは極めて限定的な制御となり、1回の `PUT` / `GET` ステートメントが、そのまま(あるいは極めてダイレクトに)物理的なI/O要求に直結しやすくなる。

  • メリット: バッファ溢れや制御の複雑さから解放される…と言いたいところだが、実務でこれをメリットに感じる場面はまずない。
  • デメリット: 圧倒的なI/Oネック。 数万件のファイルを処理させようものなら、EXCP(入出力実行回数)が跳ね上がり、あっという間にCPU時間を食いつぶしてオペレータから怒りの連絡が飛んでくる。

2. VSAMやQSAMファイルとの絡みと実務上の罠

メインフレームのバッチ処理では、順次ファイル(QSAM)やVSAM(KSDS/ESDS)を相手にすることが多い。ここで勘違いしやすいのが、「VSAMだからSTREAMではなくRECORDを使わなきゃいけない」という思い込みだ。

ログ出力や簡単なテキストレポートの生成などでは、依然としてSTREAMファイルがバリバリ現役で使われている。
特に、夜間バッチの集計結果をテキストとして吐き出す際、無駄に `UNBUFFERED`(あるいはそれに近い暗黙の挙動)を引き起こすコードを書いていると、データ量が増加した途端に性能劣化のボトルネックになる。

さらに恐ろしいのが、バッファ溢れ(Buffer Overflow) だ。
レコード長を超えるデータを無理やりストリームに流し込もうとしたり、レコード・フォーマット(F/V/VBなど)の定義とバッファサイズがミスマッチを起こすと、コンパイラやOSレベルで致命的な例外が発生する。これをいかにハンドリングするか、それがプログラマの腕の見せ所というわけだ。

3. 実践!PL/Iストリーム入出力とONユニット制御のコード例

百聞は一見に如かずだ。実際に現場で使える、バッファ制御と例外処理(ONユニット)を考慮した堅牢なPL/Iプログラムのサンプルを見せておこう。

すべて大文字、適切なインデント、そして `SUBSTR` などの `BUILTIN` 関数を駆使した、保守性の高いコードだ。

STRP01: PROC OPTIONS(MAIN);

/————————————————————–/
/ 修正履歴: 202X.10.15 新規作成 /
/ 概要 : 大量ストリーム出力におけるBUFFERED属性の活用と /
/ 入出力エラー(ENDFILE / ERROR)の制御サンプル /
/————————————————————–/

DCL OUT_FILE FILE STREAM OUTPUT
BUFFERED; / 明示的にBUFFEREDを指定しI/O効率を最大化 /

DCL 1 W_DATA_REC,
5 W_ID CHAR(8),
5 W_NAME CHAR(30),
5 W_AMOUNT FIXED DEC(9,2);

DCL W_LINE_BUF CHAR(100);
DCL W_COUNT FIXED BIN(31) INIT(0);

/ 異常系および終了時のONユニット定義 /
ON ENDFILE(OUT_FILE) BEGIN;
PUT SKIP LIST(‘ 正常終了: すべてのデータ書き込みが完了しました ‘);
GOTO CLOSE_RTN;
END;

ON ERROR BEGIN;
PUT SKIP LIST(‘ 致命的エラー発生: 処理を中断します ‘);
/ ここでダンプ取得やエラーログ出力のルーチンを呼び出す /
EXIT;
END;

/ ファイルのオープン(BUFFEREDの恩恵を受ける準備) /
OPEN FILE(OUT_FILE) TITLE(‘RPTFILE’) OUTPUT;

PUT SKIP LIST(‘— 大量データ・ストリーム出力処理 開始 —‘);

/ メインのループ処理(ダミーデータ生成と書き込み) /
DO WHILE(W_COUNT < 50000); W_COUNT = W_COUNT + 1; / BUILTIN関数や編集機能を活用したデータ整形 / W_ID = 'ID' || REPEAT('0', 5 - LENGTH(TRIM(CHAR(W_COUNT)))) || CHAR(W_COUNT); W_NAME = 'CUSTOMER_' || CHAR(W_COUNT); W_AMOUNT = W_COUNT 123.45; / 文字列の組み立て(ストリーム出力用バッファへ蓄積される) / W_LINE_BUF = W_ID || ',' || W_NAME || ',' || PUT_STRING(W_AMOUNT); / 実際にはEDIT等を使用 / / STREAM出力: BUFFEREDのおかげで物理I/Oは極小化される / PUT FILE(OUT_FILE) EDIT (W_LINE_BUF) (A); / 1万件ごとに進捗を出力(バッファのフラッシュポイントを意識) / IF MOD(W_COUNT, 10000) = 0 THEN PUT SKIP EDIT('処理件数: ', W_COUNT) (A, F(10)); END; CLOSE_RTN: CLOSE FILE(OUT_FILE); PUT SKIP LIST('--- 処理正常終了 ---'); RETURN; END STRP01; ---

4. ベテランからの現場の教訓(まとめ)

このコードを見て、「なんだ、当たり前のことじゃないか」と思ったかい?
違うんだよ。実際のレガシーシステム改修現場では、前任者が適当に書いたコードをコピペし回すうちに、本来 `BUFFERED` で処理されるべき巨大なCSV/テキスト出力ストリームに、余計な制御コードや非効率なフォーマット指定が混ざり込み、パフォーマンスがガタガタになっているケースが後を絶たない。

特に注意すべきポイントをもう一度おさらいしておこう。

1. 基本は `BUFFERED` を死守せよ
特別な理由(リアルタイムの同期書き込みがどうしても必要な場合など)がない限り、STREAM出力には `BUFFERED` を信頼し、OSのバッファ管理能力を最大限に引き出せ。
2. ONユニットでI/O異常をねじ伏せろ
バッファ溢れやディスクフル、その他の入出力エラーはいつだって突然やってくる。`ON ERROR` や `ON ENDFILE` を適切に配置し、異常時にシステムが暴走するのを防ぐのがプロの仕事だ。
3. CPU使用率が高いときはEXCPとバッファを疑え
もしお前が担当しているバッチジョブが「CPUバウンドなのに妙に時間がかかる」「I/O待ちが多い」と感じたら、まずはPL/Iのファイル定義とJCLのDCBパラメータ(BLKSIZEなど)、そして今回話したバッファリング属性を真っ先に疑うんだな。

基本に忠実であれ。そして、コードの裏側でメインフレームのハードウェアとOSがどう動いているのかを常に想像しながらキーボードを叩くこと。それができれば、お前も一人前のメインフレーム・アーキテクトに近づけるはずだ。さて、コーヒーでも飲んで次のタスクに取り掛かるとしようか。

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