Rust | redis-rs で Amazon ElastiCache for Redis の Hash・JSON を操作する

2024/01/31に公開

Redis とは

Redis (Remote dictionary server) とは、メモリ上でデータを管理するインメモリデータベースの一つで、オープンソースソフトウェアとして公開されています。

保存する値(value)として様々なデータ構造を利用することができ、文字列、バイナリデータ、リスト、集合(セット)、ハッシュなどを保存することができます。

https://redis.io/

Amazon ElastiCache for Redis

今回は Amazon ElastiCache を使用します。

https://aws.amazon.com/jp/elasticache/redis/

Amazon ElastiCache for Redis は、ミリ秒未満のレイテンシーを実現する非常に高速なインメモリデータストアで、インターネット規模のリアルタイムアプリケーションを強化できます。Redis 用 ElastiCache は、オープンソース Redis 上に構築されており、Redis API との互換性を備えています。また、Redis クライアントとの連携が可能で、データの保存にはオープン Redis のデータ形式を使用します。

Redis API と互換性があるので、クレートは redis-rs を使用していきます。

構造体を扱いたい

JSON 形式の構造体を扱いたいというユースケースを想定して、
以下の Redis データ型 を扱うサンプルコードを作成してみます。

  • Hash
  • JSON

Hash

Hash は文字列型のフィールドと値のマップです。

CLI で以下のように操作することができます。

> HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972
(integer) 4
> HGET bike:1 model
"Deimos"
> HGET bike:1 price
"4972"
> HGETALL bike:1
1) "model"
2) "Deimos"
3) "brand"
4) "Ergonom"
5) "type"
6) "Enduro bikes"
7) "price"
8) "4972"

Rust のサンプルコード

[dependencies]
redis = { version = "0.24.0", features = ["tls","cluster", "json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
main.rs
use redis::Commands;

fn main() -> redis::RedisResult<()> {
    // Redis クライアントの作成
    let client = redis::Client::open("redis-connection-url")?;
    let mut con = client.get_connection()?;

    con.hset("user:1", "id", "1")?;
    con.hset("user:1", "name", "Ziggy")?;
    con.hset("user:1", "age", "29")?;

    let sections: Vec<String> = con.hgetall("user:1")?;

    // HGETALL はフィールドと値の組み合わせを field1, value1, field2, value2, ..., fieldN, valueNの形で返す
    let mut is_key = true;
    for (i, s) in sections.iter().enumerate() {
        if is_key {
            println!("{}: {}", s, sections.get(i + 1).unwrap());
            is_key = false;
        } else {
            is_key = true;
        }
    }

    // フィールド指定で値を取り出す
    let name: String = con.hget("user:1", "name")?;
    println!("{:?}", name);

    Ok(())
}

JSON

Serde を利用することで、JSON を簡単に扱うことができます。

Redis で JSON を扱う際は以下のドキュメントをご確認ください。

https://redis.io/docs/data-types/json/

Rust のサンプルコード

[dependencies]
redis = { version = "0.24.0", features = ["tls","cluster", "json"] }
redis-macros = "0.2.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"

#derive には以下を追加しています。

  • FromRedisValue
  • ToRedisArgs

Serde がよしなに扱ってくれるので、特に難しいと感じるところはありませんでした。

main.rs
use redis::JsonCommands;
use redis_macros::{FromRedisValue, Json as RedisJson, ToRedisArgs};
use serde::{Deserialize, Serialize};

fn main() -> redis::RedisResult<()> {
    // Redis クライアントの作成
    let client = redis::Client::open("redis-connection-url")?;
    let mut con = client.get_connection()?;

    let user = User {
        id: 1,
        name: "Ziggy".to_string(),
        age: 29,
        addresses: vec![
            Address::Street("Downing".to_string()),
            Address::Road("Abbey".to_string()),
        ],
    };

    // JSON を Redis に保存
    con.json_set("my_json_key", "$", &user)?;

    let mut user: User = con.json_get("my_json_key", "$")?;
    println!("{:?}", user);

    // 更新
    let new_age = user.age + 1;
    con.json_set("my_json_key", "$.age", &new_age)?;
    let user: User = con.json_get("my_json_key", "$")?;
    println!("{:?}", user);

    // 新しい User
    let new_user = user.happy_birthday();
    con.json_set("my_json_key", "$", &new_user)?;

    let user: User = con.json_get("my_json_key", "$")?;
    println!("{:?}", user);

    Ok(())
}

#[derive(Debug, Clone, Serialize, Deserialize)]
enum Address {
    Street(String),
    Road(String),
}

// Derive the necessary traits
#[derive(Debug, Clone, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
struct User {
    id: u32,
    name: String,
    age: i32,
    addresses: Vec<Address>,
}

impl User {
    fn happy_birthday(&self) -> Self {
        let new_age = self.age + 1;

        Self {
            id: self.id,
            name: self.name.to_string(),
            age: new_age,
            addresses: self.addresses.clone(),
        }
    }
}

まとめ

Hash は取得時の結果にクセがありますね。。。
(少し扱いづらい🙄)

やはり、見慣れた JSON が使いやすいような気がしました。
Serde の存在が大きいですね〜!!

また、Rust の参考文献はまだまだ少ないですね🔥

参考

コラボスタイル Developers

Discussion