こんにちは。メインフレームの現場で、日々数百万件のレコードをさばくバッチ処理と格闘しているエンジニアの皆さん。
今回は、PL/Iにおけるレコード入出力の心臓部、RECORDファイルにおける`ENVIRONMENT`属性、そして`F`/`FB`/`V`/`VB`/`U`形式の内部制御について徹底的に解説しよう。
今のオープン系から来たエンジニアには「レコード長?ブロック長?何を今さら」と言われがちだが、我が国を支える基幹システムのメインフレーム(z/OS)において、このデータセットの物理構造とアクセス方式(QSAM/BSAM)のメカニズムを理解しているか否かで、夜間バッチの処理時間が数倍変わることもあるし、最悪の場合は原因不明のABEND(S013やS037など)で運用担当者を深夜に呼び出すことになる。
今回は、PL/Iの構文規則とあわせて、実務で絶対に知っておくべき最適化の勘所を伝授しよう。
—
1. 予約語を持たないPL/Iの構文と識別子の妙
本題に入る前に、PL/Iという言語のユニークさに触れておこう。
C言語やJavaとは異なり、PL/Iには厳密な意味での「予約語(Reserved Words)」が存在しない。 すべてのキーワード(`DECLARE`, `READ`, `WRITE`, `ENVIRONMENT` など)は「文脈依存語(Contextual Keywords)」である。
つまり、以下のようなコードを書くことも、言語仕様上はコンパイルエラーにならない。
1
/ 変数名としてキーワードを流用する悪夢の例(絶対に真似してはいけません) /
DECLARE ENVIRONMENT FIXED BIN(31);
DECLARE READ CHARACTER(10);
コンパイラは、その位置が文法的に変数名なのか、それとも構文キーワードなのかを前後の文脈から判断する。しかし、これをやってしまうと、保守するプログラマ(数年後のあなた自身かもしれない)が発狂することになる。識別子(変数名)の命名にあたっては、システム共通のコーディング標準を遵守し、キーワードの流用は厳に慎むべきだ。
—
2. RECORDファイルとENVIRONMENT属性の基礎
PL/Iで順編成データセット(QSAM)などを扱う際、`DECLARE`文でファイルを定義し、`ENVIRONMENT`属性でOSレベルのアクセス方式やレコード形式を指定する。
ここでの指定が誤っていると、OPEN時にオープンエラー(S013-18など)を引き起こすか、最悪の場合、データが化けたままサイレントに処理が進むという恐怖の事態を招く。
レコード形式(F / FB / V / VB / U)の内部制御
OS( z/OSのDFP / Data Facility Product )がどのようにデータをメモリ(バッファ)とDASD(Direct Access Storage Device)の間でやり取りしているか、その違いを整理しておこう。
- F (Fixed / 固定長非ブロック化)
- 論理レコード長(LRECL)= ブロック長(BLKSIZE)
- 1ブロックに1レコードしか入らないため、DASDの容量効率が極めて悪い。現代のバッチ処理で単独のF形式を使う理由はほぼない。
- FB (Fixed Blocked / 固定長ブロック化)
- ブロック長(BLKSIZE)は論理レコード長(LRECL)の整数倍( `BLKSIZE = LRECL n` )。
- 複数の論理レコードを1つの物理ブロックにまとめて入出力する。最も一般的で効率的な形式。
- V (Variable / 可変長非ブロック化)
- レコードごとに長さが異なる形式。各レコードの先頭に4バイトのRLH(Record Length Header:RDW)が付加される。
- `BLKSIZE = LRECL + 4` となる。
- VB (Variable Blocked / 可変長ブロック化)
- 可変長レコードをさらにブロック化したもの。
- ブロック全体の先頭に4バイトのBDW(Block Descriptor Word)、各レコードの先頭に4バイトのRDW(Record Descriptor Word)が付く。
- PL/Iでこれを扱う場合、OS側がよしなにブロックを剥いてくれるが、`LRECL`と`BLKSIZE`の設計を誤るとS037等の領域溢れABENDが頻発する。
- U (Undefined / 不定長)
- レコード長やブロック長の概念をOS側が強制しない形式。ロードモジュール(PDSのメンバー)や、ダンプデータの出力、あるいは特殊なグラフィックデータなどに使われる。
—
3. 実践:PL/IによるFB/VBファイルの効率的コーディング
それでは、実際に`ENVIRONMENT`属性を用いてQSAMファイルを制御するPL/Iのサンプルコードを見てほしい。ここでは、固定長ブロック(FB)の入力ファイルからデータを読み込み、可変長ブロック(VB)のファイルへ出力するバッチ処理の骨組みを示す。
1
—————————————————————-
- BATCH_SAMPLE: QSAMファイルの入出力と環境制御のサンプル
—————————————————————-
BATCH_SAMPLE: PROC OPTIONS(MAIN);
/ 入力ファイル宣言 (固定長ブロック: FB) /
DECLARE IN_FILE FILE RECORD INPUT
ENVIRONMENT(
BLOCKSIZE(8000)
RECLENGTH(80)
BUFOFFSET(0)
);
/ 出力ファイル宣言 (可変長ブロック: VB) /
DECLARE OUT_FILE FILE RECORD OUTPUT
ENVIRONMENT(
BLOCKSIZE(27998)
RECSIZE(1000)
VB
);
/ ワーキングストレージ領域 /
DECLARE IN_REC CHARACTER(80);
DECLARE OUT_REC CHARACTER(1000) VARYING;
DECLARE IO_EOF BIT(1) INIT(‘0’B);
/ 異常系制御のためのONユニット(ファイル終了・入出力エラー) /
ON ENDFILE(IN_FILE) IO_EOF = ‘1’B;
ON UNDEFINEDFILE(IN_FILE) BEGIN;
DISPLAY(‘【SEVERE】入力ファイルがオープンできません。JCLを確認してください。’);
SIGNAL ERROR;
END;
/ ファイルのオープン /
OPEN FILE(IN_FILE) INPUT,
FILE(OUT_FILE) OUTPUT;
/ メインループ /
DO WHILE(^IO_EOF);
/ レコードの読み込み (QSAM基本アクセス) /
READ FILE(IN_FILE) INTO(IN_REC);
IF IO_EOF THEN
LEAVE;
/ データ変換ロジック(例:入力80バイトを可変長バッファに加工) /
OUT_REC = ‘PROCESSED: ‘ || SUBSTR(IN_REC, 1, 50);
/ レコードの書き込み /
WRITE FILE(OUT_FILE) FROM(OUT_REC);
END;
/ ファイルのクローズ /
CLOSE FILE(IN_FILE),
FILE(OUT_FILE);
DISPLAY(‘【INFO】バッチ処理が正常終了しました。’);
END BATCH_SAMPLE;
—
4. バッファリングの最適化手法と現場の勘所
ここからがシニアアーキテクトとしての腕の見せ所だ。上のコードの`ENVIRONMENT`属性、特に`BLOCKSIZE`の指定を見てほしい。
1. ブロックサイズ(BLKSIZE)の最適化
JCL側(あるいはDCBパラメータ)で`BLKSIZE`を省略し、PL/Iのソースコード側やコンパイル時属性に頼る設計は、大規模システムではトラブルの元になる。
特に現代のメインフレーム(DS8000などの高性能ストレージ)においては、トラック容量を効率的に使い切るブロックサイズ(オプティマル・ブロック・サイズ)を選ぶ必要がある。
- 3390型DASDの場合、1トラックの物理容量は 56,664バイト だ。
- この容量に対して、ブロックサイズが中途半端だと、1トラックあたりに入るブロック数が減り、I/O効率が著しく落ちる。
- 一般的には、半トラックブロック(約27,998バイト)や、それに準じたサイズに設定するのが、チャンネルプログラムの効率とバッファプールの利用効率のバランス点となる。上記のサンプルで出力ファイルのBLKSIZEに `27998` を指定しているのはそのためだ。
2. バッファ数(BUFNO)のチューニング
大量レコードを処理するバッチで最もボトルネックになるのは、CPU時間ではなくI/O待ち(EXCP数)である。
`ENVIRONMENT`属性には、明示的にバッファ数を指定することができる。
1
DECLARE IN_FILE FILE RECORD INPUT
ENVIRONMENT(
BLOCKSIZE(32000)
RECLENGTH(80)
BUFNO(10) / バッファ面数を10面に拡大 /
);
デフォルトのバッファ数は通常3面程度だが、これを多重化(例: 10面や20面)することで、OSのキューイングと非同期I/O(QSAMのBPAMベースの先読み機能など)がフルに効き、夜間バッチの壁時間を劇的に短縮することが可能だ。ただし、オンライン領域や同時間帯に走る他のバッチとのメモリ(VSAMバッファプールやOS共有領域)の兼ね合いがあるため、MVSシステムプログラマと相談のうえで決定してほしい。
3. ONユニットによる例外処理の重要性
PL/Iの真骨頂は、強力な例外処理(`ON`ユニット)にある。ファイル入出力における`ENDFILE`や`TRANSMIT`(ハードウェアI/Oエラー)、`UNDEFINEDFILE`を適切に捕捉し、単に異常終了させるだけでなく、どのキーのデータで何が起きたのかをログ(SYSOUT)に残す設計が、本番障害の早期解決には不可欠である。
—
おわりに
PL/Iの構文や`ENVIRONMENT`属性の指定は一見すると古めかしく見えるかもしれないが、その裏ではz/OSのアーキテクチャと密接に連携した洗練されたI/O制御が行われている。
「なぜこのブロックサイズなのか」「なぜこのレコード形式なのか」をロジカルに説明でき、性能チューニングまで踏み込めるメインフレームエンジニアは、現在のIT業界において非常に希少価値の高い存在だ。
今回の解説が、君たちの日常の保守開発や、次なるマイグレーションプロジェクトの羅針盤となれば幸いである。質問があれば、いつでも現場の背中を叩きに来てくれ。
