おい、調子はどうだ?
今、オンラインが落とせない時間帯のバッチ窓口で、VSAMの入出力エラー(いわゆるABENDコード `ASRA` や `0C4`、あるいはファイルステータスの `35` や `46`)に頭を抱えてこのブログに辿り着いたってところか?
気持ちは痛いほどよく分かる。メインフレームの基幹系システムを支えるPL/Iプログラミングにおいて、ファイルアクセス、特にVSAM(KSDSやESDS)を相手にする際の `ENVIRONMENT` 属性の指定ミスは、一発で本番障害を引き起こす「地雷原」だ。
C言語やJavaあたりから入ってきた連中は「変数名に予約語がない代わりに、属性の組み合わせでコンパイル結果やランタイムの挙動がガラリと変わる」というPL/Iの変態的な……いや、懐の深い仕様に面食らう。中でも今日のテーマである `ENVIRONMENT` 属性の `KEYED`、`SEQUENTIAL`、`DIRECT` の指定が、コンパイラ生成コードのアクセスルーチンにどう影響するか は、バッチ性能のチューニングや保守開発において絶対に外せないキモだ。
今日は、長年数々のレガシーマイグレーションや大規模バッチ改修を潜り抜けてきた俺が、このアクセスパス決定のメカニズムを現場のリアルな視点で叩き込んでやる。コーヒーでも飲みながらじっくり読んでくれ。
—
1. なぜPL/IのENVIRONMENT属性がこれほど重要なのか?
COBOLであれば、`ORGANIZATION IS INDEXED` や `ACCESS MODE IS DYNAMIC` といった句が `DATA DIVISION` の `SELECT` 文に直感的に記述される。しかし、PL/Iの世界では、ファイル(DATASET)の性格を決めるのは `DECLARE` 文における `FILE` 変数の `ENVIRONMENT` 属性 だ。
PL/Iコンパイラは、この `ENVIRONMENT` 属性の指定(および `READ`, `WRITE`, `REWRITE`, `DELETE`, `GET`, `PUT` といった動詞の組み合わせ)を見て、内部的にどのVSAMアクセスモジュール(ACB/RPLの制御ブロック群)を呼び出すかを決定する。
ここで間違った組み合わせをすると、何が起きるか?
- コンパイルは平然と通るのに、実行時に `UNDEFINEDFILE` 条件(ON-UNITで捕捉しないと即ABEND)が発生する。
- 本来はダイレクトアクセスしたいのに全件シーケンシャルスキャン(CIのムダ撃ち)になり、夜間バッチのウィンドウを盛大にブチ破る。
- `KEYED` を書き忘れたがために `READ (FILE) KEY (KEY_VAL)` が構文エラーになる。
要するに、ここを適当にコピペで済ませているプログラマは、メインフレームエンジニアとして三流だ。しっかりとコードの裏側の動きをイメージできるようになろう。
—
2. アクセスモードの三銃士:SEQUENTIAL / DIRECT / KEYED の正体
まずは、それぞれの属性が持つ意味と、コンパイラに与える影響を整理する。
1. `SEQUENTIAL`(シーケンシャルアクセス)
- レコードを物理的、あるいは論理的な順序通りに先頭から順次処理する場合に指定する。
- VSAM KSDSやESDSに対して `GET FILE(…)`(または `READ FILE(…)`)を実行すると、内部的には `GET NEXT` リクエストが発行される。
2. `DIRECT`(ダイレクトアクセス)
- キーや相対レコード番号を指定して、特定のレコードを直接狙い撃ちする場合に指定する。
- VSAM KSDSに対して `READ FILE(…) KEY(…)` を行うための必須条件だ。内部的には `GET EQUAL` リクエストが発行される。
3. `KEYED`(キー修飾アクセス)
- 「このファイルはキーを使ってアクセスされる可能性がある」ことをコンパイラとランタイムに宣言する修飾子だ。
- これを指定することで、PL/IのランタイムライブラリはRPL(Request Parameter List)にキーフィールドのアドレスや長さをバインドするための領域を確保する。
⚠️ 現場でよくある勘違い
「`DIRECT` にしておけば、順番に読むこともできるだろう」と思っていないか?
実は、`DIRECT` を指定したファイルに対して `GET PREV` や順次読み込みを行おうとすると、ランタイムエラーになるか、意図しない挙動を示すことがある。順次処理と直接参照の両方を行いたい場合は、VSAM KSDSの特性に合わせて `KEYED ENVIRONMENT( … )` かつ適切な入出力動詞を使い分ける設計が必要になる。
—
3. 実践!VSAM KSDSを自在に操るPL/Iコード例
百聞は一見に如かずだ。KSDS(Key-Sequenced Data Set)に対して、キーを指定したダイレクト参照と、全件のシーケンシャル更新を行う実用的なサンプルコードを示す。
大文字ベースの記述、適切なインデント、そしてPL/Iが誇る強力なビルトイン関数(`INDEX`, `SUBSTR` など)や `ON-UNIT` による例外処理の作法を見てほしい。
—————————————————————-
- プログラム名: VSMACC01
- 概要: VSAM(KSDS)ファイルに対するダイレクトおよび
- シーケンシャルアクセスの制御サンプル
—————————————————————-
VSMACC01: PROC OPTIONS(MAIN);
— ファイル宣言とENVIRONMENT属性の指定
— ここでKEYEDとSEQUENTIALを明示し、アクセスパスを固定する
DECLARE CUST_FILE FILE RECORD
ENV(VSAM
KSDS
KEYED
);
— 顧客マスタ レコード構造体定義
DECLARE 1 CUST_REC,
5 Cust-ID CHAR(5), — 顧客ID (キー)
5 Cust-Name CHAR(30), — 顧客名
5 Cust-Status CHAR(10); — ステータス
DECLARE W_KEY CHAR(5);
DECLARE W_EOF_FLG BIT(1) INIT(‘0’B);
— 例外処理(ON-UNIT)の定義:ファイル終了(ENDFILE)の捕捉
ON ENDFILE (CUST_FILE)
W_EOF_FLG = ‘1’B;
— 例外処理(ON-UNIT):キー未検出時(KEY)の捕捉
ON KEY (CUST_FILE) BEGIN;
PUT SKIP LIST(‘ 警告: 指定された顧客IDは存在しません ‘);
GOTO READ_NEXT_STEP;
END;
— ファイルオープン(入出力両用: UPDATE)
OPEN FILE (CUST_FILE) UPDATE;
————————————————————
- 1. ダイレクトアクセス(DIRECT/KEYED)のテスト
————————————————————
W_KEY = ‘A0123’;
PUT SKIP EDIT (‘[DIRECT READ] 検索キー: ‘, W_KEY) (A, A);
— READ文によるダイレクト参照(KEY指定によりGET EQUALが発動)
READ FILE (CUST_FILE) INTO (CUST_REC) KEY (W_KEY);
PUT SKIP LIST(‘ 取得成功 -> 顧客名: ‘ || CUST_REC.Cust-Name);
— レコードの内容を書き換えてREWRITE(更新)
Cust-Status = ‘ACTIVE’;
REWRITE FILE (CUST_FILE) FROM (CUST_REC);
PUT SKIP LIST(‘ -> レコードを更新しました。’);
————————————————————
- 2. シーケンシャルアクセス(SEQUENTIAL)のテスト
————————————————————
READ_NEXT_STEP:
PUT SKIP LIST(‘——————————————–‘);
PUT SKIP LIST(‘[SEQUENTIAL READ] 全件順次スキャンを開始します’);
— 先頭から順次読み込むためにファイルの位置をリセット
— (※実装上は一度CLOSE/OPENするか、START文を使う場合もある)
CLOSE FILE (CUST_FILE);
OPEN FILE (CUST_FILE) INPUT;
— ループ処理によるシーケンシャル読み込み
DO WHILE (W_EOF_FLG = ‘0’B);
— GET文による順次読み込み(GET NEXTが発動)
GET FILE (CUST_FILE) INTO (CUST_REC);
IF W_EOF_FLG = ‘0’B THEN
PUT SKIP EDIT (‘ ID: ‘, Cust-ID, ‘ / Name: ‘, Cust-Name)
(A, A, A, A);
END;
— クローズ処理
CLOSE FILE (CUST_FILE);
PUT SKIP LIST(‘正常終了しました。’);
END VSMACC01;
—
4. コンパイル結果とランタイムの裏側:シニアからのアドバイス
上記のコードをよく見てほしい。同じ `CUST_FILE` というファイル変数であっても、前半ではダイレクトアクセス(`READ … KEY(…)`)を行い、後半ではシーケンシャルアクセス(`GET …`)を行っている。
ここでベテランとして後輩に一番伝えたい注意点がある。
> 「ファイル変数一つに対して、ダイレクトとシーケンシャルの両方の側面を持たせる場合、`ENVIRONMENT` 属性には少なくとも `KEYED` が必須であり、かつオープンモード(`INPUT` / `UPDATE`)と動詞(`READ` / `GET`)の整合性をコンパイラがどう解釈するかを常に意識しろ」
もし `ENVIRONMENT` に `KEYED` を書き忘れた状態で `READ … KEY(…)` を書くと、コンパイルエラー(IBM Enterprise PL/Iコンパイラであれば `IBM1235I` あたりのメッセージ)が即座に飛んできて目を覚ますことになる。しかし、もっとタチが悪いのは、「動くには動くが、内部で無駄なパスやロック競合を引き起こすケース」だ。
- 無駄なCICS/IMS環境での共用:
オンライン(CICS)のFCT(File Control Table)や、バッチのJCLで定義されるDD文の属性(DISP=SHRなど)と、PL/I側の `ENVIRONMENT` 属性が食い違っていると、エンキュー(ENQ)競合によるデッドロックや、思わぬパフォーマンス低下を引き起こす。
- ON-UNIT(例外処理)の設計:
VSAMアクセスで最も怖いのは、キー重複(DUPKEY)やレコード不在(NOTFND)だ。PL/Iではこれらを `ON KEY` などの条件として捕捉できる。これを怠ると、ファイルエラー一つでバッチ全体が無慈悲にU0000(あるいはシステムABEND)でブッ飛ぶことになり、夜間運用チームから冷たい視線を浴びることになる。
—
5. おわりに
PL/Iは古い言語だと言われる。ネットでググっても、今やAIが生成した浅い要約か、20年前のメインフレームフォーラムの過去ログしかヒットしないかもしれない。
だが、日本の金融、証券、航空、流通といった止まることが許されない基幹システムの心臓部では、今この瞬間もPL/Iで書かれた巨大なバッチ群が何百万件ものトランザクションを猛烈なスピードで処理し続けている。
そのコードの挙動を支配しているのは、今日解説した `ENVIRONMENT` 属性であり、コンパイラが吐き出すアクセスパスの選択だ。「なぜこの指定が必要なのか」「これを変えるとランタイムがどう動くのか」を突き詰めることこそが、真のメインフレーム・システムアーキテクトへの第一歩だ。
さて、ウンチクはこの辺にして、そろそろ次のモダナイゼーション案件の設計書に戻るとするか。
お互い、本番障害のない堅牢なコードを書いていこうぜ。質問があればいつでも声をかけてくれ!
