🐿️

Rust x Momento | client-sdk-rust でキャッシュを操作する

2024/03/28に公開

Momento Cache とは

https://jp.gomomento.com/services/momento-cache/

Momento Cacheは、世界初の真のサーバーレスキャッシングサービスです。瞬時の弾力性、scale-to-zero機能、圧倒的な高速パフォーマンスを提供します。 容量の選択、管理、プロビジョニングが必要な時代は終わりました。Momento Cacheでは、SDKを入手し、エンドポイントを取得し、コードに数行入力するだけで、すぐに実行できます。

Momento Cache Documentation | Momento Docs

圧倒的な高速性と使いやすさが売りのサーバーレスのキャッシュサービスです。

client-sdk-rust でキャッシュを操作する

momentohq/client-sdk-rust

Rust の SDK も提供されています🎉

https://github.com/momentohq/client-sdk-rust

Experimental SDK の記載がなくなった!?

README から Experimental SDK の記載がなくなっていました...👀✨

v0.32.0

v0.33.1

Cargo.toml

momento の SDK と非同期ランタイムである tokio を使用します。

[dependencies]
momento = "0.33.1"
tokio = { version = "1.36.0", features = ["full"] }

Momento キャッシュと API キー

Momento コンソール でキャッシュを作成し、環境変数 MOMENTO_CACHE_NAME を設定して実行しています。

また、API キーは Momento コンソール から取得できます。

MOMENTO_CACHE_NAME={Cache name} MOMENTO_AUTH_TOKEN={API key} cargo run

Scalar

単純な文字列をキャッシュさせます。

setget を使用しています。

use momento::response::Get;
use momento::{CredentialProvider, MomentoError, SimpleCacheClientBuilder};
use std::process;
use std::time::Duration;

pub async fn scalar() -> Result<(), MomentoError> {
    // SimpleCacheClient の生成
    let mut cache_client = match SimpleCacheClientBuilder::new(
        CredentialProvider::from_env_var("MOMENTO_AUTH_TOKEN".to_string())?,
        Duration::from_secs(60),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{err}");
            process::exit(1);
        }
    }
    .build();
    let cache_name = std::env::var("MOMENTO_CACHE_NAME").expect("Cache name must be set!");

    // キー・バリュー(文字列)のセット
    let key = "my_key".to_string();
    let value = "my_value".to_string();

    println!("Setting key: {key}, value: {value}");
    match cache_client
        .set(&cache_name, key.clone(), value.clone(), None)
        .await
    {
        Ok(res) => {
            println!("{:?}", res);
        }
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // キー・バリュー(文字列)の取得
    match cache_client.get(&cache_name, key.clone()).await {
        Ok(r) => match r {
            Get::Hit { value } => {
                let v: String = value.try_into().expect("I stored a string!");
                println!("Got value: {v}");
            }
            Get::Miss => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    Ok(())
}

Dictionary

構造体をキャッシュさせることもできます。

Rust では HashMap として扱います。

dictionary_fetch ではキーを指定して取得でき、dictionary_get では構造体のフィールドまで指定することで取得する値を絞り込むことができます。

use momento::response::{DictionaryFetch, DictionaryGet};
use momento::{CollectionTtl, CredentialProvider, MomentoError, SimpleCacheClientBuilder};
use std::collections::HashMap;
use std::process;
use std::time::Duration;

pub async fn dictionary() -> Result<(), MomentoError> {
    // SimpleCacheClient の生成
    let mut cache_client = match SimpleCacheClientBuilder::new(
        CredentialProvider::from_env_var("MOMENTO_AUTH_TOKEN".to_string())?,
        Duration::from_secs(60),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{err}");
            process::exit(1);
        }
    }
    .build();
    let cache_name = std::env::var("MOMENTO_CACHE_NAME").expect("Cache name must be set!");

    // Dictionary のセット
    let key = "my_dict".to_string();
    let dict = HashMap::from_iter([
        ("my_dict_1", "my_dict_value_1"),
        ("my_dict_2", "my_dict_value_2"),
    ]);

    println!("Setting key: {key}, value: {:?}", dict);
    let ttl = CollectionTtl::default();
    match cache_client
        .dictionary_set(&cache_name, key.clone(), dict.clone(), ttl)
        .await
    {
        Ok(res) => {
            println!("{:?}", res);
        }
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // Dictionary の取得
    match cache_client
        .dictionary_fetch(&cache_name, key.clone())
        .await
    {
        Ok(r) => match r {
            DictionaryFetch::Hit { value } => {
                let v: HashMap<String, String> = value.try_into().expect("I stored a dictionary!");
                println!("Got value: {:?}", v);
            }
            DictionaryFetch::Miss => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // Dictionary の取得(キーでの絞り込み)
    match cache_client
        .dictionary_get(&cache_name, key.clone(), vec!["my_dict_1"])
        .await
    {
        Ok(r) => match r {
            DictionaryGet::Hit { value } => {
                let v: HashMap<String, String> = value.try_into().expect("I stored a dictionary!");
                println!("Got value: {:?}", v);
            }
            DictionaryGet::Miss => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    Ok(())
}

List

配列をキャッシュさせることもできます。

use momento::{CollectionTtl, CredentialProvider, MomentoError, SimpleCacheClientBuilder};
use std::process;
use std::time::Duration;

pub async fn list() -> Result<(), MomentoError> {
    // SimpleCacheClient の生成
    let mut cache_client = match SimpleCacheClientBuilder::new(
        CredentialProvider::from_env_var("MOMENTO_AUTH_TOKEN".to_string())?,
        Duration::from_secs(60),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{err}");
            process::exit(1);
        }
    }
    .build();
    let cache_name = std::env::var("MOMENTO_CACHE_NAME").expect("Cache name must be set!");

    // List のセット
    let key = "my_lst".to_string();
    let list = ["my_list_1", "my_list_2", "my_list_3"];

    println!("Setting key: {key}, value: {:?}", list);
    let ttl = CollectionTtl::default();
    match cache_client
        .list_set(&cache_name, key.clone(), list.clone(), ttl)
        .await
    {
        Ok(res) => {
            println!("{:?}", res);
        }
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // List の取得
    match cache_client.list_fetch(&cache_name, key.clone()).await {
        Ok(r) => match r {
            Some(entry) => {
                let values: Vec<String> = entry
                    .value()
                    .iter()
                    .map(|v| String::from_utf8(v.clone()).expect(""))
                    .collect();
                println!("Got value: {:?}", values);
            }
            None => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    Ok(())
}

Set

Set というデータ型をキャッシュさせることができます。

セットとは、一意な要素の並び順のないコレクションのことで、それぞれが文字列形式になっている。

Supported data types | Momento Docs

以下のコードでは、次のように出力されます。

Setting key: my_set, value: ["my_set_1", "my_set_2", "my_set_3", "my_set_3", "my_set_4", "my_set_5", "my_set_6", "my_set_6", "my_set_7", "my_set_8"]
()
Got value: ["my_set_2", "my_set_4", "my_set_6", "my_set_5", "my_set_3", "my_set_8", "my_set_7", "my_set_1"]

my_set_3my_set_6 の重複除去がされていることが分かります。
また、順番の保証がない点にも注意が必要です。

use momento::{CollectionTtl, CredentialProvider, MomentoError, SimpleCacheClientBuilder};
use std::process;
use std::time::Duration;

pub async fn set() -> Result<(), MomentoError> {
    // SimpleCacheClient の生成
    let mut cache_client = match SimpleCacheClientBuilder::new(
        CredentialProvider::from_env_var("MOMENTO_AUTH_TOKEN".to_string())?,
        Duration::from_secs(60),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{err}");
            process::exit(1);
        }
    }
    .build();
    let cache_name = std::env::var("MOMENTO_CACHE_NAME").expect("Cache name must be set!");

    // Set のセット
    let key = "my_set".to_string();
    let sets = vec![
        "my_set_1", "my_set_2", "my_set_3", "my_set_3", "my_set_4", "my_set_5", "my_set_6",
        "my_set_6", "my_set_7", "my_set_8"
    ];

    println!("Setting key: {key}, value: {:?}", sets);
    let ttl = CollectionTtl::default();
    match cache_client
        .set_add_elements(&cache_name, key.clone(), sets.clone(), ttl)
        .await
    {
        Ok(res) => {
            println!("{:?}", res);
        }
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // Set の取得
    match cache_client.set_fetch(&cache_name, key.clone()).await {
        Ok(r) => match r.value {
            Some(hs) => {
                let values: Vec<String> = hs
                    .into_iter()
                    .map(|v| String::from_utf8(v.clone()).expect(""))
                    .collect();
                println!("Got value: {:?}", values);
            }
            None => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    Ok(())
}

Sorted Set

Sorted set というデータ型をキャッシュさせることができます。

Sorted setsは、値(文字列)とスコア(符号付きダブル64ビットフロート)のペアを持つユニークな要素のコレクションです。項目の要素はスコア値順に並べられます。

Supported data types | Momento Docs

Set の順番を保証できるようにしたデータ型です。

sorted_set_fetch_by_index は取得するインデックスの範囲を指定でき、sorted_set_fetch_by_score は取得するスコアの範囲のを指定できます。

以下のコードでは、次のように出力されます。

Got value: ["my_sorted_set_50", "my_sorted_set_100"]
Got value: ["my_sorted_set_100", "my_sorted_set_200"]
use momento::response::SortedSetFetch;
use momento::sorted_set::{Order, SortedSetElement};
use momento::{CollectionTtl, CredentialProvider, MomentoError, SimpleCacheClientBuilder};
use std::process;
use std::time::Duration;

pub async fn sorted_set() -> Result<(), MomentoError> {
    // SimpleCacheClient の生成
    let mut cache_client = match SimpleCacheClientBuilder::new(
        CredentialProvider::from_env_var("MOMENTO_AUTH_TOKEN".to_string())?,
        Duration::from_secs(60),
    ) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("{err}");
            process::exit(1);
        }
    }
    .build();
    let cache_name = std::env::var("MOMENTO_CACHE_NAME").expect("Cache name must be set!");

    // Sorted set のセット
    let key = "my_sorted_set".to_string();
    let sorted_sets = vec![
        SortedSetElement {
            value: "my_sorted_set_200".into(),
            score: 200.0,
        },
        SortedSetElement {
            value: "my_sorted_set_100".into(),
            score: 100.0,
        },
        SortedSetElement {
            value: "my_sorted_set_50".into(),
            score: 50.0,
        },
        SortedSetElement {
            value: "my_sorted_set_1".into(),
            score: 1.0,
        },
    ];

    println!("Setting key: {key}, value: {:?}", sorted_sets);
    let ttl = CollectionTtl::default();
    match cache_client
        .sorted_set_put(&cache_name, key.clone(), sorted_sets.clone(), ttl)
        .await
    {
        Ok(res) => {
            println!("{:?}", res);
        }
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // Sorted set のインデックスが 1 以上 2 以下までのデータを取得
    match cache_client
        .sorted_set_fetch_by_index(&cache_name, key.clone(), Order::Ascending, 1..=2)
        .await
    {
        Ok(r) => match r {
            SortedSetFetch::Hit { elements } => {
                let values: Vec<String> = elements
                    .into_iter()
                    .map(|v| String::from_utf8(v.value).expect(""))
                    .collect();
                println!("Got value: {:?}", values);
            }
            SortedSetFetch::Miss => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    // Sorted set のスコアが 100 以上 200 以下までのデータを取得
    match cache_client
        .sorted_set_fetch_by_score(&cache_name, key.clone(), Order::Ascending, 100.0..=200.0)
        .await
    {
        Ok(r) => match r {
            SortedSetFetch::Hit { elements } => {
                let values: Vec<String> = elements
                    .into_iter()
                    .map(|v| String::from_utf8(v.value).expect(""))
                    .collect();
                println!("Got value: {:?}", values);
            }
            SortedSetFetch::Miss => {
                println!("Cache miss!");
            }
        },
        Err(err) => {
            eprintln!("{err}");
        }
    };

    Ok(())
}

まとめ

気になっているサービスである Momento を Rust で検証してみました。

シンプルで非常に扱いやすいです!!

あと、キャラクターが可愛い🐿️🐿️🐿️

ソースコードを Github で公開しました。

https://github.com/codemountains/momento-rust-sandbox

参考

コラボスタイル Developers

Discussion