[Rust] 高速キャッシュサービス Momentoを使ってみる

2022.07.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Introduction

Momentoは、クラウドネイティブな
高速のサーバーレスキャッシュサービスです。
キャッシュサーバの設定や準備は短時間(数分)で完了し、
キャッシュ最適化・スケール・管理も自動。
セキュリティもデフォルトでしっかりしています。(E2E暗号化や監査ログサポートなど)

公式サイトでは「世界最速」を謳っていますが、実際はどんな感じでしょうか。
今回はRustを使ってMomentoにアクセスしてみます。

Environment

  • OS : MacOS 12.4
  • rust : 1.61.0

M1 Macで動かしました。
AWSアカウントはセットアップ済みとします。

Setup

Momentoのセットアップは公式Github以前の記事などを参考におこなってください。
ここにあるように、
momento account signupで指定したメールアドレス宛にトークンが送付されているはずです。
このトークンはMomento CLIセットアップや
このあとプログラムからアクセスするために必要なので、確認しておきましょう。

momentoコマンドを実行してversionが表示できればOKです。

% momento --version
momento 0.20.1

そしてRustのインストール。

% curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

RustとCargoのコマンドが使えることを確認。

% rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.61.0 (fe5b13d68 2022-05-18)`

% cargo --version
cargo 1.61.0 (a028ae42f 2022-04-29)

Lambdaのビルド&デプロイを簡単にするため、Cargo Lambdaをインストールします。

% cargo install cargo-lambda

Introduce Momento Library

各種Momentoライブラリの使い方はシンプルです。
ここを参考に、
RustでMomentoライブラリを使う方法をみてみましょう。

Momentoライブラリは、
Cargo.tomlに下記依存ライブラリを追記するだけでOKです。

[dependencies]
momento = "0.6.0"

ちなみにここにあるサンプルの場合、
tokioも追加する必要があります。

RustでMomentoにアクセス

クライアントを作成するためにSimpleCacheClientBuilderを使います。
tokenとttlを指定してクライアントを作り、
create_cacheでキャッシュを作成します。

let auth_token = "your token";
let item_default_ttl_seconds = 300;
let mut cache_client = SimpleCacheClientBuilder::new(
    auth_token,
    NonZeroU64::new(item_default_ttl_seconds).unwrap(),
)
.unwrap()
.build();

let cache_name = String::from("your-cache-name");
match cache_client.create_cache(&cache_name).await {
    Ok(_) => {
                println!("create_cache OK!");
        }
    Err(err) => {
        eprintln!("{}", err);
    }
}

Rust用Momentoライブラリでは、キャッシュ関連操作はすべて非同期なので、
create_cache時にawaitを使います。
ちなみに、キャッシュを削除したいときは
cache_client.delete_cacheとすればOK。

作成したキャッシュに対してデータの保存/取得/削除をするのも簡単です。
クライアントの各種関数に、さきほど作ったキャッシュの参照と
データのkeyやvalueを渡します。

//キャッシュデータの保存
let key = String::from("my_key");
let value = String::from("my_value");
cache_client
    .set(&cache_name, key.clone(), value.clone(), None)
    .await
    .unwrap();

//キャッシュデータの取得
cache_client
    .get(&cache_name, key.clone())
    .await
    .unwrap();

//キャッシュデータの削除
cache_client
    .delete(&cache_name, key.clone())
    .await
    .unwrap();

getは正常に処理が行われた場合、MomentoGetResponseを返します。
MomentoGetResponseはresult(キャッシュヒットしたか判定するenum)と
value(実際の値の&[u8])をもっています。
なので、例えば文字列をsetして、キャッシュからgetして使いたい場合は↓のようにします。

let result:MomentoGetResponse = cache_client.get(&cache_name, key.clone()).await.unwrap();
println!("{:?}",String::from_utf8(result.value).unwrap());

Create AWS Lambda on Rust

次は、AWS Lambda on RustでMomentoをつかってみましょう。
Lambdaでも、ローカルで動かすのと特に違いはありません。

まずはCargo Lambdaでプロジェクトの作成。
簡単に実行できるように、HTTP functionで作る。

% cargo lambda new momento-lambda
 Is this function an HTTP function? <yes>

momento-lambda/Cargo.tomlに依存ライブラリを追加。

[dependencies]

 ・・・

quanta = "0.10.0"
momento = "0.6.0"

momento-lambda/src/main.rsにMomento関連の処理を追記します。
キャッシュをset/get/deleteして処理時間を計測し、それらをログに出力します。

use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Response};
use quanta::Clock;
use momento::response::cache_get_response::MomentoGetStatus;
use momento::simple_cache_client::SimpleCacheClientBuilder;

use std::num::NonZeroU64;
use std::process;

async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
    let clock = Clock::new();
    const N:u32 = 1_000_000;
    let start = clock.now();
    let mut stop = start;
    let auth_token = "<your Momento Token>".to_string();
    let item_default_ttl_seconds = 60;
    let mut cache_client = match SimpleCacheClientBuilder::new(
        auth_token,
        NonZeroU64::new(item_default_ttl_seconds).unwrap(),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{}", err);
            panic!("create cache_client error")
        }
    }
    .build();

    // Creating a cache named "cache"
    let cache_name = String::from("cache");
    match cache_client.create_cache(&cache_name).await {
        Ok(_) => {
            println!("create_cache OK!")
        }
        Err(err) => {
            eprintln!("{}", err);
            panic!("create_cache error")
        }
    }

    // cache key
    let key = String::from("my_key");
    // cache value
    let value = String::from("my_value");
    println!("Setting key: {}, value: {}", key, value);
    match cache_client
        .set(&cache_name, key.clone(), value.clone(), None)
        .await
    {
        Ok(_) => {}
        Err(err) => {
            eprintln!("{}", err);
        }
    };

    //get cache value
    match cache_client.get(&cache_name, key.clone()).await {
        Ok(r) => match r.result {
            MomentoGetStatus::HIT => println!("cache hit!"),
            MomentoGetStatus::MISS => println!("cache miss"),
            _ => println!("error occurred"),
        },
        Err(err) => {
            eprintln!("{}", err);
        }
    };

    // delete cache
    match cache_client.delete_cache(&cache_name).await {
        Ok(_) => {
            println!("Permanently deleted cache named, {}", cache_name);
        }
        Err(err) => {
            eprintln!("{}", err);
        }
    };

    //Execution Time Measurement
    stop = clock.now();
    println!("quanta::clock::now() overhead = {:?}",stop.duration_since(start));

    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body("Hello AWS Lambda HTTP request")
        .map_err(Box::new)?;
    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}

ファイルが修正できたらビルド&デプロイ。  

% cd momento-lambda
% cargo lambda build
% cargo lambda deploy --enable-function-url momento-lambda --iam-role <iamロールのArn> 
? function arn: arn:aws:lambda:XXXXXXXXXXXXXXXXXXX
function url: https://somethingurl.lambda-url.us-east-1.on.aws/

Lambda実行後にログをみてみると、Momentoにアクセスできているのがわかります。

Summary

今回はサーバレスキャッシュサービスMomentoをRustから使ってみました。
セットアップも使い方も簡単ですし、Rust以外にも
JavaScript、Python、Java、Go、C#にライブラリが用意されています。
基本的な使い方は同じなので、お好みの環境で試してみてください。

References