Rust | redis-rs で Amazon ElastiCache for Redis の Hash・JSON を操作する
Redis とは
Redis (Remote dictionary server) とは、メモリ上でデータを管理するインメモリデータベースの一つで、オープンソースソフトウェアとして公開されています。
保存する値(value)として様々なデータ構造を利用することができ、文字列、バイナリデータ、リスト、集合(セット)、ハッシュなどを保存することができます。
Amazon ElastiCache for Redis
今回は Amazon ElastiCache を使用します。
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"
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 を扱う際は以下のドキュメントをご確認ください。
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 がよしなに扱ってくれるので、特に難しいと感じるところはありませんでした。
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 の参考文献はまだまだ少ないですね🔥
Discussion