IBM API Connect Mapポリシーパフォーマンス

# はじめに

IBM API Connectでは、APIの動作をアセンブルで定義します。
そのアセンブリーで利用可能なビルトインの動作ポリシーの一つがMapポリシーです。
Mapポリシーは、XMLもしくはJSON形式のデータを編集するポリシーです。

本記事では、Mapポリシーは使い方によって、パフォーマンスがよくないケースがあり、それをご紹介します。

なお、本記事で作成したAPI定義は、[ここ](https://github.com/yo24/APIC-Map)からダウンロード可能です。

# 前提条件

検証には、2019/11 時点のIBM Cloud API Connectサービスを利用します。
IBM CloudのAPI Connectは v5ベースですが、ver2018でも本記事の考え方は適用するはずです(経験者談)。

# ケース1. 処理対象の項目数が多いケース

配列の要素数が多いなど、処理対象の項目数が多いケースは、若干パフォーマンスに悪化が見られます。

1万個の要素をもつ配列を含むJSONを処理するMapポリシーのパフォーマンスを計測してみます。

処理対象のJSONは以下の通り。

```json:request.json
{
  "itemStr": "def",
  "itemInt": 99,
  "itemArray": [
    {
      "arrayStr": "abc",
      "arrayInt": 12
    },
    {
      "arrayStr": "abc",
      "arrayInt": 13
    },
    # 1万要素続く
  ]
}
```

Mapポリシーのaction部分は以下の通り。

単純な項目移送に加え、配列の要素毎分の固定値を返すような処理になっています。

```yaml: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になります。

```json:activity_log-latency-info抜粋
[
  {""task"":""Initialization"",""ended"":0}, 
  {""task"":""Start"",""ended"":11}, 
  {""task"":""prep"",""ended"":16},
  {""task"":""map-foreach-with-value"",""ended"":120}
]

```

1万要素に１００msを要するのは、妥当なのかは判断が難しいところです。
比較として、GatewayScriptポリシーでMapポリシーと同じ処理を実装し、処理時間を計測してみます。

GatewayScriptポリシーのコード部分は以下の通り。

```javascript: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なので、かなり開きがあることがわかります。

```json:activity_log-latency-info抜粋(GatewayScript版)
[
  {""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)でリクエストデータを参照し、若干加工する設定にしています。

```yaml: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=$(1); strVal;  #ここを変更
      - set: resArrayInt
        from: arrayInt
        value: var intVal=$(1) + 100; intVal; #ここを変更

```

平均値にもっとも近い処理時間のアクティビティーログのサンプルは以下の通りです。
Mapポリシーの処理時間は、979ms。
ケース1の処理時間は約100ms。
変数参照するだけで、顕著にパフォーマンスが悪化しました。

```json:activity_log-latency-info抜粋
[
 {""task"":""Initialization"",""ended"":0}, 
 {""task"":""Start"",""ended"":11}, 
 {""task"":""prep"",""ended"":37}, 
 {""task"":""map"",""ended"":1016}
]
```

ケース１と同じく、GatewayScriptポリシーでMapポリシーと同じ処理を実装し、処理時間を計測してみます。
まず、GatewayScriptの実装内容は以下の通りです。

```javascript: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。
ケース１の処理時間は、15ms。
こちらもパフォーマンスは悪化しましたが、Mapポリシーよりはましといったところ。

```json:activity_log-latency-info抜粋（GatewayScript版）
[
  {""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」を設定せよと記載があります([マニュアルリンク](https://www.ibm.com/support/knowledgecenter/en/SSFS6T/com.ibm.apic.toolkit.doc/configuration_props.html))。

が、ケース2のパターンに設定を追加すると、なぜかMapポリシーの処理時間が約2倍になるという結果に。。。　うーん、謎。


# まとめ

Mapポリシーのパフォーマンスについて、2点取り上げました。
Mapポリシーを利用シーンとしては、入力値を処理に利用することが多いと思われるので、ケース2が多いと思われます。

本記事と同じようなケースで、Mapポリシーのパフォーマンスに悩まれている方は、GatewayScriptポリシーへの移行を考えてみては如何でしょうか？

参考ですが、API Connect v5->v2018への移行メモが公開されており、その中でアセンブルの[ベストプラクティス](https://github.com/ibm-apiconnect/apigw/wiki/APIGW-Porting-Notes#apigw-assembly-coding-best-practices)が掲載されています。
上記記事にも、シンプルなMap処理は、GatewayScriptでの実装を検討するように記載さています。