お疲れ様です。今日も今日とて、JESのログやスプール出力の海に溺れていませんか?
メインフレームの世界では、COBOLの話題がどうしても主役になりがちですが、我らが誇るPL/I(Programming Language One)の堅牢性、そして何より「予約語を持たない(Contextual Keyword)」という類まれな言語仕様が生み出す奥深さは、世代が変わっても色褪せることはありません。
今回は、基幹システムのバックボーンを支えるVSAM(Virtual Storage Access Method)データセットのアクセス制御について、PL/Iの `ENVIRONMENT` 属性の指定方法とアクセス特性を徹底的に解説します。マイグレーション案件や、夜間バッチのパフォーマンスチューニングで頭を悩ませている若手エンジニアの皆さん、ぜひコーヒーでも飲みながら耳を傾けてください。
—
1. PL/IとVSAM:なぜ「 ENVIRONMENT属性 」が重要なのか
COBOLであれば `SELECT … ASSIGN TO` や `ORGANIZATION IS INDEXED` といった構文が環境部(Environment Division)の専売特許ですが、PL/Iの世界では、ファイルと物理的なデータセットの結びつきは `DECLARE` ステートメント内の `ENVIRONMENT`(略して `ENV`)属性が一手に引き受けます。
PL/Iの美しさ(あるいは恐ろしさ)は、「予約語が非常に少ない」点にあります。例えば、`TOTAL` という名前の変数を定義した後に、別の文脈で `TOTAL` をキーワードとして使っても、コンパイラは前後の文脈からそれを正しく解釈します。しかし、だからこそ `ENVIRONMENT` 属性の中に記述するサブオプション(KS, VSAM, RRDSなど)のスペルミスや構造の誤りは、コンパイルエラーとして検知されないまま、実行時のS0C4やオープンの異常終了(abend)として牙を向くのです。
基幹バッチで頻出する3つのVSAM組織(KSDS、ESDS、RRDS)について、PL/Iがどのようにそれらを捉え、どう記述すべきか、現場の作法とともに見ていきましょう。
—
2. VSAMデータセットの3大組織とPL/Iでの表現
① KSDS (Key-Sequenced Data Set) ~ キー順データセット
- 特性: プライマリーキーを持ち、ランダムアクセスと順次アクセスの両方が可能。基幹のマスターファイル(顧客マスタ、口座マスタなど)の9割はこれです。
- PL/Iでの指定: `ENV(VSAM ORGANIZATION(INDEXED))` または単に `ENV(VSAM) KEYED`。
② ESDS (Entry-Sequenced Data Set) ~ 順次データセット
- 特性: レコードが書き込まれた順序で格納され、キーを持たない。ログファイルや監査証跡などの蓄積に向いています。
- PL/Iでの指定: `ENV(VSAM ORGANIZATION(SEQUENTIAL))`。
③ RRDS (Relative Record Data Set) ~ 相対レコードデータセット
- 特性: 相対レコード番号(スロット番号)で直接アクセスする。ハッシュのバケットや、固定長の高速ルックアップテーブルによく使われます。
- PL/Iでの指定: `ENV(VSAM ORGANIZATION(RELATIVE))`。
—
3. 実践コード:VSAM/KSDSを自在に操るバッチプログラム
百聞は一見にしかず。KSDSマスターファイルに対し、キー順(ランダム)でレコードを読み込み、更新または新規書き込みを行う典型的なマスター更新バッチの骨組みをここに示します。現場のコーディング標準に合わせた大文字記述、適切なインデント、そしてビルトイン関数(`INDEX` や `SUBSTR` ではなく、今回は入出力制御に特化)の活用に注目してください。
1
- MODULE NAME: MSTRUPD
- DESCRIPION: VSAM/KSDSマスターファイル更新プログラム
MSTRUPD: PROC OPTIONS(MAIN);
/ ファイル宣言:KSDSマスターファイルを定義 /
DCL MSTR_FILE FILE RECORD
ENV(VSAM
ORGANIZATION(INDEXED)
);
/ トランザクションファイル宣言(順次ファイル) /
DCL TRN_FILE FILE RECORD
ENV(SEQUENTIAL);
/ 制御フラグおよびカウンタ /
DCL EOF_FLAG CHAR(1) INIT(‘OFF’);
DCL IO_ERR FIXED BIN(31) INIT(0);
/ レコード構造体(マスター) /
Dcl 1 MSTR_REC,
5 MSTR_KEY CHAR(8), / プライマリーキー /
5 MSTR_NAME CHAR(30), / 顧客名 /
5 MSTR_BAL FIXED DEC(9,2); / 残高 /
/ レコード構造体(トランザクション) /
Dcl 1 TRN_REC,
5 TRN_KEY CHAR(8),
5 TRN_CODE CHAR(1), / ‘U’:更新, ‘I’:追加 /
5 TRN_NAME CHAR(30),
5 TRN_BAL FIXED DEC(9,2);
/ 異常終了(ON条件)の捕捉設定 /
ON ENDFILE(TRN_FILE) EOF_FLAG = ‘ON’;
ON UNDEFINEDFILE(MSTR_FILE)
BEGIN;
PUT SKIP EDIT (‘FATAL: MSTR_FILE OPEN ERROR.’) (A);
SIGNAL CONDITION(ABORT_JOB);
END;
ON KEY(MSTR_FILE)
BEGIN;
PUT SKIP EDIT (‘VSAM KEY ERROR OCCURRED. KEY=’, MSTR_KEY) (A, A);
IO_ERR = 1;
END;
/ ファイルのオープン /
OPEN FILE(MSTR_FILE) UPDATE; / 更新モードでオープン /
OPEN FILE(TRN_FILE) INPUT;
/ メインループ /
READ FILE(TRN_FILE) INTO(TRN_REC);
DO WHILE (EOF_FLAG = ‘OFF’);
IO_ERR = 0;
MSTR_KEY = TRN_KEY;
/ KSDSに対するランダム読込(KEY指定) /
READ FILE(MSTR_FILE) INTO(MSTR_REC) KEY(MSTR_KEY);
IF IO_ERR = 0 THEN DO;
/ レコードが存在する場合(更新処理) /
IF TRN_CODE = ‘U’ THEN DO;
MSTR_NAME = TRN_NAME;
MSTR_BAL = TRN_BAL;
REWRITE FILE(MSTR_FILE) FROM(MSTR_REC);
END;
ELSE DO;
PUT SKIP EDIT (‘DUPLICATE KEY ERROR:’, MSTR_KEY) (A, A);
END;
END;
ELSE IF IO_ERR = 1 AND TRN_CODE = ‘I’ THEN DO;
/ レコードが存在せず、追加指示の場合(新規書込) /
MSTR_KEY = TRN_KEY;
MSTR_NAME = TRN_NAME;
MSTR_BAL = TRN_BAL;
WRITE FILE(MSTR_FILE) FROM(MSTR_REC);
END;
/ 次のトランザクション読込 /
READ FILE(TRN_FILE) INTO(TRN_REC);
END;
/ 正常クローズ /
CLOSE FILE(MSTR_FILE);
CLOSE FILE(TRN_FILE);
PUT SKIP EDIT (‘NORMAL END.’) (A);
RETURN;
END MSTRUPD;
—
4. ベテランが教える!デバッグとトラブルシューティングの極意
このコード、一見すると綺麗に書かれていますが、現場の運用保守において「お約束のハマりどころ」がいくつか存在します。私自身の苦い経験を踏まえて、後輩の皆さんへ伝授しておきます。
① `ON KEY` 条件のスコープとフォールスルーに注意せよ
上記のコードでは `ON KEY(MSTR_FILE)` でキーエラー(DUPLICATEKEY や KEYNOTFOUND)を捕捉しています。PL/IのONユニットは、COBOLの `INVALID KEY` 句とは異なり、割り込み処理(イベント駆動)として動作します。
エラーが発生した瞬間にONユニット内のブロックが実行され、その後続行不可能な場合はそのまま処理が流れてしまうため、上記の例のように `IO_ERR` フラグを立てて呼び出し元でハンドリングする設計が極めて重要です。これを怠ると、ファイルエラーを見落としたまま次の処理へ進み、データベースの整合性を破壊する大惨事(いわゆる「サイレントバグ」)に繋がります。
② `UPDATE` オープンと `REWRITE` の整合性
KSDSをランダムアクセスして書き換える場合、`OPEN FILE(…) UPDATE` と宣言しなければなりません。もし `INPUT` や `OUTPUT` でオープンしたファイルに対して `REWRITE` ステートメントを発行すると、コンパイルは通ったとしても実行時に容赦なくABEND(S0K7やS0C4、あるいはPL/I特有のIBMライブラリ異常終了)が発生します。
「読むだけならINPUT、変えるならUPDATE」――これはPL/Iプログラマの基本中の基本です。
③ バッファチューニング(BUFND / BUFNI)の罠
大量のKSDSレコードをランダム処理する場合、JCL側の `DD` ステートメントや、あるいはPL/Iの `ENVIRONMENT` 属性のサブオプション(`BUFND` や `BUFNI`)でVSAMのバッファ数を調整することがあります。
もし夜間バッチの処理時間が想定以上に遅い(I/Oネックになっている)場合は、ソースコード側のロジックを疑う前に、JCLの `ACB` マクロやプラットフォーム側のVSAM定義(IDCAMSのDEFINE CLUSTER)におけるCISIZEとバッファ割当を見直してください。PL/Iは指定された通りに忠実にI/Oを発行するため、VSAM側の器が小さければ、いくらコードを最適化してもCPUが待たされるだけです。
—
おわりに
PL/Iの `ENVIRONMENT` 属性とVSAMアクセスの制御は、一見すると泥臭く、現代のWeb系エンジニアから見れば古臭い技術に映るかもしれません。しかし、日本の金融、流通、公共を支える巨大な基幹システムにおいて、この「1秒間に何万件ものレコードを確実かつ安全に処理する」仕組みは、今この瞬間も止まることなく稼働し続けています。
「予約語を持たない」自由度の高い言語だからこそ、書き手の技量とルール遵守がコードの品質をダイレクトに左右します。今回の解説が、皆さんの日々のバッチ改修やマイグレーション調査の一助となれば幸いです。
それでは、次のジョブストリームがが無事にCC=0000で終わることを祈っております!
