OCIでfunctions,nosql,objectstorageを使ったサーバレスアーキテクチャ作成



## 概要
OCIを使用して、大量データをストレージ＆検索できるようなサーバレス環境を構築してみました。


## 構成
![diagram_serverless.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2846649/9a407495-f99b-2c61-ac36-d1fdba5c90cd.png)
こちらが構成図です。

API Gatewayを使用することで簡単に公開WebAPIを作成することができます。
クライアント・アプリケーション側は、ブラウザやwebviewを使用したスマホアプリ等から簡単に使用できます（ノンプラグイン）

APIはサーバ内部でfunctionsをキックします。
今回は以下の3つのfunctionsを作成しました。
- NoSQL Databaseからテーブルデータ取得
- Object Storageからバイナリデータ取得
- Object Storageにデータアップ後に自動でNoSQL Databaseに関連データ投入

## 実装
下記githubにOCI Java SDKを使用したサンプルを載せていますので参考にしてください。
https://github.com/masa-ishikawa/function_serverless_test

今回はeclipseを用いて開発しました。
環境構築として、まず下記2つのリポジトリからmasterをcloneします。
https://github.com/fnproject/fdk-java.git
https://github.com/oracle/oci-java-sdk.git

fdk-javaはapiプロジェクトのみ取り込み、
oci-java-sdkは開発に関連するプロジェクトを取り込みます、以下は私の取り込みプロジェクトです。
取り込み後は開発するfunctionsのプロジェクトに最新のfck,ociプロジェクトを参照させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2846649/fa4170b9-f51a-5243-53b1-2909cea5f3de.png)

## その他メモ
### Functionsのライフサイクルについて

functionsは下記にある通り、一定時間が過ぎるとコールドスタンバイとなり、次回コール時に時間がかかります。
https://docs.oracle.com/ja-jp/iaas/Content/Functions/Concepts/functionshowitworks.htm
> ファンクションの実行が終了し、アイドル状態の期間が経過すると、Dockerコンテナは削除されます。コンテナが削除される前に、Oracle Functionsで同じファンクションに対する別のコールを受信すると、2番目のリクエストは同じ実行中のコンテナにルーティングされます。Oracle Functionsが、実行中のコンテナ内で現在実行中のファンクションに対するコールを受け取ると、Oracle Functionsは水平方向にスケーリングして、両方の着信リクエストを処理し、2番目のDockerコンテナが開始されます。

今回は、作成したwebapiをコンピュートインスタンスからcronで毎分curlすることで、functionsを常に待機状態とし、常に高速な呼び出しを可能にしています。
※OCIはfunctionsコール数に無料枠があります。functionsの料金体系は下記を参照
- https://www.oracle.com/jp/cloud/cloud-native/functions/
- https://qiita.com/liu-wei/items/519abc36c32eb40c461c#2-%E4%BE%A1%E6%A0%BC%E6%AF%94%E8%BC%83



### Functionsのデータ取得について
当初はfunctionsで実行するjavaクラスの戻り値をbyte[]にして直接apigatewayを通してhttpでバイト配列を返していました(zip化はもちろん可能したが..)
```java:バイト生成
		byte[] bytes = new byte[] {};
		try (final InputStream fileStream = getResponse.getInputStream()) {
			bytes = fileStream.readAllBytes(); //javaヒープに全展開！
			log.info("bytes.length=" + bytes.length / 1024d + "kb");
		} // try-with-resources automatically closes fileStream
		client.close();
```
ただその場合、取り扱うデータがkb単位のものであれば問題ないですが、動画等の比較的大きいデータを扱う場合はバイト配列は難しいです：
- functionsとJVMのヒープメモリを増やす必要がまずありますが、
- ヒープをクリアした後でも、以下のエラーとなり、そもそもfunctionsのFunctionInvokeResponseBodyには6MBデータ制約があります
    - "Served function invocation request in 6.597 seconds with error code 502 - FunctionInvokeResponseBodyTooLarge (502): function response body too large "
    - https://docs.oracle.com/ja-jp/iaas/Content/Functions/Tasks/functionstroubleshooting_topic-Issues-invoking-functions.htm#Invoking_a_function_returns_a_FunctionInvokeResponseBodyTooLarge_message_and_a_502_error

このため今回はObjedctStorageの[事前認証リクエスト](https://community.oracle.com/tech/developers/discussion/4474260/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88-%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%BF%E3%81%86-oracle-cloud-infrastructure%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86-%E3%81%9D%E3%81%AE7)を使用しました。
oci sdkを使えば任意のタイミングで生成可能です。
APIコール後にfunctionsにて、オンデマンドで期限を限りなく短くした事前認証リクエストを払い出し、クライアント側に渡します。
そのためfunctionsのメモリは最小の128MBで問題なく動作します。

【おまけ】クライアント側でファイルとしてDLさせたい場合、例えば下記jsで可能です。
```javascript:ファイル強制DL
      fetch(url)
        .then(function (response) {
          if (response.ok)
            return response.blob();
          throw new Error();
        })
        .then(blob => {
          var urlUtil = window.URL || window.webkitURL;
          var imgUrl = urlUtil.createObjectURL(blob);
          var link = document.createElement('a');
          link.href = imgUrl;
          link.download = fileName;
          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link)
        })
        .catch((error) => {
          alert(error);
        });
```

## おわりに
functions+OCI SDKを使用してデータをストレージ＆検索できるサーバレスアーキテクチャを作成しました。
ObjectStorage＋期限極短の事前認証リクエストを使用することで、データ配信サーバ(Functions)を低スペック・低負荷で実現可能です。

ObjectStorageにデータの実体、NoSQLに各ファイルに紐づく属性情報を格納しましたが、
NoSQLの代わりにMySQLやAutonomousを使用したり、またBIツール等を使用してより高度な分析も可能です。