なぜ私はRustとAxumに「オールイン」したのか?
Rustのサービスをプロダクション環境で6ヶ月間運用してみて、確信したことがあります。それは、Rustは巷で言われているほど「習得が難しい」わけではないということです。実際に最も苦労するのは、メモリ管理の考え方(所有権:Ownership)に慣れることだけでした。以前は、開発スピードを重視してNode.jsやGoをAPI開発に選んでいました。しかし、数百万件のレコード処理や10ms以下のレイテンシが求められる課題に直面したとき、Rustは真の「怪物的なパフォーマンス」を発揮しました。
Rustのエコシステムの中で、Axumは最もバランスの取れた選択肢として浮上しています。Tokioの開発チームによって作られたAxumは、Rustの型システム(Type System)を最大限に活用しています。これにより、コンパイルが通った時点でAPIの安全性は極めて高くなります。コードがコンパイラをパスすれば、ランタイムエラーに遭遇することは稀です。
私がAxumを最も気に入っている理由は、towerエコシステムとの完璧な互換性です。ExpressやGinのミドルウェアの書き方に慣れている人なら、Axumは非常に親しみやすく感じるでしょう。しかし、その安全性とパフォーマンスは全く別次元のレベルにあります。
プロジェクトの初期化:標準的な構成を忘れずに
まず、rustupを通じてRustがインストールされていることを確認してください。以下のコマンド一行でプロジェクトを初期化します。
cargo new rust-axum-api && cd rust-axum-api
以下は、実際のプロジェクトで私が常に信頼して使用している「骨格」となる依存関係です。Cargo.tomlファイルに追記してください。
[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["runtime-tokio-native-tls", "postgres", "macros"] }
dotenvy = "0.15"
tower-http = { version = "0.5", features = ["trace", "cors"] }
tracing = "0.1"
tracing-subscriber = "0.3"
私は重量級のORMではなく、sqlxを選択しています。理由は単純で、ビルド時にSQLの構文エラーをチェックできるからです。カラム名を書き間違えた場合、コンパイラが即座にエラーを出してくれるため、ステージング環境でのデバッグ時間を大幅に節約できます。
実践的な設定:データベースからルーティングまで
1. ボトルネックのないデータベース接続
コード内に接続文字列をハードコードするのではなく、.envファイルを使用しましょう。これにより、開発環境と本番環境の切り替えが容易になります。
DATABASE_URL=postgres://user:password@localhost:5432/my_db
main関数の中で、コネクションプールを設定します。私の経験上、サーバーの構成に基づいてmax_connectionsを設定するのがベストです。例えば、2 vCPUのVPSであれば、Postgresへの過負荷を避けるために5〜10程度が適切です。
#[tokio::main]
async fn main() {
// .envファイルを読み込む
dotenvy::dotenv().ok();
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URLが設定されていません");
// データベース接続プールの設定
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&db_url)
.await
.expect("データベースの接続に失敗しました");
}
2. インテリジェントなExtractorによるハンドラーの実装
Axumには非常に強力な「Extractor」という仕組みがあります。JSONボディやクエリ文字列からデータを取得したいですか?関数の引数にデータ型を宣言するだけです。正確なJSON構成を準備するために、RustのStructにマッピングする前にJSON Formatterを使って構造をチェックすることをよく行います。
#[derive(Serialize, Deserialize)]
struct User {
id: i32,
username: String,
}
async fn get_users(State(pool): State<sqlx::PgPool>) -> Json<Vec<User>> {
// ユーザー一覧を取得するクエリ
let users = sqlx::query_as!(User, "SELECT id, username FROM users")
.fetch_all(&pool)
.await
.unwrap_or_default();
Json(users)
}
プロダクションへのデプロイ:シングルバイナリの威力
よくある間違いは、ソースコードを丸ごとサーバーにコピーしてcargo runを実行することです。それはやめましょう!Rustの最大の利点は、約15〜20MBの単一のバイナリファイルにコンパイルできることです。リリースビルドを作成するだけで済みます。
cargo build --release
その後、target/release/rust-axum-apiにあるファイルをLinuxサーバーにコピーします。サービスを24時間365日安定して稼働させるには、systemdを使用しましょう。以下は、アプリがクラッシュした場合に自動再起動させるための設定ファイルの例です。
[Service]
ExecStart=/var/www/app/rust-axum-api
Restart=always
Environment=DATABASE_URL=postgres://user:password@localhost/db
User=www-data
驚異的な数値:パフォーマンスとRAM
実際に運用してみると、その数値に驚くはずです。基本的なAxum APIのアイドル時のRAM消費量はわずか12〜15MB程度です。対照的に、同等のJavaやSpring Bootアプリケーションは通常、少なくとも200〜300MBを消費します。処理速度については、低スペックのVPSでwrkを使用して負荷テストを行った際、CPUが非常に安定した状態で毎秒45,000リクエストを達成しました。
ログを確認するには、journalctl -u myapp -fコマンドを使用するだけです。tracingクレートからのすべての情報が明確に表示され、本番環境でのエラー調査が悪夢ではなくなります。
Rustへの移行は、初期段階では学習コストがかかるかもしれません。しかし、システムの安心感とサーバーコストの徹底的な最適化は、それに見合うだけの非常に価値のある報酬です。迷わず、今日から小さなマイクロサービスで始めてみてください!

