momentoを利用してサーバレスキャッシングしてみた

## はじめに
最近インメモリキャッシュサービスを格安で利用することができないかと色々調べていました。
(Elasticacheなどのサービスは高いのでなるべく無料のものを利用したい。)

その結果、Momentoというサービスを発見しましたので、今回はそれについて触ってみてLambdaへの実装までを実施しました。


## Momentoとは ? 
インメモリキャッシュのSaaSサービスです。正確には[Momento Cache](https://jp.gomomento.com/services/momento-cache/)にてインメモリキャッシュを行うことができます。
AWS上でキャッシュを利用するとなると、ElastiCacheやAppSync(正確にはElasticache)などを利用す

毎月5GBまでのデータの登録は無料で利用することができるため、。
その後は1GBあたり0.50ドル課金されます。
スタンダードプラン以降はCloudWatch メトリクスへエクスポートすることも可能になります。

## Momento設定内容
今回はMomento cache を作成して

キャッシュを作成するには[Momento Cache](https://console.gomomento.com/caches)画面にて「キャッシュを作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/872532/66be154e-052c-8794-abce-f38aa3894437.png)

以下のようにキャッシュ名とクラウドプロバイダー、リージョンを選択すればこれで完了です。
かなり簡単に作成することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/872532/02beaf89-abd4-c7b7-670c-a5dc688dfb30.png)

あとは、キャッシュを利用するための　APIキーの発行です。[API コンソール](https://console.gomomento.com/api-keys)を作成して
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/872532/7e72745b-74fc-483f-91e0-4d1ed98b076e.png)からプロバイダーと権限を作成します。

以上で、momentoでの設定は完了になります。
超シンプルにキャッシュを作成することができました。

## AWS Lambda + Momento でサーバレスキャッシングやってみた
次にAWS Lambda から Momento Cacheへ接続してみたいと思います。

複数の関数でmomentoパッケージを利用したいので今回はmomentoパッケージをLayer化して、
Lambda関数で利用したいと思います。

今回使用するLambdaランタイム環境はPython3.10とします。

- AWS CDK 

### 1. Lambda Layer Package
Pypiから[Momento Python Client Library](https://pypi.org/project/momento/)をインストールします。

以前投稿したように「invalid ELF header」によるELF headerの不一致によるエラーが発生するので、Lambdaの公式ランタイムのイメージ上でmomentoをインストールするようにします。

https://qiita.com/atw0_0w/items/2362dced7e8987210698

具体的な手順は[こちら](https://qiita.com/atw0_0w/items/2362dced7e8987210698#%E6%89%8B%E9%A0%86)を参考にお願いします。

### 2. Lambda デプロイ
今回はCDKを使用してLambdaとLambdaLayerを作成してデプロイしようと思います。使用言語はTypeScriptで記述していこうと思います。

CDKの使い方については[こちら](https://cdkworkshop.com/ja/)を参考に。

CDKアプリケーションのエントリポイントは以下の通りです。./lib下のCDKアプリケーションのメインスタックを呼び出します。今回はLambdaとLambdaLayerのスタックを作成します。
```typescript:./bin/demo-momento.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LambdaFuncStack } from '../lib/lambda-func.ts';
import { LambdaLayerStack } from '../lib/lambda-layer.ts';


const app = new cdk.App();

new LambdaLayerStack(app, 'LambdaLayerStack', {});
new LambdaFuncStack(app, 'LambdaFuncStack', {});
```


Lambda Layerを作成するCDKアプリケーションを作成します。
./lib下にて
layer.zipの部分は、「1. Lambda Package」で作成したmomentoのパッケージファイルの相対pathを指定します。
```typescript:./lib/lambda-layer.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';

import * as path from 'path';


export class LambdaLayerStack extends cdk.Stack {
    public readonly layer: lambda.LayerVersion;
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);

        const commonLayerName = `demo-layer-momento`;
        const commonLayer = new lambda.LayerVersion(this, commonLayerName, {
            layerVersionName: commonLayerName,
            code: lambda.AssetCode.fromAsset(path.join(__dirname, '../src/lambda/layers/layer.zip')), // momento パッケージを圧縮したファイルを指定
            compatibleRuntimes: [lambda.Runtime.PYTHON_3_10],

        });

        // rtn
        this.layer = commonLayer;

    }
}
```

続いてLambdaFunctionを作成するCDKになります。
```typescript:./lib/lambda-func.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';

import * as path from 'path';

export class LambdaFuncStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);

        const region = this.region;
        const accountId = this.account;

        const funcName = 'demo-func-hello-world';
        const layerArn = `arn:aws:lambda:${region}:${accountId}:layer:demo-layer-momento:1`;

        const momentoCacheName = 'test-api-cache'

        const func = new lambda.Function(this, funcName, {
            functionName: funcName,
            runtime: lambda.Runtime.PYTHON_3_10,
            code: lambda.Code.fromAsset(path.join(__dirname, '../src/lambda/hello_world')),
            handler: 'lambda_function.lambda_handler',
            environment: {
                MOMENTO_API_KEY: 'xXxXxX',
                MOMENTO_CACHE_NAME: momentoCacheName,
                APISPORTS_API_KEY: 'xXxXxX'
            },
            timeout: cdk.Duration.seconds(120),
        });

        func.addLayers(lambda.LayerVersion.fromLayerVersionArn(this, `LambdaLayer`, layerArn));

    }
}
```

次にLambdaのソースファイルとなるlambda_function.pyを作成したいと思います。
今回はシンプルにMomentoクライアント認証したのち、キャッシュにキーと値をMomento Cacheへセットするだけのシンプルなものになります。

```python:./src/lambda/hello_world/lambda_function.py
import json
import os
from datetime import timedelta
import boto3 
import logging
from momento import CacheClient, Configurations, CredentialProvider
from momento.responses import CacheGet, CacheSet, CreateCache, ListCaches, CacheIncrement

MOMENTO_API_KEY = os.getenv('MOMENTO_API_KEY', 'xXxXxXxXxXxXxXxXxX')
MOMENTO_CACHE_NAME = os.getenv('MOMENTO_CACHE_NAME', 'test-cache-name')


logger = logging.getLogger()
logger.setLevel("INFO")

def lambda_handler(event, context):
    client = create_cache_client()
    client.set(MOMENTO_CACHE_NAME, "test-key", "test-value")
    res = client.get(MOMENTO_CACHE_NAME, "test-key")
    logger.info("Result: {}".format(res))
    return {
        'statusCode': 200,
        'body': json.dumps(res)
    }


def create_cache_client():
    cache_client = CacheClient(
        configuration=Configurations.Laptop.v1(),
        credential_provider=CredentialProvider.from_environment_variable("MOMENTO_API_KEY"),
        default_ttl=timedelta(seconds=600),
    )
    return cache_client
```


最後にデプロイコマンドを実行します。
以下のコマンドを実行して、CDKデプロイを実施します。
```bash
cdk synth

cdk deploy
```

最後にLambdaのテストをします。
以下のようにキャッシュのキーに関連した値を取得することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/872532/c8a52d82-942d-64be-e4d8-0f2d2044a770.png)



## 終わりに
今回は、Momentoと呼ばれるSaaSのサービスについて触ってみました。
ほかにも有用なSaaSサービスがあったらそれにも触ってみたいと思います。