Actix Webを活用した高並行分散システムの設計パターン

Actix Webは、Rust製の非同期Webフレームワークであり、高スループット低レイテンシを両立させたマイクロサービス基盤として注目を集めている。本稿では、実践的なコード例とパフォーマンスチューニング手法を交えながら、分散環境で活きる設計パターンを解説する。

非同期ランタイムの内部構造

Actix WebはTokioランタイムの上に独自のActorレイヤーを載せており、リクエストごとに独立した「Context」を生成することで競合を回避する。以下は最小構成のサーバ起動例である。

use actix_web::{web, App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health", web::get().to(health_check))
    })
    .bind(("0.0.0.0", 3000))?
    .workers(num_cpus::get())
    .run()
    .await
}

async fn health_check() -> impl actix_web::Responder {
    "OK"
}

workers()にCPUコア数を渡すことで、ワーカーごとに独立したイベントループが生成され、ロックフリーなスケーリングを実現する。

ミドルウェアチェーンの組み立て方

Actix Webのミドルウェアは「Transform」トレイトを実装した構造体として定義される。以下はカスタムタイムアウトミドルウェアの実装例。

use std::time::{Duration, Instant};
use actix_web::{
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    Error,
};
use futures_util::future::{ok, Ready};

pub struct TimeoutMiddleware {
    pub limit: Duration,
}

impl<S, B> Transform<S, ServiceRequest> for TimeoutMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = TimeoutService<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(TimeoutService {
            service,
            limit: self.limit,
        })
    }
}

pub struct TimeoutService<S> {
    service: S,
    limit: Duration,
}

impl<S, B> Service<ServiceRequest> for TimeoutService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    // ...省略...
}

このミドルウェアをApp::wrap()で登録するだけで、全エンドポイントに横断的にタイムアウトを適用できる。

型安全ルーティングと抽出器

パスパラメータやクエリ文字列はコンパイル時に検証されるため、ランタイムエラーがゼロに近づく。以下は複合的な抽出器の使用例。

use actix_web::{get, web, Result};
use serde::Deserialize;

#[derive(Deserialize)]
struct PageQuery {
    offset: Option<usize>,
    limit: Option<usize>,
}

#[get("/repos/{owner}/{name}/issues")]
async fn list_issues(
    path: web::Path<(String, String)>,
    query: web::Query<PageQuery>,
) -> Result<String> {
    let (owner, repo) = path.into_inner();
    let PageQuery { offset, limit } = query.into_inner();
    Ok(format!(
        "owner={}, repo={}, offset={:?}, limit={:?}",
        owner, repo, offset, limit
    ))
}

抽出器は自動的にバリデーションを実行し、不正なリクエストに対しては400レスポンスを返す。

負荷分散とサービスメッシュ連携

Actix Web単体でも高い性能を発揮するが、KubernetesやConsulなどのサービスディスカバリ基盤と組み合わせることで水平スケーラビリティが飛躍的に向上する。以下はEnvoyサイドカー構成でのヘルスチェックエンドポイント例。

use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "actix_web=info");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .service(
                web::scope("/ready")
                    .route("", web::get().to(ready))
                    .route("/live", web::get().to(live)),
            )
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}

async fn ready() -> HttpResponse {
    // DB接続などの準備チェック
    HttpResponse::Ok().body("ready")
}

async fn live() -> HttpResponse {
    HttpResponse::Ok().body("live")
}

/readyはPodがトラフィックを受け入れられる状態かを判定し、/liveはプロセス生存確認に使われる。

メモリ効率を高めるレスポンスストリーミング

巨大なJSONやCSVを返す際、チャンク単位でストリーミングすることでメモリ占有量を大幅に削減できる。

use actix_web::{get, web, HttpResponse};
use futures_util::stream::{repeat, StreamExt};

#[get("/stream/{n}")]
async fn counter_stream(path: web::Path<usize>) -> HttpResponse {
    let n = path.into_inner();
    let body = repeat(())
        .take(n)
        .enumerate()
        .map(|(i, _)| Ok(web::Bytes::from(format!("{}\n", i + 1))));
    HttpResponse::Ok()
        .content_type("text/plain")
        .streaming(body)
}

このエンドポイントはn個の数字を逐次送信し、サーバ側は全体をメモリに載せないため、GB単位のデータでも安定して配信できる。

まとめ

Actix Webは単なるHTTPサーバではなく、ActorモデルゼロコピーI/O型安全APIを統合した分散システムの要石である。上記パターンを踏襯することで、Rustのメモリ安全性とActixの非同期性能を最大限に活用したサービスを構築できる。

タグ: Actix-Web rust async actor-model Middleware

8月25日 09:47 投稿