なぜバックエンドにRustなのか
「Rustは難しい」という評判が先行しているが、バックエンドにおけるRustの採用は確実に増加している。DiscordがGoからRustに移行してtail latencyを大幅改善した事例は有名だが、日本でもメルカリ、LINE、サイバーエージェントが一部サービスでRustを採用している。
KGAがRustをバックエンドに採用する理由は3つ。メモリ安全性(GCなしでメモリリーク・データ競合をコンパイル時に排除)、パフォーマンス(C/C++同等のスループットとレイテンシ)、運用コスト削減(GCのstop-the-worldがないためp99レイテンシが安定し、OOM killのリスクが低い)。
特に3番目が本番環境で重要だ。Go製のAPIサーバーでGCによるp99レイテンシスパイクが200msに達していたケースを、Rust(Axum)に書き換えたところp99が8msに安定した。平均レイテンシは大差ないが、テールレイテンシの改善は25倍だ。
Actix-web vs Axum: 2025年の選択
- 年時点でRustのWebフレームワークは実質2択だ。Actix-webとAxum。
Actix-webはcrates.ioでのダウンロード数が最多で、長い歴史と安定した実績がある。独自のactorシステムを持ち、WebSocketやストリーミング処理に強い。ただし、tokioとは別のランタイム(actix-rt)を使用するため、tokioエコシステムの他ライブラリとの互換性に注意が必要だ。
Axumはtokioチーム公式のWebフレームワークで、towerミドルウェアとの完全な互換性を持つ。型安全なextractorパターンが強力で、ハンドラー関数のシグネチャだけでリクエストの解析方法が決まる。2024年のv0.7、2025年のv0.8で安定性が大幅に向上し、KGAの新規プロジェクトではAxumを標準としている。
ベンチマーク比較: JSONレスポンス(Hello World相当)のスループット: Actix-web 680,000 req/s、Axum 650,000 req/s。DB接続ありのCRUD操作: Actix-web 45,000 req/s、Axum 43,000 req/s。実用上の差はほぼない。選定基準はパフォーマンスではなく、エコシステム互換性とチームの学習コストで判断すべきだ。
所有権モデル: バックエンドエンジニアの理解法
Rustの所有権(ownership)はフロントエンド・バックエンド問わず最大の学習障壁だが、バックエンドの文脈で理解すると腑に落ちやすい。
「所有権」=「リソースの解放責任」と読み替える。DBコネクションプールのコネクション、ファイルハンドル、ネットワークソケット。これらのリソースは使い終わったら返却する必要がある。Rustの所有権はこの「誰がリソースを解放する責任を持つか」をコンパイル時に追跡する仕組みだ。
「借用」(borrowing)=「コネクションプールからの一時貸出」。関数にDB接続を渡す際、所有権を移す(move)のではなく借用(&reference)する。読み取り専用なら&db、書き込みも必要なら&mut db。これはコネクションプールの共有リード/排他ライトロックと同じ概念だ。
「ライフタイム」=「リクエストスコープ」。HTTPリクエストのライフタイムに紐づくデータ(リクエストボディ、認証情報等)は、レスポンスを返すまで有効でなければならない。Rustのライフタイム注釈はこの「データがどのスコープまで有効か」を型で表現する。
async/awaitとtokioランタイム
Rustのasync/awaitはNode.jsやPythonのasyncと概念は同じだが、実行モデルが根本的に異なる。Rustのasyncは「ゼロコスト抽象化」で、Future(Promiseに相当)はヒープアロケーションなしにステートマシンにコンパイルされる。
tokioはRustのデファクト非同期ランタイムで、マルチスレッドのワークスティーリングスケジューラを提供する。Node.jsがシングルスレッドイベントループなのに対し、tokioはCPUコア数分のワーカースレッドでタスクを並列実行する。
注意すべきはtokioのruntime flavor設定だ。current_thread(シングルスレッド)とmulti_thread(マルチスレッド)の2モードがある。CPU boundな処理が混在する場合、current_threadではイベントループがブロックされる。KGAではmulti_threadをデフォルトとし、CPU bound処理はtokio::task::spawn_blockingで専用スレッドプールに逃がしている。
実践的な開発Tips
Rustのコンパイル時間は最大の開発体験上の問題だ。KGAの実測値: クリーンビルド 3分20秒、インクリメンタルビルド 12秒(中規模プロジェクト、約50クレート)。対策として、cargo-watchでファイル保存時に自動ビルド、sccache(共有コンパイルキャッシュ)でCI/CDビルドを40%高速化、craneliftバックエンドでdebugビルドを50%高速化(最適化は無効だが開発中は十分)している。
エラーハンドリングはanyhow(アプリケーションコード用)とthiserror(ライブラリコード用)の2クレートを標準採用。HTTP APIのエラーレスポンスはAxumのIntoResponseトレイトを実装したカスタムエラー型で統一している。
ORMはsqlxを推奨。SeaORMやDieselも選択肢だが、sqlxのコンパイル時SQLバリデーション(query!マクロがSQLの構文とスキーマをコンパイル時に検証)の安心感は他に代えがたい。実行時のSQL文字列組み立てバグがゼロになる。