TableauをWebに埋め込む技術 続

この記事はちゅらデータアドベントカレンダーの24日目の記事になります。

思い出すこと１年前、私はTableauをWebに埋め込む技術という記事を公開しました。
https://qiita.com/thanyu/items/3561c1e4e634b1e74697

実際私はこの技術をとある案件の中で使用していたのですが、１画面上に40枚程度のグラフを埋め込んでいるという制約上、描画速度が非常に遅く実運用は耐えられないようなものとなってしまっていました。

そのため今年は大量の Tableauを Webに組み込んで、なおかつパフォーマンス改善を図るという対応を行ったため、その過程で得られた知見についてご紹介いたします。

あまりにも限られた方向けの記事となってしまいますが、Tableauを Webに組み込む場合には様々な制約があるんだなとお思っていただければ幸いです。

## そもそもなぜ遅いのか
我々の環境では、Tableau Serverと Webサーバーをそれぞれ AWS EC2上で起動し、Tableau Serverで用意した Tableauグラフを Web側に組み込むというような構成としていました。そして Tableauはまた別に用意した Snowflakeのデータベースを参照するようにしていました。
そして組み込んだ Tableauに対して、Web側から Tableau Javascript APIを用いてフィルターを適用することで、ユーザーが見たいデータを可視化するというような仕組みを開発していました。
とくに描画に時間がかかってしまっていたのが、Webに大量のグラフを組み込み、さらに読み込んだグラフに対して追加でフィルターを適用するような処理でした。

１画面に大量のグラフを描画している場合、このフィルターの適用こそが描画速度の遅くなってしまう原因となってしまいます。

Webに組み込んだ Tableauに対して Tableau Javascript APIを用いて、フィルターを適用する場合、「グラフ数 × 適用するフィルター数」分のリクエストを Tableau Serverに送付する必要があります。
我々が開発していたツールでは、最大15項目程度のフィルターをすべてのグラフ（40個程度）に対して適用する必要があったため、一度の画面描画処理に必要なリクエスト数が「（グラフ数 40個） * （フィルター数 15個） = 600 リクエスト」となり、そのような大量のリクエストの処理をさばくのに Tableau Serverが耐えられないために起こってしまっていた事象となります。

## 解決策
上記の大量のリクエストが発生してしまうために生じる課題に対して、GETリクエストのパラメータにフィルター条件を書くことで上記の課題の解決を試みました。
本来の Tableau Javascript APIのチュートリアルで紹介されているようなフィルターは POSTリクエストとなり、異なるフィルター項目ごとに１件１件送信されているのですが、GETパラメータにフィルター条件を含めることにより複数のフィルターリクエストを同時に送信することができるようになります。
これによりリクエスト数の削減ができるようになります。

この機能は Tableau Javascript APIでは以下リンクの「Apply Filters Before Loading the Visualization」で紹介されています。
https://help.tableau.com/current/api/js_api/en-us/JavaScriptAPI/js_api_concepts_filtering.htm

以下の例では、"Container"というカラムに対して、"Boxes"という値でフィルターを適用するという命令です。

```js
var containerDiv = document.getElementById("vizContainer"),
url = "http://YOUR-SERVER/views/YOUR-VIEW",
options = {
    "Container": "Boxes"
};

viz = new tableau.Viz(containerDiv, url, options);
```

ただし、十分なドキュメントが整備されているとは言い難く、実装過程で躓いてしまった点がいくつかありましたので、紹介したいと思います。



### URLが長すぎると404エラーとなってしまう
非常に多くのフィルタを適用してしまうと、その適用したフィルタはすべて GETリクエストのパラメータとなるため、発行される　URL長が長くなってしまいます。
Tableau Serverが受け取る URL長の制限は特にないはずなのですが、5000文字程度を超えるようなURLが発行されてしまった場合には404エラーが帰ってきてしまうことがあります。
それだけの長さの URLが発行されるケースというのは稀だとは思うのですが、多くのフィルターをURLに含めている場合で404エラーが帰ってくるような事象が発生した場合には URL長を疑ってみることをおすすめします。

### 「,」が含まれているような値は、フィルターの値に組み込むことができない
上の例で実際に発行されるURLは以下のドキュメントに準拠しています。
https://help.tableau.com/current/pro/desktop/en-gb/embed_structure.htm

一つのカラムに対して複数値でフィルターする場合には、以下のようなURLが発行されます。

```
http://<servername>/#/views/<workbook>/sheet?param1=value1,value2
```
こちらの仕様と競合してしまうため、値に「,」が入ってしまっている場合は正しくフィルターの適用が行うことができなくなります。

### セカンダリデータソースのフィルターはURLに埋め込むことができない

セカンダリデータソースに対するフィルターは、URLに埋め込んで実装することができないため、下記のような applyFilterAsync文を書いてPOSTリクエストで対応する必要があります。

```js
workSheet.applyFilterAsync('[Data Source].[field name]', value, tableau.FilterUpdateType.REPLACE);
```
https://community.tableau.com/s/question/0D54T00000C1SEPSA3/applying-a-filter-on-a-secondary-data-source-from-javascript-api

### 存在しないフィルターを適用しようとしてもエラーは発生しない
URL にフィルターを埋め込むため、たとえ存在しないフィルター項目に対するフィルターを行ってしまった場合でもエラーを検知することができません。
存在しないフィルター項目をURLに埋め込んでも、特にエラーとならずそのフィルター項目は無視されてしまいます。

### （非推奨）パラメータ埋め込み機能
しかし公式のサポート外ではありますが、以下の optionsの中に 定義したTableau Parameterの名称と値を埋め込む事が一応可能となります。
以下のようにすることで可能となります。

```js
var containerDiv = document.getElementById("vizContainer"),
url = "http://YOUR-SERVER/views/YOUR-VIEW",
options = {
    "TableauParameterYear": "2020"
};
viz = new tableau.Viz(containerDiv, url, options);
```
ただし、こちらの機能はTableau社に直接問い合わせを行ったところ、公式サポートは行っていないとの回答でしたので使用しないのが無難な機能となります。

## 終わりに
いかがでしたでしょうか。

非常に限られた方向けの記事となってしまいましたが、もしお役に立てれば幸いです。