はじめに
IBM API Connectでは、APIの動作をアセンブルで定義します。
そのアセンブリーで利用可能なビルトインの動作ポリシーの一つがMapポリシーです。
Mapポリシーは、XMLもしくはJSON形式のデータを編集するポリシーです。
本記事では、Mapポリシーは使い方によって、パフォーマンスがよくないケースがあり、それをご紹介します。
なお、本記事で作成したAPI定義は、ここからダウンロード可能です。
前提条件
検証には、2019/11 時点のIBM Cloud API Connectサービスを利用します。
IBM CloudのAPI Connectは v5ベースですが、ver2018でも本記事の考え方は適用するはずです(経験者談)。
ケース1. 処理対象の項目数が多いケース
配列の要素数が多いなど、処理対象の項目数が多いケースは、若干パフォーマンスに悪化が見られます。
1万個の要素をもつ配列を含むJSONを処理するMapポリシーのパフォーマンスを計測してみます。
処理対象のJSONは以下の通り。
{
"itemStr": "def",
"itemInt": 99,
"itemArray": [
{
"arrayStr": "abc",
"arrayInt": 12
},
{
"arrayStr": "abc",
"arrayInt": 13
},
# 1万要素続く
]
}
Mapポリシーのaction部分は以下の通り。
単純な項目移送に加え、配列の要素毎分の固定値を返すような処理になっています。
actions:
- set: res.resStr
from: req.itemStr
- set: res.resInt
from: req.itemInt
- set: res.resFixed
default: $(api.name)
- create: res.resArray
foreach: req.itemArray
from: req.itemArray
actions:
- set: resArrayStr
from: arrayStr
value: 'var strVal="aaaaa"; strVal;'
- set: resArrayInt
from: arrayInt
value: var intVal=1+100; intVal;
ポリシー毎の処理時間は、アクティビティーログのlatency_infoで参照できます。
アクティビティーログのサンプルは以下の通りです(100回試行した平均値にもっとも近い値を掲載します)。
各行の末尾の数値が、各ポリシーの処理終了時の経過時間です(単位はミリ秒)。
Mapポリシーの処理時間は、120-16=104msになります。
[
{""task"":""Initialization"",""ended"":0},
{""task"":""Start"",""ended"":11},
{""task"":""prep"",""ended"":16},
{""task"":""map-foreach-with-value"",""ended"":120}
]
1万要素に100msを要するのは、妥当なのかは判断が難しいところです。
比較として、GatewayScriptポリシーでMapポリシーと同じ処理を実装し、処理時間を計測してみます。
GatewayScriptポリシーのコード部分は以下の通り。
// get request values
var req = apim.getvariable('message.body');
// add array element
var res = {};
res['resStr'] = req['reqStr'];
res['resStr'] = req['reqInt'];
res['resFixed'] = '$(api.name)';
// create output itemarray
var resArray = [];
for (const duumy of req['itemArray']) {
let strVal="aaaaa";
let intVal=1+100;
var map = {};
map['arrayStr'] = strVal;
map['arrayInt'] = intVal;
resArray.push(map);
}
res['resArray'] = resArray;
apim.setvariable('message.body', res);
平均値にもっとも近い処理時間のアクティビティーログのサンプルは以下の通りです。
GatewayScriptの処理時間は、15ms。
Mapポリシーの処理時間が約100msなので、かなり開きがあることがわかります。
[
{""task"":""Initialization"",""ended"":0},
{""task"":""Start"",""ended"":11},
{""task"":""prep"",""ended"":17},
{""task"":""gatewayscript"",""ended"":32}
]
ケース2. 変数参照回数が多いケース
Mapポリシーでは、$(変数名)の形式で、APIプロパティーやAPICのコンテキスト変数の値を参照することができます。
この変数参照のパフォーマンスがよくありません。
ケース1と同じJSONを処理するMapポリシーで処理時間を計測してみます。
Mapポリシーの設定は、ケース1から若干変更しており、$(1)でリクエストデータを参照し、若干加工する設定にしています。
actions:
- set: res.resStr
from: req.itemStr
- set: res.resInt
from: req.itemInt
- set: res.resFixed
default: $(api.name)
- create: res.resArray
foreach: req.itemArray
from: req.itemArray
actions:
- set: resArrayStr
from: arrayStr
value: var strVal=$(1); strVal; #ここを変更
- set: resArrayInt
from: arrayInt
value: var intVal=$(1) + 100; intVal; #ここを変更
平均値にもっとも近い処理時間のアクティビティーログのサンプルは以下の通りです。
Mapポリシーの処理時間は、979ms。
ケース1の処理時間は約100ms。
変数参照するだけで、顕著にパフォーマンスが悪化しました。
[
{""task"":""Initialization"",""ended"":0},
{""task"":""Start"",""ended"":11},
{""task"":""prep"",""ended"":37},
{""task"":""map"",""ended"":1016}
]
ケース1と同じく、GatewayScriptポリシーでMapポリシーと同じ処理を実装し、処理時間を計測してみます。
まず、GatewayScriptの実装内容は以下の通りです。
// get request values
var req = apim.getvariable('message.body');
// add array element
var res = {};
res['resStr'] = req['reqStr'];
res['resStr'] = req['reqInt'];
res['resFixed'] = '$(api.name))';
// create output itemarray
var resArray = [];
for (const reqArrayItem of req['itemArray']) {
let strVal= reqArrayItem['arrayStr'];
let intVal= reqArrayItem['arrayInt'] + 100;
var map = {};
map['resArrayStr'] = strVal;
map['resArrayInt'] = intVal;
resArray.push(map);
}
res['resArray'] = resArray;
apim.setvariable('message.body', res);
次に、平均値にもっとも近い処理時間のアクティビティーログのサンプルです。
GatewayScriptポリシーの処理時間は、103ms。
ケース1の処理時間は、15ms。
こちらもパフォーマンスは悪化しましたが、Mapポリシーよりはましといったところ。
[
{""task"":""Initialization"",""ended"":0},
{""task"":""Start"",""ended"":11},
{""task"":""prep"",""ended"":32},
{""task"":""gatewayscript"",""ended"":135}
]
Mapポリシーにおける変数参照パフォーマンスの改善策?
APICのマニュアルでは、Mapポリシーの変数参照パフォーマンス改善用の設定として、APIプロパティーに「x-ibm-gateway-map-resolve-apic-variables=false」を設定せよと記載があります(マニュアルリンク)。
が、ケース2のパターンに設定を追加すると、なぜかMapポリシーの処理時間が約2倍になるという結果に。。。 うーん、謎。
まとめ
Mapポリシーのパフォーマンスについて、2点取り上げました。
Mapポリシーを利用シーンとしては、入力値を処理に利用することが多いと思われるので、ケース2が多いと思われます。
本記事と同じようなケースで、Mapポリシーのパフォーマンスに悩まれている方は、GatewayScriptポリシーへの移行を考えてみては如何でしょうか?
参考ですが、API Connect v5->v2018への移行メモが公開されており、その中でアセンブルのベストプラクティスが掲載されています。
上記記事にも、シンプルなMap処理は、GatewayScriptでの実装を検討するように記載さています。