🦀
aws-sdk-rustを高速化する at Mac
ことのはじまり
GoとRustでaws sdkを使ってS3にアクセスしてみたところ、
main.go
package main
import (
"context"
"errors"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/smithy-go"
)
func main() {
start := time.Now()
profile := "hakusai"
bucket := "hakusai-test-bucket"
ctx := context.Background()
sdkConfig, err := config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile))
if err != nil {
fmt.Println("Couldn't load default configuration. Have you set up your AWS account?")
fmt.Println(err)
return
}
s3Client := s3.NewFromConfig(sdkConfig)
_, err = s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: &bucket})
if err != nil {
var ae smithy.APIError
if errors.As(err, &ae) && ae.ErrorCode() == "AccessDenied" {
fmt.Println("You don't have permission to list buckets for this account.")
} else {
fmt.Printf("Couldn't list buckets for your account. Here's why: %v\n", err)
}
return
}
requestSent := time.Since(start)
fmt.Printf("request sent %d\n", requestSent)
}
main.rs
use std::{error::Error, time::Instant};
use aws_config::BehaviorVersion;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let start = Instant::now();
let profile = "hakusai";
let bucket = "hakusai-test-bucket";
let config = aws_config::ConfigLoader::default()
.behavior_version(BehaviorVersion::latest())
.profile_name(profile)
.load()
.await;
let client = aws_sdk_s3::Client::new(&config);
let _result = client.list_objects_v2().bucket(bucket).send().await?;
let request_sent = start.elapsed();
println!("{:?}", request_sent);
Ok(())
}
Cargo.toml
[package]
name = "aws_sdk_rust_bench"
version = "0.1.0"
edition = "2021"
[dependencies]
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
aws-sdk-s3 = "1.71.0"
tokio = { version = "1", features = ["full"] }
[profile.release]
debug = true
- Go 89ms
- Rust 207ms
もちろんRustが常に最速とは限りませんが、さすがに差が大きすぎないか?と思ったので調べてみました。
ボトルネックを特定する
cargo flamegraph
を使ってどこに時間が掛かっているかを見てみます。
一番時間がかかっているのがrustls_native_certs::macos::load_native_certs
から呼ばれているSecurity SecTrustSettingsCopyTrustSettings
https://developer.apple.com/documentation/security/sectrustsettingscopytrustsettings(::_:)
これはMacOSの標準APIで、どうやらrustls
経由でOSが信用している証明書を取ってくる処理に時間がかかっているようです。(他のOSの場合はここまで時間はかからないかもしれません。)
解決策
aws-sdk-rust
が使うrustls
の設定を変更し、OS搭載の証明書の代わりに他の証明書で検証するようにしてみます。hyper_rustls
のfeaturesを指定すれば代わりにwebpki_roots
を使うことができます。webpki_roots
はソースコードにCA証明書のバイト列がベタ書きされているという力技です。
以下のように書き換えます。
Cargo.toml
[package]
name = "aws_sdk_rust_bench"
version = "0.1.0"
edition = "2021"
[dependencies]
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
aws-sdk-s3 = "1.71.0"
aws-smithy-runtime = "1.7.7"
hyper-rustls = { version = "0.25.0", features = ["webpki-roots"] }
tokio = { version = "1", features = ["full"] }
[profile.release]
debug = true
main.rs
use std::{error::Error, time::Instant};
use aws_config::BehaviorVersion;
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let start = Instant::now();
let profile = "hakusai";
let bucket = "hakusai-test-bucket";
let rustls_connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_webpki_roots()
.https_only()
.enable_http1()
.build();
let http_client = HyperClientBuilder::new().build(rustls_connector);
let config = aws_config::ConfigLoader::default()
.behavior_version(BehaviorVersion::latest())
.profile_name(profile)
.http_client(http_client)
.load()
.await;
let config_loaded = start.elapsed();
println!("{:?}", config_loaded);
let client = aws_sdk_s3::Client::new(&config);
let _result = client.list_objects_v2().bucket(bucket).send().await?;
let request_sent = start.elapsed();
println!("{:?}", request_sent - config_loaded);
Ok(())
}
注意点としては、aws-sdk-rust
は内部的にはhttp
クレートの0.2系に依存しているので、1系に依存している最新のhyper-rustls
を使うと型が合わずにコンパイルエラーになります。以下のエラーが出たらバージョンを見直してください。
the trait bound `HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector>: tower_service::Service<http::uri::Uri>` is not satisfied
the trait `tower_service::Service<http::uri::Uri>` is implemented for `HttpsConnector<hyper_util::client::legacy::connect::http::HttpConnector>`
for that trait implementation, expected `http::uri::Uri`, found `http::uri::Uri`
- 変更前 207ms
- 変更後 110ms
十分でしょう。
参考
Discussion