Pleasanter の怖い話！

# Pleasanter で起こる怖い話！

怖い話といえば、夏ですが、冬の怖い話も良いもの…  
ということで、冬ですが、Pleasanter の怖い話をします！

## 経緯

成熟した Pleasanter のパフォーマンス調査や改善は、膨大な時間を要し、とても苦労します。  
本記事では、特に個人的に怖いと感じた話を共有します！

Pleasanter は高性能で多機能なので、パフォーマンスに考慮した設計をしないと、後々の実装追加や設定変更などでパフォーマンス問題になりかねません。
私はスクリプトに起因したパフォーマンス問題に直面したことがありますが、入り組んだ実装を紐解くのに膨大な時間と労力を要し、リファクタリングと調査を実施しました。
仮説と検証を繰り返し、最終的には改善できましたが、設計の段階でパフォーマンスを考慮することで少なくともこの工数は少なくできるのではと考えています。

そのような経緯から、私自身への備忘録を含めて、Pleasanter でローコード開発をするエンジニアの役に立てばと思い、本記事を残します。

## 対象

下記読者を想定しています。

* Pleasanter を最近触り始めたエンジニア
* Pleasanter を使ったローコード開発で注意すべき点を知りたいエンジニア
* Pleasanter のスクリプト/サーバスクリプトの違いがよくわからない方
* Pleasanter のスクリプト/サーバスクリプトの違いを実感したい方

## 目的

Pleasanter入門者・初心者向けに「パフォーマンスの観点から Pleasanter のスクリプト/サーバスクリプトの違い」をご紹介します！

## 初めに

普段、私はSIer(システムインテグレーター)としてお客様環境の要件・仕様の検討や開発、テスト、運用保守等様々な仕事をこなしています！

今回の記事では、私の苦い思いをもとに、「パフォーマンスの観点から「スクリプト/サーバスクリプトの違い」を理解し、Pleasanter を使う立場として、未然にパフォーマンス問題を考慮した設計を目指していこう」というのが本記事のテーマです。

## スクリプト/サーバスクリプトの違いとは・・・？

結論から記載すると、処理の実行タイミングが違います。

### スクリプトって・・・？

スクリプトとは、「クライアント側」で処理される JavaScript のことです。
クライアントで API を実装することで「 API の実行ごとにサーバとの通信が発生します。」

### サーバスクリプトって・・・？

サーバスクリプトとは、「サーバ側」で処理される JavaScript のことです。
サーバで API を実装することで「サーバ内で API が実行され、サーバとの通信は発生しません。」

### つまり・・・？

どちらを実装したほうが良いのか悩んだときは「クライアント/サーバのどちらで API を実行したいか」で考えてください。

## 違いを意識した一段上の設計

Pleasanter でローコード開発に着手する場合、API を利用することが多いです(APIについては「参考」の「開発者向け機能：API」をご参照ください)。
そして、API を活用する場合、処理速度の観点からサーバスクリプトを利用したほうが良いと考えています。
なぜなら、下記の違いがあるからです(「参考」の「セキュリティの観点から見るスクリプト/サーバスクリプトの違い」もご参照ください)。

**スクリプト　　　：API 実行ごとにサーバとの通信が発生するため、サーバとの通信が API 実行回数に比例する**  
**サーバスクリプト：API 実行ごとにサーバとの通信が発生しないため、サーバとの通信が API 実行回数に比例しない**  

### スクリプトで API を実行した場合

下記キャプチャの通り、API を実行するたびにクライアントとサーバとの通信が発生します。
これにより、API 実行ごとにサーバとの通信が発生するため、パフォーマンス低下の懸念があります。

[スクリプトで API を実行した場合]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659914/f276c844-a89b-68b4-5412-410d5503ac5e.png)

### サーバスクリプトで API を実行した場合

下記キャプチャの通り、API を実行してもサーバ側で処理が完結します。
これにより、API 実行ごとにサーバとの通信が発生しないため、スクリプトよりもパフォーマンスの向上が見込めます。

[サーバスクリプトで API を実行した場合]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659914/c79638ad-6eb6-1ce4-7d6f-7b110a5cdaae.png)

## 検証

新しく構築したローカルの Pleasanter 上に下記キャプチャの構成でサイトを構築し、各参照元テーブルから「3回全件データ取得」APIを呼び出しました。
**※ データ比較結果をわかりやすくするために、3回レコード取得を実行しています(回数に何らかの意図はありません)。**  

[検証環境の構成]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659914/25a2ecbd-02bb-61fe-dd22-36500923b990.png)

### 検証方法

下記それぞれサンプルスクリプトにタイマーを仕掛け、スクリプト単体の処理時間を調べました。

### 参照元(スクリプト)

こちらのテーブルには下記サンプルスクリプトを「テーブルの管理画面 > スクリプトタブ」から設定しています。

```JavaScript
try {
    console.log('開始');
    // 「サイト名」からサイト情報を取得
    $p.apiGetClosestSiteId({
        id: $p.siteId(),
        async: false,
        data: {
            // 「サイト名：参照先」
            FindSiteNames: ['参照先'],
        },
        done: function (data) {
            // 参照先のサイトIDを設定
            const targetSiteId = data.SiteId;
            // 一覧画面からチェックを付けたレコードのIDを設定
            const selectingRecordIds = $p.selectedIds();
            // 選択したレコード数分反復処理
            for (const selectedRecordId of selectingRecordIds) {
                // 参照先のレコード情報を取得(API実行)：1回目
                $p.apiGet({
                    id: targetSiteId,
                    async: false,
                    done: function () {
                        // 参照先のレコード情報を取得(API実行)：2回目
                        $p.apiGet({
                            id: targetSiteId,
                            async: false,
                            done: function () {
                                // 参照先のレコード情報を取得(API実行)：3回目
                                $p.apiGet({
                                    id: targetSiteId,
                                    async: false,
                                });
                            },
                        });
                    },
                });
            }
        },
    });
} catch (ex) {
    console.log(ex.stack);
} finally {
    console.log('終了');
}
```

#### 結果

上記サンプルスクリプトを実行した結果、処理時間としては「45489(ms)」でした。

### 参照元(サーバスクリプト)

こちらのテーブルには下記サンプルスクリプトを「テーブルの管理画面 > サーバスクリプトタブ」から設定しています。

```JavaScript
try {
    context.Log('開始');
    // 「サイト名：参照先」のサイトIDを設定
    const targetSiteId = items.GetClosestSite('参照先').SiteId;
    // 選択したレコード数分反復処理
    for (const selectedRecordId of grid.SelectedIds()) {
        // 参照先のレコード情報を取得(API実行)：1回目
        items.Get(targetSiteId);
        // 参照先のレコード情報を取得(API実行)：2回目
        items.Get(targetSiteId);
        // 参照先のレコード情報を取得(API実行)：3回目
        items.Get(targetSiteId);
    }
} catch (ex) {
    context.Log(ex.stack);
} finally {
    context.Log('終了');
}
```

#### 結果

上記サンプルスクリプトを実行した結果、処理時間としては「22591(ms)」でした。

### まとめ

どちらのスクリプトも得られる結果は同じですが、サーバスクリプトのほうが処理速度が速いということがわかりました。
そのため、同じ条件で API が実行される場合、スクリプトよりもサーバスクリプトで実装したほうがパフォーマンス低下のリスクを未然に下げることができます。
したがって、

**設計段階では、可能な限りサーバスクリプトを実装する前提で設計したほうが良いと考えています。**  

もちろん、要件次第でスクリプトのほうが良い場合もあります。

### 備考

今回の検証で処理時間以外にも様々なデータを取得したため、下記にまとめます。

**スクリプト(単位：ms)**  
|ResponseSize|Elapsed|画面描画処理|クライアント+サーバ処理時間|
|:----|:----|:----|:----|
|13713038|39191|49833|89024|

**サーバスクリプト(単位：ms)**  
|ResponseSize|Elapsed|画面描画処理|クライアント+サーバ処理時間|
|:----|:----|:----|:----|
|13712927|58592|204|58796|

**各項目について**  

|項目|内容|
|:----|:----|
|ResponseSize|サーバから返ってきたデータ量|
|Elapsed|サーバでの処理時間|
|画面描画処理|クライアントでの処理時間|
|クライアント+サーバ処理時間|「Elapsed」+「画面描画処理」|


## 参考

・セキュリティの観点から見るスクリプト/サーバスクリプトの違い

https://qiita.com/teppei12345/items/9a5c1ff6685b24082856#%E5%86%85%E9%83%A8%E7%94%A8

・開発者向け機能：API

https://pleasanter.org/ja/manual/api