aws sdk Go(v1) を使う場合はHTTPクライアントの設定を行おう

AWS SDK for Goを使って多数のDynamoDBクエリを行うワークロードのパフォーマンスをコネクションプールのサイズをチューニングすることで改善しました。
2022.02.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

aws sdk Go(v1) では各クライアントが使用するhttpクライアントの設定をカスタマイズすることができます。

この記事では、あるワークロードのパフォーマンスを改善していてこの設定が非常に重要だったことに気づいたのでその経緯をまとめています。

想定しているワークロード

今回想定しているワークロードは以下のようなものです。

  • ECSのタスクとしてGoで実装されたワーカープロセスが起動する
  • プログラムの中では1000個程度のGo ルーティンが起動してそれぞれが繰り返しDynamoDBにクエリを行う

パフォーマンスの問題

このECSタスクである日too many open files エラーが発生しました。タスクで明示的にulimit(nofiles)の上限を設定していなかったので上限を大きく設定してベンチマークしてみたところ、上限を増やすほどパフォーマンスが悪化していきました。ulimitを設定することでエラーが発生しなくなりましたがパフォーマンスの悪化は想定外だったのでこの原因を調査しました。

ボトルネックの特定

上記のプログラムをnofilesの上限を変更してtimeで実行時間を計測すると下記のようにsysの時間がぐんぐん伸びていくのがわかります。

# | nofiles | max conn | max conns per host | real | user | sys 
-- | -- | -- | -- | -- | -- | -- | --
0 | 1024 | 100 | 2 | 1m49.239s | 1m7.309s | 0m38.332s 
1 | 2048 | 100 | 2 | 2m27.203s | 1m18.238s | 0m51.000s
2 | 4096 | 100 | 2 | 2m33.433s | 1m19.089s | 1m24.792s

また実行中のプロセスが使っているファイルディスクリプタをlsof で確認してみるとほとんどがDynamoDBへの接続で、その数がnofilesの上限付近に達していることがわかりました。このことからDynamoDBへのHTTP接続がボトルネックではないかとあたりをつけてHTTPクライアントの実装について調査しました。

GoのHTTPクライアントにおけるコネクションの再利用

調べていくとGoのHTTPクライアント(net/http)をデフォルトで使うと大量のHTTPリクエストを同時に行う場合にはコネクションが再利用されにくく、パフォーマンスの問題が発生することがわかりました。

この詳細はGoのnet/httpクライアントで大量のリクエストを高速に行う方法 が詳しいです。

HTTPクライアントのカスタマイズ

カスタマイズについては Creating a Custom HTTP Client には以下のように記載されています。

Although you can change some of these configuration values, the default HTTP client and transport are not sufficiently configurable for customers using the AWS SDK for Go in an environment with high throughput and low latency requirements.

今回は以下の2つを設定します。

  • Transport.MaxIdleConns (default 100)
    • This setting represents the maximum number of idle (keep-alive) connections across all hosts. One use case for increasing this value is when you are seeing many connections in a short period from the same clients
  • Transport.MaxIdleConnsPerHost (defualt 2)
    • This setting represents the maximum number of idle (keep-alive) connections to keep per-host. One use case for increasing this value is when you are seeing many connections in a short period from the same clients

※設定するコード例は割愛

MaxIdleConns(max conn) およびMaxIdleConnsPerHost(max conns per host)を以下のように設定して再度測定したところ、実行時間が改善できました。またsysの時間も短縮されています。

# | nofiles | max conn | max conns per host | real | user | sys 
-- | -- | -- | -- | -- | -- | -- | --
0 | 1024 | 2048 | 1024 | 0m36.074s | 0m32.393s | 0m10.912s 
1 | 2048 | 4096 | 1024 | 0m31.884s | 0m29.220s | 0m9.355s

まとめ

ドキュメントの記載内容をみるとカスタム設定を適用するのはユースケースによりけりといった雰囲気なのですが、実際はよほど負荷が低いワークロードでない限りは設定をしたほうがいい結果が得られると思います。