QSAMとは(概要、特長)
QSAM(Queued Sequential Access Method:待機順アクセス法)は、順次データセットをレコード(論理レコード)単位にアクセスするためのプログラミング・インタフェースです。特長は、レコード単位であるためプログラミングが容易であること、自動的なバッファリング制御により高速なI/O処理が行われることです。
QSAMの他に、BSAM(Basic Sequential Access Method:基本順アクセス法)もあります。こちらはレコード単位ではなくブロック単位のアクセスとなり、自動バッファリングは行われません。バッファリングの制御はプログラムが自分で行う必要があります。BSAMは、途中のブロックを読み飛ばしたり後ろへ戻ったりといった任意の位置からのI/Oを行ったり、I/O処理と他の処理をオーバーラップさせる非同期I/O制御を行うなどのきめ細かなI/O制御ができますが、それらは一般のデータセットのアクセスではほとんど必要とされません。
QSAMはビギナー向けのEasyなI/Oというイメージがありますが、内部では効率のよいバッファリング制御がなされており高速なI/O処理が可能です。READ処理の場合、データセットのOPENと同時に5ブロック分が先読みされます。アプリケーションがGETマクロでレコードの読み込みを要求すると、バッファー内のブロックをデブロッキングして論理レコードをプログラムに渡す(移動モード)か、レコードの先頭アドレスを通知します(位置づけモード)。QSAMは論理レコード単位でアクセスできるため、ブロッキングとデブロッキングと言う本来のアプリケーションには必要の無い処理を作る必要がなく、プログラムはその分ビジネスロジックに専念できます。そして、複雑なバッファリング制御を自ら行わなくても高速なI/O処理を行うことができ、しかも、I/O処理に使用するバッファーの数はJCLでも指定できるのでデータ量に応じてバッファー数を容易に調整できます(省略時は5ブロック分)。今日では特別な理由がない限りBSAMを選択する必要はないでしょう。COBOLやPL/Iなどのコンパイラー言語でも順次データセットのアクセスにはQSAMが利用されています。
QSAMの機能
QSAMには次のI/O機能が提供されています。(抜粋)
- OPEN
- CLOSE
- GET
- PUT
- PUTX
- RELSE
- TRUNC
データセットをオープンする。バッファー・プールが用意されていなければI/O用のバッファー・プールを作成する。読み込みOPENであればバッファーにブロックを先読みする。
データセットをクローズする。バッファー内に残っている未書き出しのブロックがあればそれをデータセットに書き出す。
データセットから次の論理レコードを読む。実際にはバッファー内のブロックをデブロックして次の論理レコードを取り出す。バッファーが空になっていれば続きのブロックを読み込み次の論理レコードを取り出す。
データセットへ次の論理レコードを書く。実際にはバッファー内に論理レコードを並べてブロックキングを行うだけ。バッファーが一杯になればデータセットにブロックとして書き出す。※PUTが完了してもそこでバッファーが一杯にならなければ書き出したレコードはまだデータセットには書き出されていない。
論理レコードを更新する。更新処理の場合、PUTXに先だってGETを発行しておく。GETで読んだ論理レコードの内容を書き換えてからPUTXで書き戻す形になる。内容の変更はできるがレコードの長さは変えられない。なお、PUTXにはこの他に出力モードというのがあり、入力と出力に異なるデータセットを指定することもできる。データセットの複写処理や新たなレコードを追加するなどができる。
バッファー内の未処理レコードを破棄する。バッファーとブロックは対応しているので、ブロック内の残りレコードを捨てると考えてよい。RELSEを発行すると次のGETでは次のブロックの最初のレコードが通知される。
バッファー内の未出力レコードをバッファーが一杯になるのを待たずに書き出す。もうこれ以上PUTするレコードがなくなった時、TRUNCを出すことでブロック長に満たないショートブロックとして書き出すことができる。ただし、TRUNCを発行しなくてもCLOSEマクロを出せばCLOSEルーチンの中でTRUNC処理が行われるので、特別な理由がなければ直接TRUNCを使う必要はない。
バッファ-・プール
QSAMは、バッファーを使用して複数ブロックをまとめてI/Oする効率が良いアクセス方式です。バッファー数はプログラムまたはJCLで変更できます。プログラムで指定する場合は、DCBマクロにBUFNOパラメーターを指定します。JCLで指定する場合は、DDステートメントにDCB=BUFNOパラメーターを指定します。どちらにも指定されない場合は「5」が内部で採用されます。なお、プログラムでDCBに指定してしまうとJCLのDDステートメントに指定しても有効にならないので、特別な事情がない限りDCBマクロでは指定しない方がよいでしょう。
実際どの程度のバッファー数がパフォーマンスに効果があるかは一概には言えませんが、昔テスト用プログラムで実測したことがあって、その結果ではバッファー数は5~10ぐらいまでが、バッファ数1に比べてメモリー量も程よく、CPU使用量も減りELAPも短縮される、となっていました。11以上は(例えば100個用意しても)CPU量もELAPもさほどの減少効果はなく、メモリー量だけがバッファー数分しっかり増える、となっていました。つまり、メモリー量に見合うほどCPUとELAPは減らず(メモリーが100倍でもCPUが1/100になるわけではない)、あるところで頭打ちになるわけです。OSのデフォルトが5なのを考えても妥当な数だと考えます。テストしたデータセットのブロック長は23476バイトでしたが、ブロック長が比較的小さなデータセット(数千バイト以下)の場合は、もう少し増やしても(20~30ぐらいまで)いいかも知れません。
なお、MSPではQSAMのI/Oバッファは16MBの上の拡張域に作成できないので、ブロック長の大きなデータセットではむやみにバッファー数を増やしてもリージョンが圧迫されるだけです。MVSとVOS3であってもQSAMバッファを31ビット領域に作成するかどうかはプログラム次第です。
プログラムが使用したバッファ-・プールの解放は、基本的にQSAMルーチンではなくQSAMを使用したアプリケーション・プログラムの責任でした。従来は、CLOSEマクロの後に同じDCBを再使用してそのデータセットを再OPENしないのであれば、FREEPOOLマクロによってバッファーを明示的に解放する必要がありました。オンライン処理(対話処理)などで処理するデータセットを端末から入力させるような場合、処理の都度DCBをGETMAINし直してOPENに使うようなプログラムでは、プログラムでの処理が繰り返されていくうちにやがてリージョン不足になってしまうことになってしまいます。CLOSEはきちんと出すがFREEPOOLはしていない、というプログラマーも結構いるようです。バッチ処理ではあまり問題になりませんが、使った資源は解放するのが基本なので、CLOSEとFREEPOOLはペアーで覚えることを奨めます。
しかし、FREEPOOLは不要な場合もあります。それはMVSでDCBEを使いQSAMのバッファーを31ビット領域に確保させる場合です。31ビット領域内のバッファー・プールでは、QSAMが使ったバッファーはCLOSEルーチンの中で解放されるためFREEPOOLは必要ありません。また、富士通のXSPもFREEPOOLはありません。MSPとXSPの両方をサポートするプログラムの場合、QSAMそのもののマクロや使い方は似ていますが(DCBがFCBとなるが基本は同じ)細かな点で相違点があることに注意します。日立のVOS3ではQSAMバッファーをMVSと同じく31ビット領域に確保できますが、解放にはMVSと違ってFREEPOOLマクロが必要です。
書き込みデータの保証
QSAMは使いやすく性能もよいアクセス・メソッドですが、x37ABENDのリカバリーが難しい点に注意します。プログラムをRE-RUNすることでデータをリカバリーできるなら問題ありませんが、PUTした後は元のレコードが消えてしまいRE-RUNで再作成もできない場合は、x37ABENDしないだけの十分な大きさのデータセットを確保するか、BSAMを使うか、データセットをVSAMに変更できないかの検討も必要です。
QSAMではPUT要求の処理と実際のデータセットへの書き出し処理は連動しません。PUTの完了とはバッファー内にレコード・データが書き込まれるだけです。バッファーが一杯になった後の次のPUTの時にI/Oが行われます。それは、最初のPUTよりもはるかに後になってからかも知れません。x37ABEND(他のI/Oエラーも含め)は実際のI/Oが行われないと起きませんので、起きたときにはアプリケーションからはレコード・データが消えています。また、必ずしもPUTで起きるわけでもありません。最終ブロックの書き込みであれば、CLOSEの延長で発生します。ABEND事象そのものはDCB ABEND出口やESTAEでトラップできますが、リカバリーはかなり面倒です。
具体的にどの程度の量のレコードがロストするかは、ブロッキング・ファクター×BUFNO数で求められます。例えば、RECFM=FB、BLKSIZE=800、LRECL=80なら1ブロックに10レコード入るので、BUFNOが5なら50レコードがロストします。BUFNOが1なら10レコード、10なら100レコードのロストです。固定長レコードの場合は、BUFNO分のブロック・データをメモリーなどにSAVEすればリカバることはできるでしょう。BUFNOの実際の値はOPEN後のDCBを見ればわかります。しかし可変長レコードの場合、ブロッキング・ファクターは個々の論理レコード長に左右されブロック毎に変わります。正確なリカバリーを行うのは無理ではないにしてもかなり面倒です。
筆者は、パフォーマンス上の理由があってどうしてもQSAMを採用する必要があり、x37ABENDのリカバリー用にPUTしたレコードのコピーをデータ空間に持つ方法を使ったことがあります。その前に、QSAMバッファーには書き出せなかったブロックが残っているから、再OPENした後にそれをTRUNCマクロでRE-WRITEできないかなどいろいろ試したこともありました。メモリーだけを見るとレコードのデータが残っているし何とかできないのか、ということです。バッファーを予めGETPOOLしたりも試しましたが、結局DCB ABEND出口から戻った時にはプール内のバッファーは返却されてしまっていました。そのため、バッファーの先頭に置かれているレコードの先頭部分はバッファー・チェインのアドレスで壊されてる箇所もあって、上手く行かなかった記録が残ってました。何かやり方がないかいろいろとあがいたのですが、結局見つけられませんでした。
いずれにしても、x37ABENDをABEND出口でトラップし、無視を要求してPUTの直後から再開させても、そのPUTで書き込もうとしたレコードより前のレコードは全部がデータセットに書き出されているわけではありません。復元できない重要なデータを順次データセットや区分データセットのメンバーとして書き込む場合、このような特性は知っておくべきことです。
サンプルコード
いずれのサンプルもデータセットのコピーを行うQSAMプログラムです。MVS、MSP、VOS3に共通な24ビットモードのQSAMで、マクロの使用に関しては同じです。ただし、MVSのHLASM前提のコーディングなのでMSPとVOS3で試す場合、そのままではアセンブル・エラーになります。必要に応じて修正してください。SYSUT1からSYSUT2のDCBにRECFM、BLKSZ、LRECLをコピーするためのMVC命令の箇所です。なお、31ビットモードのQSAMプログラミングについては、こちらのページ「DFP31ビットモード・プロセッシング」でも解説しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
UT1 USING IHADCB,SYSUT1 ADDRESS FOR SYSUT1 DCB UT2 USING IHADCB,SYSUT2 ADDRESS FOR SYSUT2 DCB OPEN (SYSUT1,INPUT) OPEN INPUT DATASET(ORIGIN) MVC UT2.DCBRECFM,UT1.DCBRECFM COPY RECFM TO SYSUT2 MVC UT2.DCBBLKSI,UT1.DCBBLKSI COPY BLKSZ TO SYSUT2 MVC UT2.DCBLRECL,UT1.DCBLRECL COPY LRECL TO SYSUT2 OPEN (SYSUT2,OUTPUT) OPEN OUTPUT DATASET(DEST) LOOP DS 0H GET SYSUT1 GET NEXT RECORD LR R0,R1 LOAD LOGOCAL RECORD ADDRESS PUT SYSUT2,(0) PUT NEXT RECORD B LOOP LOOP FOR NEXT RECORD EODAD DS 0H CLOSE (SYSUT1,,SYSUT2) CLOSE USED DATASET FREEPOOL SYSUT1 FREE QSAM I/O BUFFER FREEPOOL SYSUT2 FREE QSAM I/O BUFFER B EXITPROC PROCESSING DONE SPACE , SYSUT1 DCB DDNAME=SYSUT1,MACRF=GL,DSORG=PS,EODAD=EODAD SYSUT2 DCB DDNAME=SYSUT2,MACRF=PM,DSORG=PS |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
UT1 USING IHADCB,SYSUT1 ADDRESS FOR SYSUT1 DCB UT2 USING IHADCB,SYSUT2 ADDRESS FOR SYSUT2 DCB OPEN (SYSUT1,INPUT) OPEN INPUT DATASET(ORIGIN) MVC UT2.DCBRECFM,UT1.DCBRECFM COPY RECFM TO SYSUT2 MVC UT2.DCBLRECL,UT1.DCBLRECL COPY LRECL TO SYSUT2 BLKSIZEのコピーはしない。RECFMとLRECLは変えないがBLKSIZEはコピー先 データセットの装置タイプに合わせて最適化される。(MVSのみ) OPEN (SYSUT2,OUTPUT) OPEN OUTPUT DATASET(DEST) LOOP DS 0H GET SYSUT1 GET NEXT RECORD LTR R0,R1 EOF ? BZ EODPROC YES, DO CLOSE DATASET PUT SYSUT2,(0) PUT NEXT RECORD B LOOP LOOP FOR NEXT RECORD EODPROC DS 0H CLOSE (SYSUT1,,SYSUT2) CLOSE USED DATASET FREEPOOL SYSUT1 FREE QSAM I/O BUFFER FREEPOOL SYSUT2 FREE QSAM I/O BUFFER B EXITPROC PROCESSING DONE SPACE , EODAD DS 0H SLR R1,R1 INDICATE DATASET IS EOD BR R14 RETURN TO MAIN LINE GLモードならEODADルーチンはこのように汎用化できる。 複数のDCBを使うプログラムなどでは便利な方法。 GR14の示すアドレスは、GETマクロの次のアドレスを指している。 SPACE , SYSUT1 DCB DDNAME=SYSUT1,MACRF=GL,DSORG=PS,EODAD=EODAD SYSUT2 DCB DDNAME=SYSUT2,MACRF=PM,DSORG=PS |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
UT1 USING IHADCB,SYSUT1 ADDRESS FOR SYSUT1 DCB UT2 USING IHADCB,SYSUT2 ADDRESS FOR SYSUT2 DCB OPEN (SYSUT1,INPUT) OPEN INPUT DATASET(ORIGIN) MVC UT2.DCBRECFM,UT1.DCBRECFM COPY RECFM TO SYSUT2 MVC UT2.DCBBLKSI,UT1.DCBBLKSI COPY BLKSZ TO SYSUT2 MVC UT2.DCBLRECL,UT1.DCBLRECL COPY LRECL TO SYSUT2 OPEN (SYSUT2,OUTPUT) OPEN OUTPUT DATASET(DEST) LOOP DS 0H GET SYSUT1 GET NEXT RECORD PUTX SYSUT2,SYSUT1 PUT NEXT RECORD PUTではなくPUTXの出力モードを使用した例。書き込むレコードアドレスは コピー元のDCBから得られるようになっている。 B LOOP LOOP FOR NEXT RECORD EODAD DS 0H CLOSE (SYSUT1,,SYSUT2) CLOSE USED DATASET FREEPOOL SYSUT1 FREE QSAM I/O BUFFER FREEPOOL SYSUT2 FREE QSAM I/O BUFFER B EXITPROC PROCESSING DONE SPACE , SYSUT1 DCB DDNAME=SYSUT1,MACRF=GL,DSORG=PS,EODAD=EODAD SYSUT2 DCB DDNAME=SYSUT2,MACRF=PM,DSORG=PS |