$30 off During Our Annual Pro Sale. View Details »

自作オブジェクトストレージをRustで

Avatar for noharu36 noharu36
December 01, 2025

 自作オブジェクトストレージをRustで

Avatar for noharu36

noharu36

December 01, 2025
Tweet

More Decks by noharu36

Other Decks in Programming

Transcript

  1. 自己紹介 { name: 能島明希 handle: harukun origin: 広島->岡山->大阪->東京->会津 tech: {

    front-end: React+TS backend: Rust, Go etc: Rust, Haskell Rust: Rust } favorites: Tobacco, BoyScout, Rust, Neovim, NixOS Twitter(x): https://twitter.com/pieceofharuki Blog: https://zenn.dev/haru_blog }
  2. 外部クレート [dependencies] reed-solomon-erasure = "6.0.0" bytes = "1.10.1" tokio =

    { version = "1.44.2", features = ["full"] } anyhow = "1.0.98" axum={ version = "0.8.3", features = ["multipart"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" futures = "0.3.31" tokio-util = { version = "0.7.14", features = ["io"] } mime_guess = "2.0.5" tower-http = { version = "0.6.2", features = ["limit"] } rayon = "1.10.0" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] } chrono = "0.4.41" dotenvy = "0.15.7" [dependencies.uuid] version = "1.16.0" features = ["v4"]
  3. 軽く説明 • reed_solomon_erasure: イレイジャーコーディングの部分をよしなにやってくれる • tokio, tower_http, axum: Rustの非同期ランタイムとかWebフレームワークとか。   

    サーバーをたてる上で使う部分 • tracing: ログをいい感じに出してくれるやつ • sqlx: DB接続ライブラリ。ORMではない。マクロを使うとコンパイル時チェックが優秀 • anyhow: エラー管理ライブラリ。エラーの型を脳死で統合できるので良くも悪くも楽できる
  4. 構成 • benches/ : ベンチマークを計測するためのコードを置く • migrations/ : マイグレーションファイル •

    src/handler/ : REST APIに対応する関数とか • src/db.rs : DBにアクセスするやつ • src/encode.rs , src/decode.rs : データのエンコード・デコー ド、ストレージへの格納・ロードの処理 • src/server.rs : サーバーの設定とか
  5. ルーティングの例 #[instrument] fn object_routes() -> Router<MetadataStore> { Router::new() .route( "/bucket/{:bucket_name}/{:object_id}",

    post(post_object).get(get_object).delete(delete_object), ) .layer(DefaultBodyLimit::disable()) .layer(RequestBodyLimitLayer::new(3 * 1024 * 1024 * 1024)) } /bucket/{bucket_name}/{object_id}に POST、GET、DELETEのメソッドをはやしている Bodyのサイズを3GBまでに制限している
  6. POSTの処理 sample.mp4 (100mb) post_object( Path((bucket_name, object_id)), State(store), mut multipart )

    save_shards(shards,object_id) store_data(bytes, id) encode_file(content) /bucket/{bucket_name}/{object_id}に POSTリクエストを送る multipart形式でデータを受け取る。 bucketが存在しているかをチェックし、メタデータを作成 してDBに登録する。 データをBytesに変換してメモリに読み込み、 store_data 関数の引数にする bytesをbytesmutに変換し、 encode_file関数の引数にする reed-solomon符号を用いてデータを分割し、 Result<Vec<BytesMut>を返す ベクタのインデックスと object_idを組み合わせてハッシュ化し、 その値を用いて(おそらくなるべくなんとなく)データが均一に分 散されるようストレージに保存する
  7. IntoResponse post, deleteの関数の返り値は特定の型ではなく Impl IntoResponseとしている src/handler/api.rsで共通のレスポンスの型を定義し ている #[derive(Debug, Serialize)] struct

    ApiResponse<T> { status: String, data: T, } #[derive(Debug, Serialize)] struct ApiError { status: String, message: String, } pub enum ApiResult<T> { Success(StatusCode, T), Error(StatusCode, String), } impl<T: Serialize> IntoResponse for ApiResult<T> { fn into_response(self) -> axum::response::Response { match self { ApiResult::Success(status, data) => ( status, Json(ApiResponse { status: "success".to_string(), data, }), ) .into_response(), ApiResult::Error(status, message) => ( status, Json(ApiError { status: "error".to_string(), message, }), ) .into_response(), } } }