100日後にRustをちょっと知ってる人になる: [Day 52]Wasm Workers Server と KVS
Day 52 のテーマ
Day 50 そして Day 51 と Wasm Workers Server の最もシンプルな基本動作をするアプリケーションについて眺めてみました。
とは言え、基本動作の中でもまだふれていない機能が Wasm Workers Server にはあります。標準機能として、キー/バリューのインメモリストアを提供しています。
今日は、その キー/バリューストアについて見てみたいと思います。
キー/バリューストア
キー/バリューストアとは、文字通りキーとそのキーに対する値からなるデータ構造を保管するためのストレージです。ハッシュテーブルとしてよく知られているデータ構造ですよね。Redis, Memcached や MongoDB などが有名ですね。
さて、Wasm Workers Serverで提供しているキー/バリューストアは、Wasm Wokers Server がまだまだ進化中ということもあり有名な NoSQL のようなリッチな機能は搭載していません。そこは今後に期待ですね。
現在のところは、メモリ上にデータを保管し、再起動するたびにデータ内容はクリーンアップされるようになっています。永続化はまだできません。
制約事項としても予め言われていることがあります:
同時リクエストで同じネームスペースに書き込む場合にデータがオーバーライドされるということです。これは、将来的にはデータ統合の仕組みや別のデータ保存の仕組みによる実装で、大量の書き込みに対しても対応していく予定になっています。
しかし、とは言ってもシンプルながらもこのキー/バリューストアがあることによって、異なるワーカー同士からの読み書きをキー/バリューストア介して行うことができるようになります。
キー/バリューストアを用いたワーカー
それでは、キー/バリューストアを用いたワーカーを作成してみようと思います。チュートリアルに即して作るだけなのですけれどね…
Dependencies
先日の超シンプルなレスポンスをかえすだけのアプリケーションの時と同じく以下を Dependency に追加します。
anyhow = "1.0.65"
wasm-workers-rs = { git = "https://github.com/vmware-labs/wasm-workers-server/" }
Reply 関数
受け取ったリクエストに対してレスポンスを返すだけの reply
関数を定義します。
use anyhow::Result;
use wasm_workers_rs::{handler, http::{self, Request, Response}, cache::Cache};
#[handler(cache)]
fn reply(req: Request<String>, cache: &mut Cache) -> Result<Response<String>> {
Ok(http::Response::builder()
.status(200)
.header("x-generated-by", "wasm-workers-server")
.body(String::from("Hello Wasm!").into())?)
}
Day 50 の時と異なるのは次の 1点です。
今回:
#[handler]
fn reply(req: Request<String>)
前回:
#[handler(cache)]
fn reply(req: Request<String>)
ハンドラマクロのパラメータに cache
属性を付けるかどうかです。
Cache
Cache を見てみます。
use std::collections::HashMap;
pub type Cache = HashMap<String, String>;
文字通り、HashMap
データ構造を提供しているものでした。
キー/バリューストアを用いたレスポンス
キー/バリューストアを用いてカウントアップを行います。
まず、hashmap
のキーとして、counter という文字列を使うことにします。
let count = cache.get("counter");
次に、列挙型な Option
型を利用します。Option
型は値が存在しないかもしれないときに使用する列挙型です。
定義は次のようになっています。
enum Option<T> {
None,
Some(T),
}
T
型 (今回の例では String
型) が存在するときには、Some
でラップします。
一方で値が存在しない場合は、None
になります。
let count_num = match count {
Some(count_str) => count_str.parse::<u32>().unwrap_or(0),
None => 0,
};
Some(count_str) => count_str.parse::<u32>().unwrap_or(0)
では、カウント数の文字列を u32
な数値にパースし、unwrap
して値を取り出しています。ここで、デフォルト値としては、0
を与える unwrap_or()
メソッドが使用されています。
取り出した値は、reply
関数が呼ばれるたびにカウントアップしています。
cache.insert("counter".to_string(), (count_num + 1).to_string());
ソースコード全量
use anyhow::Result;
use wasm_workers_rs::{handler, http::{self, Request, Response, response}, cache::Cache};
#[handler(cache)]
fn reply(req: Request<String>, cache: &mut Cache) -> Result<Response<String>> {
let count = cache.get("counter");
let count_num = match count {
Some(count_str) => count_str.parse::<u32>().unwrap_or(0),
None => 0,
};
let response = format!(
"<!DOCTYPE html>
<body>
<h1>キー/バリューストア in Rust</h1>
<p>カウンター: {}</p>
<p>This page was generated by a Wasm modules built from Rust.</p>
</body>",
count_num
);
cache.insert("counter".to_string(), (count_num + 1).to_string());
Ok(http::Response::builder()
.status(200)
.header("x-generated-by", "wasm-workers-server")
.body(String::from("Hello Wasm!").into())?)
}
Wasm のコンパイル
Wasm イメージを以下の cargo
コマンドでコンパイルします。
cargo build --target wasm32-wasi --release
コンパイルが終了すると、target/wasm32-wasi/release/
ディレクトリの配下に Wasm イメージが出力されています。
ls -l target/wasm32-wasi/release/
drwxr-xr-x 5 yanagiharas staff 160 Oct 21 13:02 build/
-rw-r--r-- 1 yanagiharas staff 207 Oct 21 13:02 day_52_wasm-worker-kv.d
-rwxr-xr-x 1 yanagiharas staff 2216591 Oct 21 13:02 day_52_wasm-worker-kv.wasm*
drwxr-xr-x 31 yanagiharas staff 992 Oct 21 13:02 deps/
drwxr-xr-x 2 yanagiharas staff 64 Oct 21 13:02 examples/
drwxr-xr-x 2 yanagiharas staff 64 Oct 21 13:02 incremental/
この、target/wasm32-wasi/release/
ディレクトリの配下に TOML
ファイルを作成します。
target/wasm32-wasi/release/day_52_wasm-worker-kv.toml
name = "day_52_wasm-worker-kv"
version = "1"
[data]
[data.kv]
namespace = "day_52_wasm-worker-kv"
この TOML
ファイル名はハンドラファイル名と同じにしておく必要があるようです。
Wasm Workers Server の起動
生成された Wasm イメージのディレクトリまで移動し、Wasm Workers Server を起動します。
cd target/wasm32-wasi/release
wws .
⚙️ Loading routes from: .
🗺 Detected routes:
- http://127.0.0.1:8080/day_52_wasm-worker-kv
=> day_52_wasm-worker-kv.wasm (handler: day_52_wasm-worker-kv)
- http://127.0.0.1:8080/deps/day_52_wasm_worker_kv-78cdb3ea12732eda
=> deps/day_52_wasm_worker_kv-78cdb3ea12732eda.wasm (handler: default)
🚀 Start serving requests at http://127.0.0.1:8080
検知されたエンドポイント (http://127.0.0.1:8080/day_52_wasm-worker-kv) に対してアクセスを複数回してみます。
curl http://127.0.0.1:8080/target/wasm32-wasi/release/day_52_wasm-worker-kv
<!DOCTYPE html>
<body>
<h1>キー/バリューストア in Rust</h1>
<p>カウンター: 1</p>
<p>This page was generated by a Wasm modules built from Rust.</p>
</body>⏎
<!DOCTYPE html>
<body>
<h1>キー/バリューストア in Rust</h1>
<p>カウンター: 2</p>
<p>This page was generated by a Wasm modules built from Rust.</p>
</body>⏎
<!DOCTYPE html>
<body>
<h1>キー/バリューストア in Rust</h1>
<p>カウンター: 3</p>
<p>This page was generated by a Wasm modules built from Rust.</p>
</body>⏎
:
:
期待通りカウントアップされていることが分かりました。
Day 52 のまとめ
Wasm Workers Server で標準で提供している機能のキー/バリューストアについてサンプルアプリケーションを作りながら見てみました。ユースケースを妄想すると、Wasm Workers Server のようなイベント・ドリブンで動作するサーバーレスタイプのアプリケーションの場合、処理結果を一時的にどこかに保管しておくことが出来ると便利だというのは容易に想像できると思います。
Wasm のアプリケーションプラットフォームでもそれが出来るように、まず最初に対応させて来たのは今後の方向性を少し想像と妄想ができるかなと感じました。
とは言え、まだまだ発展途上な OSS なのでキー/バリューストアの機能に関してもさらなる進化を期待したいかなと思いました。
Discussion