レガシーの深淵:QSAM/BSAMにおけるレコード形式(F/FB/V/VB/U)とPL/I `ENVIRONMENT` 属性の制御メカニズム
メインフレームの基幹バッチ処理において、I/O性能のボトルネックは常に「いかにして物理的なDASD(直接アクセス記憶装置)へのアクセス回数を減らすか」という点に帰着する。現代のオープン系エンジニアから見れば、単なるテキストファイルやバイナリファイルの読み書きに見える処理も、IBM汎用機の世界ではOSのアクセス方式であるQSAM(Queued Sequential Access Method)やBSAM(Basic Sequential Access Method)のアーキテクチャと密接に結びついている。
特に、PL/I(Programming Language One)で `RECORD` ファイルを扱う際、`ENVIRONMENT` 属性で指定する `F`、`FB`、`V`、`VB`、`U` といったレコード形式の選択は、単なるフォーマットの定義にとどまらない。それは、チャネル・プログラムの効率、EXCP(Execute Channel Program)数の削減、そしてバッファプール内のメモリ管理に決定的な影響を与える。
本稿では、PL/Iプログラマおよびモダナイゼーションを担うシステムアーキテクトに向けて、ブロック化因子(`BLKSIZE`)と論理レコード長(`LRECL`)がOSレベルのアクセス方式に与える影響を解き明かし、動的メモリ操作やアベンド(ABEND)解析、さらにはJava/C#等への移行設計におけるクリティカルな知見を共有する。
—
1. レコード形式(F/FB/V/VB/U)の内部制御とOSアクセス方式
PL/Iの `DECLARE` 文でファイル定義を行う際、`ENVIRONMENT` 属性に指定するパラメータは、JCL側の `DCB` パラメータ(`RECFM`、`LRECL`、`BLKSIZE`)と完全に調停されていなければならない。ここに矛盾が生じると、オープン時の `S013` や `S037` などの致命的なアベンドを引き起こす。
固定長と可変長の内部構造
- F (Fixed) / FB (Fixed Blocked):
論理レコード長(`LRECL`)が一定の形式。`FB` は、1つの物理ブロック(`BLKSIZE`)の中に複数の論理レコードが隙間なく詰め込まれている状態を指す。OSのQSAMは、物理I/Oを1回発行するだけで、バッファ内にある複数の論理レコードを順次プログラムに引き渡すことができる。
- V (Variable) / VB (Variable Blocked):
論理レコード長が変動する形式。COBOLやPL/Iにおいて可変長レコードを扱う場合、各論理レコードの先頭に4バイトのRDW(Record Descriptor Word)が付加される。さらに、ブロック化された `VB` の場合、物理ブロックの先頭にも4バイトのBDW(Block Descriptor Word)が存在する。
- RDWの上位2バイト:レコードの全長(データ長 + RDW自体の4バイト)
- RDWの下位2バイト:予約領域(通常はX’0000’)
- U (Undefined):
レコード長が不定、またはOS側で解釈されない形式。ロードモジュール(PDSのメンバー)の読み込みや、ダンプデータの処理、あるいは特殊なイメージデータをそのままストリームとして扱う場合に用いる。
—
2. PL/Iにおけるファイル定義とベース変数・ポインタによる動的メモリ操作
可変長レコード(`VB`)をPL/Iで処理する場合、レコード長が動的に変わるため、コンパイル時にサイズが確定しないデータを扱うことになる。ここで威力を発揮するのが、`BASED` ストレージクラスと `POINTER` 変数を用いた動的メモリ操作である。
以下のコードは、`VB` 形式のトランザクションファイルを読み込み、RDWを解析した上で、ベース変数を用いて効率的にメモリ上のデータをハンドリングする実例である。
—————————————————————-
- 可変長(VB)レコードを処理するPL/Iバッチプログラムの骨格例
—————————————————————-
VBPUMP: PROC OPTIONS(MAIN);
— ファイル宣言 —
DCL TRANS_FILE FILE RECORD INPUT
ENVIRONMENT(FB 80); / 実際にはJCL側でVB定義と突合 /
— レコード全体のバッファ用ベース変数 —
DCL 1 RAW_RECORD BASED(RAW_PTR),
5 RDW_LEN FIXED BIN(15), / レコード長 (RDW前半) /
5 RDW_RES FIXED BIN(15), / 予約領域 (RDW後半) /
5 DATA_BODY CHAR(32760); / データ本体 (最大サイズ) /
DCL RAW_PTR POINTER;
DCL IO_EOF BIT(1) INIT(‘0’B);
— 終了条件(ENDFILE)の監視 —
ON ENDFILE(TRANS_FILE) IO_EOF = ‘1’B;
OPEN FILE(TRANS_FILE);
DO WHILE(^IO_EOF);
— READ…SET形式によるポインタベースの読込(高速化) —
READ FILE(TRANS_FILE) SET(RAW_PTR);
IF IO_EOF THEN LEAVE;
— RDWから実効データ長を算出 —
- 注: RDW_LENにはRDW自身の4バイトが含まれるため、実データ長は -4 する
DCL ACTUAL_DATA_LEN FIXED BIN(31);
ACTUAL_DATA_LEN = FIXED(RDW_LEN, 31) – 4;
— データ本体に対するビジネスロジック呼び出し —
CALL PROCESS_VARIABLE_DATA(RAW_PTR, ACTUAL_DATA_LEN);
END;
CLOSE FILE(TRANS_FILE);
RETURN;
PROCESS_VARIABLE_DATA: PROC(P_DATA, P_LEN);
DCL P_DATA POINTER;
DCL P_LEN FIXED BIN(31);
— 渡されたポインタを別のベース構造体に割り当てる —
DCL 1 LOGICAL_REC BASED(P_DATA),
5 KEY_FIELD CHAR(10),
5 SUB_DATA CHAR(100) VARYING; / 実際の構造に合わせる /
- ここでポインタ経由で高速にデータを参照・加工する
- コピーが発生しないため、大規模バッチにおいてメモリ効率が極めて高い
END PROCESS_VARIABLE_DATA;
END VBPUMP;
このコードのポイントは、`READ FILE(…) SET(…)` 構文の採用にある。OSのバッファ内にあるアドレスを直接ポインタ変数(`RAW_PTR`)に格納するため、ワークエリアへのデータ転送(`INTO` 句を使用した場合のメモリコピー)が発生しない。これが、億単位のレコードを処理する基幹バッチにおける極限のチューニング手法である。
—
3. バッファリングの最適化とOSレベル(QSAM)への影響
メインフレームのI/O性能を語る上で欠かせないのが、バッファリング戦略である。PL/Iの `ENVIRONMENT` 属性では、バッファの面数(Buffers)を明示的あるいは暗黙的に制御できる。
JCLの `DCB=(RECFM=VB,LRECL=4096,BLKSIZE=27998)` のような設定を想像してほしい。
トラック長(3390デバイスの場合約56KB)に対して、`BLKSIZE` を27998(約28KB)に設定することで、1トラックあたり2ブロック(2.5Dという表現はしないが、実質的に2ブロックが最適配置される)を効率よく配置できる。
- バッファ不足によるオーバーヘッド:
バッファ数が少なすぎると(例: `BUFNO=2`)、アプリケーションの演算処理速度がOSの物理I/O(EXCP)の完了待ち(WAIT)に追いつかれ、CPUが遊んでしまう(I/O待機時間の増大)。
- 過剰なバッファ確保による弊害:
逆にバッファを無制限に確保すると、リアルストレージや24ビット/31ビット領域(あるいは64ビット領域)の仮想記憶を圧迫し、ページング(Paging/Swapping)を引き起こし、システム全体のパフォーマンスを悪化させる。
PL/Iコンパイラは、デフォルトで最適なバッファ数を割り当てるが、高スループットが求められるソート後処理や巨大なマスター更新バッチでは、JCLの `DCB=BUFNO=` やPL/I側の環境設定と連動させ、物理I/Oのヒット率を監視しながらチューニングを行う必要がある。
—
4. アベンド(ABEND)発生時のダンプ解析とエッジケース
基幹システムにおいて、レコード形式の不整合やポインタの誤操作は、即座に致命的なアベンドへと直結する。ここでは現場で遭遇しうる典型的なトラブルシューティングを取り上げる。
1. `S0C4` アベンド(Protection Exception)とポインタの迷子
`READ … SET(RAW_PTR)` で取得したポインタが指す領域に対し、誤って構造体サイズを超えたアクセスを行った場合や、ファイルがEOFに達した後のポインタ参照によって `S0C4` が発生する。
- 解析の勘所:
セルフダンプ(SYSUDUMP/CEEDUMP)のPSW(Program Status Word)位置から該当のPL/Iステートメントを特定し、CEEDUMPのレジスタ内容から `RAW_PTR` のアドレス値を確認する。該当アドレスがファイルバッファの範囲外を指している場合、EOF判定の抜けや、レコード長(RDW)の不正読み込みが疑われる。
2. `S013-18` / `S037` アベンド
JCL上の `DCB` と、プログラム内の `DECLARE FILE … ENVIRONMENT(…)` の定義が食い違っている場合に発生する。
- 例えば、プログラム側で `FB` と想定しているのに、実データが `VB` であり、先頭4バイトがRWDとして解釈されてデータがズレるケース。
- あるいは、`BLKSIZE` が `LRECL` の整数倍になっていない(`FB` の場合)矛盾など。これらはコンパイル時ではなく、オープン時にOSのデータ管理ルーチン(Data Management)によって検知される。
3. パックデシマル(`FIXED DEC`)の内部符号反転バグとデータ破損
可変長レコードのオフセット計算を誤り、文字データ領域をパックデシマル(COMP-3)として無理やり演算させたり、ゾーン10進数とパックデシマルの混同が生じたりすると、データ内に不正なゾーン/サインニブル(例: `X’F’` 以外の値)が混入し、`S0C7`(Data Exception)を引き起こす。
特にマイグレーション時に、オープン系のバイナリ変換ツールがRDWの存在を考慮せずバイトシフトを起こし、パックデシマルの符号部分(下位4ビット)を破壊する事故が後を絶たない。
—
5. モダナイゼーション(Java/C#移行)におけるアーキテクチャ設計の要諦
レガシーなPL/I資産をJava(Spring Batchなど)やC# (.NET Core) へ移行する際、最大の難所となるのが、この「物理ブロック(BLKSIZE)と可変長レコード(RDW/BDW)」の抽象化ギャップである。
1. レコード形式の隠蔽とストリームパーサの設計:
オープン系言語には、メインフレームのような「OSがブロック化/非ブロック化を自動管理してくれるQSAM」の概念が存在しない。そのため、移行先のバッチフレームワークでは、以下のレイヤーを独自に実装する必要がある。
- 物理ファイルをバイトストリームとして読み込み、BDW/RDWを解釈して論理レコードの境界を切り出す「カスタムRecordReader」の作成。
2. パックデシマル(COMP-3)の正確なエミュレーション:
Javaの `BigDecimal` やC#の `decimal` へ安全に変換するため、移行ツールによる静的解析だけでなく、バイナリレイアウト定義(CopybookやPL/I構造体)をメタデータとして保持し、実行時にバイト単位で正確にアンパックするコンバータの組み込みが必須となる。
3. ポインタベース処理の安全な置き換え:
PL/Iの `BASED` 変数によるポインタキャストは、メモリ上の任意の場所を自由に解釈できる諸刃の剣である。これをJava等の安全な言語に移植する際は、ゼロコピーのメモリマッピング(NIOの `ByteBuffer` など)を活用するか、あるいは安全性(Safety)を優先して直列化/非直列化のオーバーヘッドを受け入れるか、アーキテクトとしてのトレードオフの決断が求められる。
メインフレームの仕様は一見すると古めかしく見えるかもしれないが、そこには極限まで最適化されたハードウェアとOSの知恵が凝縮されている。その背後にあるアーキテクチャの本質を理解せずして、真に堅牢な次期システムの設計はあり得ない。
