【実務・中級編】RECORDファイルにおけるENVIRONMENT属性のF/FB/V/VB/U形式の内部制御 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは。メインフレームの現場で、日々数百万件のレコードをさばくバッチ処理と格闘しているエンジニアの皆さん。

今回は、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業界において非常に希少価値の高い存在だ。

今回の解説が、君たちの日常の保守開発や、次なるマイグレーションプロジェクトの羅針盤となれば幸いである。質問があれば、いつでも現場の背中を叩きに来てくれ。

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