TL;DR
この記事では、Rustで書かれた高性能メッセージストリーミングプラットフォーム「Apache Iggy」の起動プロセスとGraceful Shutdownの仕組みをコードから読み解きます。具体的には、core/server/src/main.rsのコードを中心に、利用されている主なライブラリやGraceful Shutdownの実装方法について解説します。
対象読者
- Rustでシステムプログラミングを学んでいる開発者
- 高性能なメッセージストリーミング基盤に興味がある開発者
- Graceful Shutdownの実装方法を知りたい開発者
紹介すること/紹介しないこと
紹介すること
- Apache Iggy ServerのShutdownプロセスの詳細な解説
- 利用されている主なライブラリとその説明
紹介しないこと
- Apache Iggyの使い方やAPIの説明
- Apache Iggyの内部アーキテクチャ全般の解説
用語の整理
まず、本記事で頻出する用語を整理します。
| 用語 | 説明 | 関連資料 |
|---|---|---|
| Thread-per-core | 各スレッドが専用のCPUコアに割り当てられ、他のスレッドとリソースを共有しないアーキテクチャ。 | Thread-per-core |
| Simultaneous Multithreading (SMT) | CPU辺り複数のスレッドを実行する技術で、Iggyでは採用されていない。 | Simultaneous multithreading - Wikipedia |
| Shared-Nothing Architecture | 各コンポーネントが独立しており、共有リソースを持たないアーキテクチャ | Shared-nothing architecture - Wikipedia |
| Shared-disk Architecture | 複数のノードが同じディスクストレージを共有するアーキテクチャ | Shared-disk architecture - Wikipedia |
| Shared-memory Architecture | 複数のプロセスが同じメモリ空間を共有するアーキテクチャ | Shared-memory architecture - Wikipedia |
| io_uring | Linuxカーネルのシステムコールインターフェースの一つで、非同期I/O操作を行う | io_uring - Wikipedia |
| Proactor Pattern | 長期間実行される非同期アクティビティが終了した後、完了ハンドラを呼び出すイベント処理の設計パターン | Proactor pattern - Wikipedia |
| Reactor Pattern | 単一スレッドのイベントループで多数のサービスリクエストを同時に処理するため、入ってくるリクエストを多重化して適切なハンドラに対応をさせるイベント処理の設計パターン | Reactor pattern - Wikipedia |
| compio | OSが提供するI/O技術を利用した非同期ランタイム。Proactorパターンに基づく設計をしている。 | compio |
| Entity Component System (ECS) | Iggyにおいて各Partitionにシャードを割り当てる際に利用されている設計パターン。コード上ではEntityComponentSystemが利用されている。 |
Entity component system - Wikipedia |
対象プロダクトの紹介
Apache Iggyは、Rustで書かれた永続化メッセージストリーミングプラットフォームです。QUIC,WebSocket,TCP,HTTPの複数のトランスポートプロトコルをサポートしています。
主な特徴:
- スレッドごとのコア、シェアードナッシングアーキテクチャ(Thread-per-core, Shared-Nothing Architecture)
- io_uringとcompioによる低レベルI/O
- ゼロから構築された独自の実装(既存インフラの拡張ではない)
今回は、このIggy Serverがどのように終了するのか、その詳細なメカニズムをRustのコードから読み解いていきます。
対象プロダクトのコードを読んで見る
今回は、core/server/src/main.rsのコードを中心に解説します。
まず、利用しているライブラリとその詳細を説明したあとに、コードの流れを順に追っていきます。
利用している主なライブラリとその詳細
| メソッドやマクロ | 説明 | 関連資料 |
|---|---|---|
#[instrument] |
関数の実行開始/終了を自動的にログ出力するマクロ | instrument in tracing - Rust, tracing|実用Rustアプリケーション開発 |
compio::runtime::Runtime::new() |
compioランタイムの新しいインスタンスを作成する | Runtime in compio::runtime - Rust |
let rt = match ... |
Rustの標準的なエラーハンドリングパターン | Recoverable Errors with Result - The Rust Programming Language |
std::io::ErrorKind |
標準ライブラリのI/Oエラーの種類を表す列挙型 | ErrorKind in std::io - Rust |
rt.block_on(async { ... }) |
非同期関数を同期的に実行するためのcompioランタイムのメソッド | Runtime in compio::runtime - Rust |
option_env |
コンパイル時に設定された環境変数を取得するマクロ | option_env in std - Rust |
std::env::var |
実行時の環境変数を取得する関数 | var in std::env - Rust |
if let Ok(...) = ... |
Rustのパターンマッチングを用いた条件分岐 | All the Places Patterns Can Be Used - The Rust Programming Language |
::parse() |
#[derive(Parser)]で自動生成されたコマンドライン引数パーサー |
clap - Rust |
.await |
非同期関数の完了を待つための構文 | Async and await - Asynchronous Programming in Rust |
with_error |
err_tailクレートで提供されるエラーハンドリングのためのトレイト拡張 |
err_tail - crate.io |
info!(...) |
tracingクレートで提供される情報ログ出力マクロ |
info in tracing - Rust, Macros - The Rust Programming Language |
Vec::with_capacity(...) |
指定した容量で空のベクタを作成する関数 | Vec in std::vec - Rust |
Arc::new(...) |
スレッドセーフな参照カウント型スマートポインタを作成する関数 | Arc in std::sync::arc - Rust |
AtomicU64 |
アトミック操作が可能な64ビット符号なし整数型 | AtomicU64 in std::sync::atomic - Rust |
mpsc::channel::<...>() |
マルチプロデューサ・シングルコンシューマチャネルを作成する関数。Senderはimpl Cloneしている。 |
channel in std::sync::mpsc - Rust |
Box::new(...) |
ヒープ上にデータを確保するスマートポインタを作成する関数 | Box in std::boxed - Rust |
Box::leak(...) |
ヒープ上のデータを意図的にリークさせ、'staticライフタイムにする関数 |
leak in std::boxed - Rust |
.into() |
Intoトレイトを利用した型変換のためのメソッド |
Into in std::convert - Rust |
std::thread::Builder::new() |
スレッドを作成するためのビルダーパターンの構造体 | Builder in std::thread - Rust |
spawn(move // { ... }) |
新しいスレッドを起動(スポーン)するメソッド。moveクロージャで所有権を移動させる。 |
spawn in std::thread - Rust |
drop(...) |
明示的に変数をスコープ外に出してドロップ(解放)する関数 | drop in std::mem - Rust |
ctrlc::set_handler(...) |
Ctrl-Cシグナルをキャッチして指定したハンドラを実行する関数 | set_handler in ctrlc - Rust |
recv() |
チャネルからメッセージを受信するメソッド。ブロッキング動作をする。 | recv in std::sync::mpsc - Rust |
core/server/src/main.rsのコードの流れを読む
今回は、Graceful Shutdownの実装を中心に、Iggy Serverの起動プロセスを解説します。
まず、「SIGTERMとSIGINTのシグナルをキャッチしてGraceful Shutdownを実装する」部分のコードを見ていきます。
その中で、initiate_shutdownという関数が呼ばれ、各シャードにシャットダウンシグナルを送信しています。
また、それぞれのIggyShardは次のように生成され、runメソッドで起動していて、最後にサーバーストップの処理についてチャネルで待機し、trigger_shutdownが呼ばれています。
-
IggyShardBuilder::buildメソッド:https://github.com/apache/iggy/blob/a74b713e9200227d8601e438201824a7a6df54f1/core/server/src/shard/builder.rs#L119-L182 -
IggyShard::runメソッド: https://github.com/apache/iggy/blob/a74b713e9200227d8601e438201824a7a6df54f1/core/server/src/shard/mod.rs#L167-L196
加えて、その際にconnectionをshardの数だけ作成していて、それをIggyShardに渡しています。
-
connectionについて -
connectionの生成詳細:https://github.com/apache/iggy/blob/a74b713e9200227d8601e438201824a7a6df54f1/core/server/src/bootstrap.rs#L252-L268
さて、話を戻して trigger_shutdown メソッドの中身を見てみましょう。
こちらを見ると、さらにtask_registryのgraceful_shutdownメソッドが呼ばれていることがわかります。
-
graceful_shutdownメソッド:https://github.com/apache/iggy/blob/a74b713e9200227d8601e438201824a7a6df54f1/core/server/src/shard/task_registry/registry.rs#L249-L309
つまり、流れとしては以下のようになっており、協調的にシャットダウンが行われていることがわかります。
- シグナルをキャッチして
initiate_shutdownが呼ばれる - 各シャードにシャットダウンシグナルを送信
- 各シャードの
runメソッドで待機しているチャネルが受信 -
trigger_shutdownが呼ばれ、task_registry.graceful_shutdownが実行される
iggy/LICENSE at a74b713e9200227d8601e438201824a7a6df54f1 · apache/iggy
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
まとめ
今回は、Rustで書かれた高性能メッセージストリーミングプラットフォーム「Apache Iggy」の起動プロセスとGraceful Shutdownの仕組みをコードから読み解きました。
感想
- task_registryという概念が出てきましたが、どのような概念かいまいち理解できませんでした。おそらくShard内で動作するタスクを管理するための仕組みだと思いますが、もう少し深掘りして理解したいです。
- compioランタイムを使った非同期I/Oの実装が興味深かったです。今回は簡単なメソッドしか触れられていませんが、今後さらに調査してみたいと思います。
- Rust性のPlatformの実装を読むのは初めてでしたが、所有権や型システムを活用した設計が非常に興味深かったです。ただ、コード量が多く全体像をどのように把握するのがベストかわからなかったです。今回はGraceful Shutdownに絞って読みましたが、それだと少し断片的な理解に留まってしまった印象があるため、別のところでOSSを読む際には全体像を把握する方法を模索してから挑戦したいと思います。