Memorystore キャッシュを使用して Cloud Run の API を高速化する。

## はじめに

この記事は、
　[Speed up your API on Cloud Run with Memorystore caching](https://www.youtube.com/watch?v=BjRAO51omQs)
という動画を視聴し『実際にやってみた』という記事になります。

## この記事でできるようになる事
`Cloud Memorystore` を使って、API実行を高速化させる。

## Cloud Memorystore について簡単に理解しておく

`Cloud Memorystore`は、フルマネージドのインメモリデータストアで、
Googleが管理するスケーラブルで安全な高可用性インフラを提供する。

これを使用する事で、データはRAM上に保存され、
高速なアクセス頻度、レイテンシの低減、スケーラビリティを実現できる。

他にも複雑なタスクの自動化や、アプリの可用性への影響を
最小限に抑えながら、インスタンスを容易に拡張できる特徴がある。

また、データの耐障害性の確保や、テータの定期的な
自動バックアップによるデータの復元も容易である。

サポートしているプロダクトには、`Redis`や`Memcached`があり、
これらの特徴から、`Cloud Memorystore`は、Webアプリケーションや
モバイルアプリ、ゲーム、分析ツールなどの多様な用途に適している。

## Google Cloud API を有効化
GCPプロジェクトのコンソールへ接続。

`Serverless VPC Access API`
`Cloud Build API`
`Artifact Registry API`
`Cloud Run Admin API`

これらのAPIを有効化。
その他、APIの有効化は、必要に応じて、適宜行ってください。

## sample code を git clone する。
[cloud-run-redis](https://github.com/GoogleCloudPlatform/serverless-expeditions/tree/main/cloud-run-redis) を、git clone してください。

```
git clone https://github.com/GoogleCloudPlatform/serverless-expeditions.git
```

git cloneの実行が完了したら、cdコマンドを使って、対象のディレクトリ階層へ移動。
```
cd serverless-expeditions/cloud-run-redis
```

## APIの実行速度が遅い事を確認する。
まずは、`Cloud Memorystore`を使用しないで、
どれだけ速度に違いが出るのかを体感する為に、
既に完成形となっているコードを一部修正します。

なお、`SERVCICE_NAME` は任意の`Cloud Run Service`の名前、
`region`は最寄りのリージョンを選択して、入力してください。

`deploy.sh`
```
GOOGLE_PROJECT_ID=[プロジェクトIDを入力してください。]
SERVICE_NAME=rest-api
#REDIS_HOST=
#REDIS_PORT=
#VPC_CONNECTOR=

gcloud beta run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region asia-northeast1 \
  --allow-unauthenticated \
  # --update-env-vars REDIS_HOST=$REDIS_HOST,REDIS_PORT=$REDIS_PORT
  # --vpc-connector $VPC_CONNECTOR
  --project=$GOOGLE_PROJECT_ID
```
![memorystore-deploy-slow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/0a26ea46-4299-abb2-7701-8c199d8b5eba.png)
※ `deploy.sh` を変更する際の注意事項
今回は、Google Cloud Shell上でvimコマンドを使って
直接修正を行なっていますが、下記のErrorがDeploy時に
コンソールへ出力される可能性があります。
![memorystore-deploy-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/ed96e715-2451-af6c-69ed-c7c135d06563.png)
このErrorが起こるのは、Windows環境での
改行文字 (\r\n) がUnixやLinux環境での
改行文字 (\n) として認識されていないことが原因との事。

これを解決するためには、以下のコマンドを実行してください。
```
sed -i 's/\r$//' deploy.sh
```
`index.js`
こちらのJSファイルは、一部コードの修正が
必要な箇所近くを抜粋しています。
```
/*
const redisClient = require('redis').createClient(
  process.env.REDIS_PORT,
  process.env.REDIS_HOST
);
*/

// Read one artist, by id.
app.get('/artist/:id', async (req, res) => {
  try {
    const id = req.params.id;
    const artist = await getArtistFromDatabase(id);
    if (artist) {
      res.json(artist);
      return;
    }
    else {
      res.status(404).json({error: `Artist ${id} not found`});
    }
  }
  catch(ex) {
    res.status(500).json({error: ex.toString()});
  }
})
```
![memorystore-index-slow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/b59c68df-d9dc-a772-5f7f-b78f40b49ae5.png)
`deploy.sh` と `index.js` それぞれコードの修正が終わったら、
`Cloud Run Service` を Deploy するために下記のコマンドを実行してください。
```
./deploy.sh
```

次に、`Postman`を起動して、Deployされた
`Cloud Run Service`の`URL`を確認して、
以下のコマンドを修正して、GET Request を実行。
```
[Cloud Run Service URL]/artist/1
```
![cloudrun-memorystore.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/14863af7-c358-9d6a-4d7b-6f291dd235cf.png)
時間が、3s以上かかっていれば、速度が遅い状態となります。
これを何度か繰り返して行なって、
すべてのRequestが3sかかる事を確認してください。
![postman-slow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/7363d8ea-e974-2e62-f887-89c429e6108e.png)

## APIの実行速度を速くする。
APIの実行速度が3s以上かかっている事を確認できたら、
次にAPIの実行速度を上げる為に必要な、
`Cloud Memorystore`のDeployを行います。

なお、`Cloud Memorystore`は、VPC内に
できる為、外部からのアクセスを受け付けません。
そこで、`Cloud Run`から`仮想プライベートクラウド`へ
接続できるように、『`サーバレスVPCアクセス`」を設定します。

以下はその必要となる大まかな構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/68c24fd8-af75-b4be-566b-c7e65188fe7c.png)

`Cloud Memorystore`を設定。
1. GCPコンソールからMemorystoreを開いて、
Redisタブを選択した状態で、『インスタンスを作成』を実行。
![memorystore-create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/978ebc8f-dad7-7887-e9dc-d2b0dd4bae97.png)
2. インスタンス名をつけて、最寄りリージョン、
ネットワークを選択、残りフィールドはデフォルトのまま『作成』を実行。
ネットワークは`default`を選択。
![memorystore-create-instanceid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/71f55907-756c-0ef0-228a-19705c6c4078.png)
![memorystore-create-region.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/bdcd1d8e-fdfd-e24e-2599-ed6f95cb86b2.png)
![memorystore-create-nw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/45a4d43b-e52c-0587-d696-53e431206007.png)

`サーバレスVPCアクセス`を設定
1. GCPコンソールからVPCネットワークを開いて、
サーバレスVPCアクセスタブを選択した状態で、『コネクタを作成』を実行。
![serverless-vpc-access-create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/c4db1ca1-8070-3223-4638-62a6f171f34f.png)
2. 任意のコネクタ名をつけて、最寄りリージョン、ネットワークは`default`を選択。
![serverless-vpc-access-info.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/f5af717d-aea5-7415-b085-1366b920abe9.png)
3. サブネットは『`カスタムIP範囲`』を選択して、
推奨値『`10.8.0.0`』を入力して、『`作成`』を実行する。
![serverless-vpc-access-subnet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/68c54ed3-f6d4-4428-6bc7-ac87b4614370.png)

`REDIS_HOST`, `REDIS_PORT` には、Deployされた
`Memorystore`のプライマリエンドポイントを入力してください。
![memorystore-praimary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/7645a3a8-61c2-dce6-2028-a36b0947aafc.png)

`VPC_CONNECTOR` は、Deployされた
サーバレスVPCアクセスの名前を入力してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/96d9b185-4949-2ba1-1d7f-54dd59841f45.png)
コードは上記の内容を適用させたものになります。
`GOOGLE_PROJECT_ID`や`REDIS_HOST`は、
それぞれ環境ごとに異なる為、そのまま利用しないでください。

`deploy.sh`
```
GOOGLE_PROJECT_ID=[プロジェクトIDを入力してください。]
SERVICE_NAME=rest-api
REDIS_HOST=10.44.88.21
REDIS_PORT=6379
VPC_CONNECTOR=my-vpc-connecter

gcloud beta run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region asia-northeast1 \
  --allow-unauthenticated \
  --update-env-vars REDIS_HOST=$REDIS_HOST,REDIS_PORT=$REDIS_PORT \
  --vpc-connector $VPC_CONNECTOR \
  --project=$GOOGLE_PROJECT_ID
```
![memorystore-deploy-fast.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/1f3e3d9f-8221-f163-8e8a-0aebcb1b19f6.png)
`index.js`
こちらのJSファイルは、一部コードの修正が
必要な箇所近くを抜粋しています。
```
const redisClient = require('redis').createClient(
  process.env.REDIS_PORT,
  process.env.REDIS_HOST
);

// Read one artist, by id.
app.get('/artist/:id', async (req, res) => {
  try {
    const id = req.params.id;
    const artist = await getArtistFromCache(id);
    if (artist) {
      res.json(artist);
      return;
    }
    else {
      res.status(404).json({error: `Artist ${id} not found`});
    }
  }
  catch(ex) {
    res.status(500).json({error: ex.toString()});
  }
})
```
![memorystore-index-fast.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/69c4fa1d-1c9f-675a-1b73-7a64ff5589b9.png)

`deploy.sh`と`index.js`の修正が完了したら、
続けて以下のコマンドを実行して、
`Cloud Run Service`の再Deployを行なってください。
```
./deploy.sh
```

再Deployされた`Cloud Run Service`を`Postman`を使って実行。
最初のRequestは、3s以上かかりますが、
2回目以降は速くなっていることを確認できればOK。
![postman-fast-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/3ce918a4-efeb-df0a-8795-3cb77daa21ce.png)
Memorystoreを利用して、キャッシュからRequestされた結果が返されている状態。
![postman-fast-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952807/7c709079-2ef6-8f36-1cab-cf00e1be8df5.png)

## 終わりに
今回の記事は、動画を視聴しまして、
実際に『手を動かしてみた』という内容でまとめました。

`Cloud Memorystore`を利用することで、
一度、Requestされたデータをキャッシュに残すことが、
可能となり、処理の高速化を確認できました。

APIの実行にかかる時間が遅いと
感じていれば、`Cloud Memorystore`を
検討するのも良いかもしれません。

`Cloud Memorystore`がどのようなものか、
試してみたい人は、参考にしていただけると幸いです。

あとで『じっくり読みたい』、『繰り返し読みたい』と
思ってくれましたら、『`ストック`』へ登録、
この記事が読まれている方にとって、
参考になる記事となりましたら、『`いいね`』を
付けていただけますと、励みになりますので、
よろしくお願いします。