Rustと非同期ライブラリーTokioで作成した簡易版Redisを紹介
メモリーセーフなプログラミング言語Rustは、その特性からシステムプログラミングに向いているとされている。Rustの非同期処理の実装はTokioというライブラリーによって行われている。今回は、AWSのイベントRe:InventからAWSのエンジニアによるRustとTokioに関するセッションを紹介する。
動画:AWS re:Invent 2020: Next-gen networking infrastructure with Rust and Tokio
セッションのタイトルは「Next-gen networking infrastructure with Rust and Tokio」というもので、AWSのエンジニアCarl Lerche氏とSean McArthur氏が登壇した。前半のRustに関する概略やメモリーセーフという特徴などの紹介をLerche氏が担当し、後半のキャッシュとして使われるRedisをmini-redisとしてRustで書き直すというチュートリアルは、McArthur氏が担当している。
あらためてRustについて解説
パブリッククラウドのトップであるAWSがRustを有望なプログラミング言語として見なしているのは、このセッションがRe:Inventで行われていることからも理解できる。AWSに限らずMicrosoftも積極的にRustを使っていこうという姿勢を見せているし、GoogleもRust Foundationに創立メンバーとして名前を連ねている。このことから、GoogleもGoに並んでRustを有望なシステムプログラミング言語として意識していることがわかる。ちなみにMicrosoftがRust Foundationに参加したことは、Microsoftの公式ブログでも紹介されている。
参考:Microsoft joins Rust Foundation
Lerche氏は、ネットワーク処理が必要なアプリケーションを開発する際に「信頼性と同時に高速に稼働する性能が必要である」としてJavaやGoなどを開発言語の候補として挙げたが、Rustをホビー用の開発言語として使ってみたところから、Rustを評価するようになったと解説した。
Rustの特徴を紹介するスライドでは、Rustのデータオーナーシップ(所有)にフォーカスして解説を行った。
変数によってデータが所有され、結果としてデータは明確に「誰(変数や関数)がそれを所有しているのか?」が常に確定されることで、メモリーアクセス違反が起こらない構造であることを強調した。
ここではtake_ownershipという関数を定義し、その引数としてfooを使うが、fooが関数内で利用された後は解放されるために、その後同じ関数内でfooを使おうとしても、すでに解放されているために使えないというエラーを、コンパイラーが返すということを解説している。関数が実行され、そのスコープが終了したと同時に変数は解放される仕様であるため、「データを誰がいつまで所有しているのか?」をRustのコンパイラーが厳密にチェックしているという例だ。
Lerche氏は、Tokioのコントリビューターの中では最も多くのコミットを行っているエンジニアである。元々はRustでデータベースを作ろうとしたが、Rustの機能には非同期処理を行うライブラリー//ランタイムがなかったため、いわゆるYak Shaving(「ヤクの毛を刈る」という言い回しはある目的を達成するために、その他の多くのことを先にやる必要がある状態を指す)として非同期通信機能であるTokioを開発したことを解説した。
この例ではループの中でプロセスがSpawnされ、非同期にテキストを書き出す処理を行っている。ソケットそのものを閉じる処理がなくても、関数の終了時にRustがデータを解放することでソケットを閉じる処理が行われることを示している。
TokioはTCP、UDP、UNIX Socketなど非同期通信を行うための必要な機能を提供していることを紹介。
さらにasync/awaitの解説として、hello_worldという関数のソースコードを使って解説。このシンプルなコードがTokioのライブラリーではどのような記述になるのか? を解説したのが次の2枚のスライドだ。
Tokioのランタイムの構造について解説したスライドでは、複数のWorkerがタスクを非同期に処理することを紹介。先ほどの例で言えば、SpawnされたタスクがこのWorkerの中で処理されているということになるのだろう。
またデータベース処理に必要なデータをタスク間で共有する処理についても、Spawnされたタスクからデータをmoveする処理ではRustのコンパイラーがエラーを返すという例を使って解説。ここでもRustが持つ厳密なデータ所有権の機能が効果を発揮していると言える。
Rustのエコシステムの中ではCargoというビルドツールが非常に使いやすいことは知られているが、Rustのコンパイラーもデベロッパーフレンドリーであることは特記すべきポイントだろう。
そしてこの部分のまとめとしてRustは安全で高速、コンパイル時にメモリー関連のチェックが行われるという特性を解説して、後半であるmini-redisを作るというパートを担当するMcArthur氏に交替した。
Redisの簡易版mini-redisをRustで作る
後半を担当するMcArthur氏も、Tokioのコントリビューターとして活動しているAWSのエンジニアだ。ここでは非同期処理の例としてRedisを挙げて、そのコアの機能をRustとTokioで書くとどういうコードになるのか? を検証し、それを教育として使うためのプロジェクトとしてmini-redisを開発したという。
参考:tokio-rs/mini-redis: Incomplete Redis client and server implementation using Tokio
最初に、インメモリーのキーバリューストアデータベースであるRedisの特性を解説した。キャッシュとして使われることの多いRedisだが、C言語で書かれていること、多くのプロセスから非同期並列に接続され、データの書き込み、検索、読み込みなどの機能を満たしていることを説明した。
その上で今回のプロジェクト、mini-redisの概要を紹介した。mini-redisはRedisを置き換えるものではなく、あくまでもRustによる非同期通信をデベロッパーに理解してもらうための教育が目的であると解説した。
実際のコードは、上記のGitHubのリポジトリーを参照してほしい。前半のLerche氏によるデータを複数のプロセスで共有するために必要な処理について、クライアントからのコネクションを処理するサーバー側のコードを使って紹介した。
データベースを構造体として定義する際にMutexを追加して排他制御の実装が必要であることを解説。他にも、Arcというスマートポインターを使ってデータを共有する仕組みを使っている。
RustにおけるArcについては以下のQiitaの記事が参考になるだろう。
参考:Rustの `Arc` を読む(1): Arc/Rcの基本
次のスライドでは標準のMutexについて解説を行っている。
ここからインメモリーデータベースへのデータ書き込み、読み込みのソースコードを解説し、Rustであれば簡潔に記述できることを説明した。
最後にRedisとmini-redisでSET、GETのベンチマークを実施し、C言語で書かれたRedisとほぼ同様の性能を出していることを解説した。ここではメモリーセーフでありながら高速に実行できるRustとTokioの組み合わせが、安全性と高速性を両立したソフトウェアを開発できるという例として強調された形になった。
McArthur氏は、RustとTokioの組み合わせによって安全で高速なソフトウェアが実現できることを再度紹介して、セッションを終えた。GitHubのmini-redisのリポジトリーのソースコードを見れば、多くのコメント行によって初めてコードを見るデベロッパーへの教育という目的を目指していることがわかる。
mini-redisのようなコードがあれば、RustとTokioによるリアルなアプリケーション開発に初めて取り組むエンジニアにとっても参考になるだろう。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Rustで書かれたシステムのトレーシングを実装するtracing
- RustConfからTwitterがキャッシュサーバーをRustで書き直したセッションを紹介
- GoogleがAndroidの開発にRustを使った事例を解説するRust Dayを開催
- RustConfで見えてきたRustプロジェクトが最も大事にする価値とは?
- 高速でメモリーセーフなプログラミング言語、Rustの特徴を紹介
- DropboxがコアサービスをRustで書き換えた背景とは
- ISRGが推進するメモリーセーフなソフトウェアを増やすための地道なプログラムProssimoを紹介
- RustとGraphQLの連携で高速/シンプルなプログラミングを実現するJuniperとは
- RustNLからマルチプラットフォームのアプリ開発のためのツールRobiusのセッションを紹介
- サービスメッシュのLinkerdの最新リリースと将来計画をBuoyantのCEOが解説