JavaやCOBOLの経験はあるけれど、IBMのメインフレーム(汎用機)やPL/Iの世界へようこそ!
「なんだか古い言葉だし、記号だらけで難しそう……」なんて、身構えていませんか? 大丈夫ですよ。世の中の多くのプログラミング言語と同じように、PL/Iも突き詰めれば「コンピュータにこういう仕事をさせたい」という人間からのラブレター(指示書)に過ぎません。一つずつ紐解いていけば、決して怖くありませんから、一緒にリラックスして進めていきましょうね。
さて、今回はメインフレームの心臓部とも言える「ディスクやテープ上のデータファイル(RECORDファイル)をどう効率よく読み書きするか」という、実務でめちゃくちゃ重要なテーマについてお話しします。
特に、周辺のオープン系システムやクラウドの世界では意識することが少ない、「ブロック化(F/FB/V/VB/U形式)」と、OSレベルのアクセス方式であるQSAM/BSAM、そしてバッファリングの裏側の仕組みを、モリモリっと深掘りしていきましょう。
—
1. なぜメインフレームのデータは「ブロック」にこだわるのか?
Javaでファイルを読み込むとき、こんな風に書きますよね。
BufferedReader reader = new BufferedReader(new FileReader(“data.txt”));
String line = reader.readLine(); // 1行ずつ優しくお行儀よく読む
現代のOSやプログラミング言語は、ハードウェアの物理的な凹凸や、ディスクの回転スピードなんて意識させないように、うまいこと隠蔽してくれます。
しかし、メインフレーム(z/OS)の世界、特に伝統的なQSAM(Queued Sequential Access Method)の世界では、開発者が「一度のI/O(ハードウェアへのアクセス)でどれだけのデータをまとめて飲み込むか」をかなりシビアにコントロールします。なぜなら、ディスクヘッドを動かす物理的なコストは、CPUが計算するコストに比べて桁違いに重いからです。
ここで登場するのが、LRECL(Logical Record Length:論理レコード長)とBLKSIZE(Block Size:ブロックサイズ=物理レコード長)という2つのキーワードです。
- 論理レコード(LRECL): アプリケーションが「1件分」として認識するデータの単位(例:社員1人分のレコードなど)
- ブロック(BLKSIZE): ハードウェア(DASDなどのディスク)とやり取りする物理的なひとまとまりの単位
「1件の荷物(LRECL)」を、そのまま1回ずつトラック(物理ブロック)に乗せて運ぶのは、ダンボール1箱にりんご1個だけ入れてトラックを走らせるようなもの。ガソリン代(I/Oコスト)がいくらあっても足りませんよね。だから、「りんごを何個もまとめて一つのダンボールに詰め込む(ブロック化)」必要があるのです。
—
2. ENVIRONMENT属性の魔法:F/FB/V/VB/U形式とは?
PL/Iでファイル(RECORDファイル)を定義するとき、`ENVIRONMENT`(略して `ENV` と書くことも多いです)という属性を使って、OSに「このファイルはこういう構造で読み書きしてね!」と伝えます。
ここに指定するフォーマットが、歴史とロマンが詰まった F, FB, V, VB, U です。怖がらずに、一つずつ見ていきましょうね。
① F形式(Fixed:固定長・非ブロック化)
- 特徴: 1ブロックに、論理レコードが1つだけ入る形式です。
- イメージ: トラックにダンボールが1箱、ぽつんと乗っている状態。
- 出番: 今どきのバッチ処理では、非効率なのでほとんどお目にかかりません。「お前、やる気あるのか?」と言われそうな非効率さですが、歴史的な遺産として残っています。
② FB形式(Fixed Blocked:固定長・ブロック化)
- 特徴: 1つのブロックに、同じ長さ(LRECL)の論理レコードが複数個(ブロック化因子:BLKSIZE ÷ LRECL)きれいに詰め込まれている形式です。
- イメージ: みかん箱に、全く同じサイズみかんがきれいに整列してギッシリ詰まっている状態。
- 実務での立ち位置: 基幹システムの主役です。固定長なので、何番目のレコードを読みに行くかの計算もしやすく、処理速度も爆速です。メインフレームのバッチ処理の9割はこれだと思って間違いありません。
③ V形式(Variable:可変長・非ブロック化)
- 特徴: レコードごとに長さが違う(可変長)データを、1ブロックに1つ格納する形式です。
- イメージ: 大きさも形もバラバラな荷物が、1個ずつトラックに乗っている状態。
④ VB形式(Variable Blocked:可変長・ブロック化)
- 特徴: 長さがバラバラな論理レコードを、一つのブロックにギュッと詰め込んだ形式です。
- イメージ: 引っ越しのダンボールに、家具から小物まで隙間なくパズル状に詰め込んだ状態。
- 裏側の仕組み: 可変長なので、「どこからどこまでが1つのレコードか」をOSが判断できるよう、各レコードの先頭にRDW(Record Descriptor Word:4バイトの長さ情報)がつき、さらにブロック全体の先頭にもBDW(Block Descriptor Word:4バイトのブロック長情報)が付きます。PL/Iはこの複雑なオフセット計算を、コンパイラが自動でよしなにやってくれるので本当に助かります。
⑤ U形式(Undefined:不老・不定長)
- 特徴: システムが構造に関与しない、生(なま)のデータブロックです。ロードモジュール(実行プログラム)のライブラリ(PDS)などで使われます。アプリケーションの通常業務で自作のデータファイルをU形式にすることはまずありません。
—
3. 実践!PL/Iでのファイル定義とコード例
百聞は一見に如かず。実際にPL/IでFB形式のファイルを読み込むプログラムの断片を見てみましょう。JavaやCOBOLをやった方なら、見ればなんとなく雰囲気をつかめるはずです。
1
/ ========================================================== /
/ FB形式のファイルを読み込むPL/Iプログラムのサンプル /
/ ========================================================== /
TEST_PROG: PROC OPTIONS(MAIN);
/ 1. 読み込む論理レコード(LRECL=80)のデータ構造を定義 /
DCL 1 EMP_RECORD,
5 EMP_ID CHAR(5), / 社員ID /
5 EMP_NAME CHAR(30), / 社員名 /
5 EMP_DEPT CHAR(10), / 部署コード /
5 FILLER CHAR(35); / 予備領域 /
/ 2. ファイルの論理的・物理的属性をDECLARE(宣言) /
/ ENVIRONMENT属性に F, FB, V, VB を指定します /
Dcl SYSIN01 FILE RECORD INPUT
ENVIRONMENT(
FB / 固定長ブロック化形式を指定 /
BLKSZ(800) / 1ブロックのサイズ (LRECLの10倍) /
RECSIZE(80) / 1論理レコードの長さ (LRECL) /
);
/ 3. 状態管理用の変数 /
Dcl EOF_FLG BIT(1) INIT(‘0’B);
/ ファイル終了(End of File)時の割り込み処理 /
ON ENDFILE(SYSIN01) EOF_FLG = ‘1’B;
/ 4. ファイルオープン /
OPEN FILE(SYSIN01);
/ 5. メインの読み込みループ /
DO WHILE (EOF_FLG = ‘0’B);
/ READ文で1件ずつレコードを取得 /
/ OSやQSAMがバッファから賢くデータを渡してくれます /
READ FILE(SYSIN01) INTO(EMP_RECORD);
IF EOF_FLG = ‘1’B THEN LEAVE;
/ ここに実際のビジネスロジック(データ加工など)を書く /
/ PUT SKIP LIST (EMP_NAME); など /
END;
/ 6. ファイルクローズ /
CLOSE FILE(SYSIN01);
END TEST_PROG;
コードのここがポイント!
- `BLKSZ(800)` と `RECSIZE(80)`: ここが今回の肝です。1レコードが80バイト(パンチカードの伝統を引く黄金サイズ!)のとき、ブロックサイズを800にすることで、「1回の物理I/Oで10件分のレコードをまとめてメモリ(バッファ)に持ってくる」という効率的な処理(ブロック化因子 = 10)を実現しています。
- 開発者は、ファイルがディスク上でどうブロック化されているかを `ENVIRONMENT` 属性で宣言するだけでよく、実際のレコードの取り出しは `READ FILE(…) INTO(…)` を呼ぶだけで、OS(QSAM)とPL/Iのランタイムがバッファから自動で1件ずつ切り出して手渡してくれます。この隠蔽された連携プレイが、メインフレームの強力なところです。
—
4. バッファリング最適化の勘所(パフォーマンスの罠)
最後に、現場でよくあるトラブルや、アーキテクトとして知っておくべきバッファリングの最適化についてお話しします。
JCL(Job Control Language)側やPL/Iの `ENVIRONMENT` 属性で、バッファの面数を指定することができます(例:`BUFNO=5` など)。
- バッファが少なすぎる場合: レコードを読み切るたびにディスクへ物理的なI/Oが発生し、CPUがいくら速くても「I/O待ち(待機状態)」でジョブ全体の処理時間が跳ね上がります(バッチの夜間枠が朝までに終わらない原因の多くはこれです)。
- ブロックサイズが小さすぎる場合(無駄なF形式など): 磁気ディスクやストレージの容量を無駄に消費するだけでなく、I/Oの回数が極端に増え、チャネルビジーを引き起こします。
【黄金律】
現代のメインフレーム環境では、ハードウェアのトラック容量(3390デバイスなど)を考慮し、「できるだけ大きなブロックサイズ(例: 27998バイトや32760バイトなど、最適トラック容量に収まるサイズ)にFBやVBでデータを詰める」ことが、パフォーマンスチューニングの定石です。
もし既存のJCLやPL/Iプログラムの改修で「処理が遅い」という相談を受けたら、まずはJCLの `DCB=(RECFM=FB,LRECL=…,BLKSIZE=…)` の組み合わせや、PL/Iの `ENVIRONMENT` の指定がケチくさい(BLKSIZEが小さすぎる)になっていないかを疑ってみてください。ここをガッツリ見直すだけで、処理時間が数分から数秒に縮まることも珍しくありません。
—
まとめ
いかがでしたでしょうか?
- 「F/FB/V/VB」という見慣れないアルファベットも、要するに「データをどう効率よくダンボールに詰めてトラックで運ぶか」という物理的な最適化の歴史の形にすぎません。
- PL/Iの `ENVIRONMENT` 属性は、その荷物の詰め方をOSに優しく教えるためのパスポートです。
最初は呪文のように見えるメインフレームの仕様も、その背景にある「ハードウェア資源を極限まで大切に使い倒す」というエンジニアたちのロマンを知れば、怖さどころか愛着すら湧いてくるはずです。
レガシーマイグレーションの現場でこのコードに出会ったら、「あぁ、あのときブログで読んだブロック化の仕組みだな」と、心の中でニヤリとしていただければ幸いです。あなたのメインフレームライフが、実り多いものになりますように!
