大量データを高速に処理するためのテクニック
はじめに
Delete (オペレーションコード 4) の拡張オペレーションである、Get Next Delete Extended (オペレーションコード 85) が Zen 14.1 で実装されるということで、使ってみました。
また、この機会に既に実装されているGet Next (オペレーションコード 6) の拡張オペレーションであるGet Next Extended (オペレーションコード 36) についても説明したいと思います。
通常の Delete、Get Next と それぞれの Extended の速度比較も行っていますのでぜひご覧になってください。
サンプルソースも最後に書いていますので参考になればと思います。
このブログの目的
拡張オペレーションは一括で処理することによる「高速化」と「通信回数の削減」の2つのメリットがあります。
- 現在の削除処理や読込処理の時間を短縮したい。
- クライアントーサーバー間の通信回数を抑え、パフォーマンスをアップしたい。
- IoT など大量データへの対応。
などの改善、パフォーマンスアップのきっかけにこのブログがなれば幸いです。
ブログの内容
前半で Get Next Delete Extended の説明と速度比較。
後半で Get Next Extended の説明と速度比較。
最後にサンプルソースを書いています。
BTRVEX() 関数とは
BTRVEX() 関数は、PSQL v13 R2 で追加された関数です。
BTRV() 関数では 64K byteであったデータバッファのサイズが 256K byteまで拡張され多くのデータを受け渡し可能となりました。
BTRV() 関数と混在して使用できます。
Extended で多くのデータを一括で扱いたいので、ここでは BTRVEX() 関数を使用しています。
Get Next Delete Extended とは
これまで削除には Delete (オペレーションコード 4) を利用していましたが、こちらは1件ずつしか削除できませんでした。
対して Get Next Delete Extended は一括削除が可能なオペレーションです。オペレーションコードは 「85」 となります。
Get Next Delete Extended の一括で削除可能な最大件数
残念ながら無制限に削除できるわけではありません。BTRVEX() 関数のデータバッファの上限サイズが 256K byte であるためです。Get Next Delete Extended を実行すると、削除したレコードのポジション情報をデータバッファに入れて返してきます。1件につき 10 byte のポジション情報が返ってくるため、256K byte ÷ 10 byte = 25600 件が一回の Get Next Delete Extended で削除可能な件数となります。(実際にはポジション情報だけでなく、削除した件数(2 byte)もデータバッファに格納されて返ってくるので、25600 件弱が最大件数となります。)
後述する Get Next Delete Extended のサンプルでは 25000件を一括削除するようにしています。
Delete と Get Next Delete Extended の基本ロジック
キーの昇順でレコード削除を行う場合の基本ロジックはそれぞれ以下のようになります。
Deleteは
Get 命令で削除したいレコードに移動 → Delete → 「Get Next → Delete」
Get Next Delete Extended は
Get 命令で削除したいレコードに移動 → 「Get Next Delete Extended」
となります。
赤文字のかっこ部分が繰り返し部分となります。Get Next Delete Extended のほうが簡潔であることがわかります。
例えば 10万件の削除を行う場合、Delete は 「Get Next → Delete」を 10 万回程繰り返すことになりますが、Get Next Delete Extended は「Get Next Delete Extended」を 4 回繰り返すだけとなります(一括削除 25000件指定時)。
クライアントサーバーのような通信回数を抑えたい場合にもかなり有効な方法となりそうです。
Get Next Delete Extended のデータバッファについて
Get Next Delete Extended のデータバッファは、BTRVEX() 関数に渡す情報と、BTRVEX() 関数から帰ってくる情報が違っています。そして引数で渡すデータバッファはひとつです。
なので今回は Visual Studio のプロジェクトを C言語で作成したのですが、データバッファは共用体を使用しています。
こちらがサンプルソースから共用体部分を抜粋したものです。
union unibuf_tmp
{
struct exbuf_tmp //BTRVEX関数への入力データバッファ
{
short int blen; //ヘッダ1 入力データバッファの正確な長さ
char sp[2]; //ヘッダ2 ポジショニングされているレコードから
short int rc; //フィルター1 リジェクトカウント最大値
short int fn; //フィルター2 フィルター条件
unsigned short int recc; //ディスクリプタ1 削除するレコード数
short int fc; //ディスクリプタ2 抽出するフィールド数
} exbuf;
struct //BTRVEX関数からの結果出力データバッファの構造体
{
short int cnt; //削除されたレコード数
struct
{
short int len; //0固定
long long pos; //削除されたレコードのポジション
} data[25000]; //25000件分の削除結果を格納します
} recbuf;
} unibuf;
共用体の中に構造体をふたつ宣言しています。ひとつ目(exbuf)が BTRVEX() 関数に渡す情報を格納する構造体です。
ふたつ目(recbuf)が BTRVEX() 関数がポジション情報をいれてくる構造体となります。25000件分を確保しています。
Get Next Delete Extended を BTRVEX() 関数で実行するときは
BTRVEX() 関数に渡すデータバッファの設定部分を抜粋し、コメント形式で説明をいれました。
//入力データバッファに値をセット
unibuf.exbuf.blen = sizeof(unibuf.exbuf); // 渡すデータバッファの正確なサイズ
sprintf(&unibuf.exbuf.sp[0], “UC”); // 現在のポジションのレコードから削除
unibuf.exbuf.rc = 0; // リジェクトカウント
unibuf.exbuf.fn = 0; // フィルターなし
if (rcnt > 25000)
unibuf.exbuf.recc = 25000; // 削除する件数
else
unibuf.exbuf.recc = rcnt; // 削除する件数
unibuf.exbuf.fc = 0; // 抽出するフィールド 0固定
buflenex = sizeof(unibuf.recbuf); //入力より出力データバッファのサイズが大きいので出力サイズをセット
kbuflenex = 255;
//Get Next Delete Extended オペレーションコード:85
stat = BTRVEX(85, posb, &unibuf.exbuf, &buflenex, kbuf,kbuflenex, 0);
ポイントは BTRVEX() 関数の引数の4つ目の「buflenex」です。これはデータバッファのサイズを渡すのですが、渡す情報のサイズでなく、BTRVEX() 関数が返してくるポジション情報を格納するデータバッファのサイズを指定します。25000 件のポジション情報を格納できるサイズを渡さないと、BTRVEX() 関数は「データバッファサイズが足りません」のエラーとなります。
Delete と Get Next Delete Extended の実行速度比較
- 1億件のレコードの古いものから10万件を削除
Delete | 16.667秒 |
Get Next Delete Extended | 2.130秒 |
- 1億件のレコードの古いものから100万件を削除
Delete | 1分24秒 |
Get Next Delete Extended | 34秒 |
Get Next Extended とは
ここから Get Next Extended の説明となります。
これまで Get には Get Next (オペレーションコード 6) を利用していましたが、こちらは1件ずつしか読込できませんでした。
対して Get Next Extended は一括読込が可能なオペレーションです。オペレーションコードは 「36」 となります。
Get Next Extended の一括で読込可能な最大件数
残念ながらこちらも無制限に読込できるわけではありません。BTRVEX() 関数のデータバッファの上限サイズが 256K byte であるためです。Get Next Extended を実行すると、取得した件数、長さ、ポジション、そしてレコード自体がデータバッファに格納されます。Get Next Delete Extended とは違い、レコード自体を格納するデータバッファを用意する必要があります。
取得可能件数の算出方法は
2 + ((2 + 8 + レコード長) × 取得可能件数) < 256K byte なので 取得可能件数 < 255998 ÷ (2 + 8 + レコード長) となります。 |
取得件数 < 255998 ÷ (2 + 8 + 808)
取得件数 < 312.9…
となります。後述するサンプルソースでは 300 件を一括読込するようにしています。
Get Next と Get Next Extended の基本ロジック
Get Next は Get First → 「Get Next」となります。Get Next Extended は Get First → 「Get Next Extended」となります。
赤文字のかっこ部分が繰り返し部分となります。
例えば 10万件の読込を行う場合、Get Next は「Get Next」を 10 万回程繰り返すことになりますが、Get Next Extended は「Get Next Extended」を 333 回程繰り返すだけとなります(一括読込 300件指定時)。
こちらもクライアントサーバーのような通信回数を抑えたい場合にもかなり有効な方法となりそうです。
Get Next Extended のデータバッファについて
Get Next Extended のデータバッファも、BTRVEX() 関数に渡す情報と、BTRVEX() 関数から返ってくる情報が違っています。そして引数で渡すデータバッファはひとつです。
こちらがサンプルソースから共用体部分を抜粋したものです。
union unibuf_tmp
{
struct //BTRVEX関数への入力バッファ
{
short int blen; //ヘッダ1 入力バッファの正確な長さ
char sp[2]; //ヘッダ2 ポジショニングされているレコードから
short int rc; //フィルター1 リジェクトカウント最大値
short int fn; //フィルター2 フィルター条件
unsigned short int recc; //ディスクリプタ(固定) 取得するレコード数
short int fc; //ディスクリプタ(固定) フィールド数
short int df; //ディスクリプタ(繰返) フィールド長
short int ds; //ディスクリプタ(繰返) オフセット
} exbuf;
struct //BTRVEX関数からの結果出力バッファ
{
short int cnt; //取得したレコード数
struct
{
short int len; //取得したレコードの長さ
long long pos; //取得したレコードのポジション
struct //取得したレコードが格納される。テーブルの構成にあわせてください。
{
ULONGLONG tstamp;
ULONGLONG item[100];
} record;
} data[300];
} recbuf;
} unibuf;
共用体の中に構造体をふたつ宣言しています。ひとつ目(exbuf)が BTRVEX() 関数に渡す情報を格納する構造体です。
ふたつ目(recbuf)が BTRVEX() 関数が読込件数や取得したレコードをいれてくる構造体となります。300件分を確保しています。上記の構造体「record」の部分を取得してくるテーブル(ファイル)の構成にあわせてください。
Get Next Extended を BTRVEX() 関数で実行するときは
BTRVEX() 関数に渡すデータバッファの設定部分を抜粋し、コメント形式で説明をいれました。
//入力データバッファに値をセット
unibuf.exbuf.blen = sizeof(unibuf.exbuf); // ヘッダ 渡すデータバッファの正確なサイズ
sprintf(&unibuf.exbuf.sp[0], “UC”); // ヘッダ 現在のポジションのレコードから読込
unibuf.exbuf.rc = 0; // フィルター固定部 リジェクトカウント
unibuf.exbuf.fn = 0; // フィルター固定部 フィルターなし
if (rcnt > 300)
unibuf.exbuf.recc = 300; // ディスクリプタ固定部 読込む件数
else
unibuf.exbuf.recc = rcnt; // ディスクリプタ固定部 読込む件数
unibuf.exbuf.fc = 1; // ディスクリプタ固定部 読込むフィールド数
unibuf.exbuf.df = sizeof(unibuf.recbuf.data[0].record); //ディスクリプタ フィールド長
unibuf.exbuf.ds = 0; //ディスクリプタ オフセット
buflenex = sizeof(unibuf.recbuf); //入力より出力データバッファのサイズが大きいので出力サイズをセット
kbuflenex = 255;
//Get Next Extended オペレーションコード:36
stat = BTRVEX(36, posb, &unibuf.exbuf, &buflenex, kbuf, kbuflenex, 0);
ポイントは Get Next Delete Extended と同じくデータバッファ長(buflenex)には BTRVEX() 関数から返ってくる大きいほう(recbuf)のサイズを設定することです。
またこちらは Get Next Delete Extended になかった 「ディスクリプタ-フィールド長(変数df )」と「ディスクリプタ-オフセット(変数 ds)」の設定が必要です。
これは読込んだレコードのどの部分が必要かを指定する項目となります。フィールド長(df)にはテーブルのレコード長(808 byte)を設定しています。
上記の場合、位置0(ds)から808 byte(df) 分の1つ(fc)を取得するという設定となり、読込んだレコードの全部分を取得する設定となります。
Get Next と Get Next Extended の実行速度比較
- 1億件のレコードから10万件読込
Get Next | 5.920秒 |
Get Next Extended | 0.593秒 |
- 1億件のレコードから100万件読込
Get Next | 58秒 |
Get Next Extended | 8秒 |
まとめ
データバッファの設定が少し複雑ですが、Get Next Delete Extendedも Get Next Extended もかなり高速に動作することがわかりました。
マニュアル
質問、疑問等ありましたら、弊社サポートフォームよりご連絡おねがいします。
次のサンプルソースもぜひ参考にしてみてください。
今回のご紹介では単純にキーの昇順でレコードを削除、読込みしていますが、フィルター条件を設定することで、削除する、しないや、取得する、しないを SQL の WHERE 句の条件のように設定することも可能です。
サンプルソース
今回の確認は Visual Studio 2017 C言語で Windows アプリを作成し行いました。
サンプルソースは上記画面の「データフェッチ(API)」ボタンや「データ一括フェッチ」ボタン等に割り当てたソースとなりますのでご了承ください。