添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

はじめに

TIG DXユニットの真野です。

DynamoDBやKinesis Data Streamsなどを利用するサービスをそれなりの期間で稼働させているとポツポツ下記のようなエラーが発生することが分かりました。

エラーログ(改行を追加しています)
[MY-APP-ERROR-LOG] RequestError: send request failedcaused by:
Post "https://kinesis.ap-northeast-1.amazonaws.com/":
read tcp 169.254.0.1:55638->3.113.218.4:443:
read: connection reset by peer

ここで疑問に思ったのは、少なくてもAWS SDK for Goを使っている限りは必要に応じてデフォルトでリトライをしてくれているはずです。下記のドキュメントでは通常は3回のリトライを実施してくれるとあります

  • Retries and Timeouts | AWS SDK for Go V2
  • では、上記のようなエラーがでるということはリトライを使い果たしても失敗したのでしょうか? そもそも read: connection reset by peer って正確には何だ? という状態だったので調べました。

    read: connection reset by peer とは

    サーバ側から(今回だとKinesis Data Streamsのエンドポイントのサーバ)から RST(Reset TCP) パケット(正確言うとRSTフラグが1のパケット)が送られて来た時にハンドリングされたエラーメッセージです。これを送信された場合は、接続要求や通信状態が拒否されたものとみなし、通信をリセットして終了する必要があるとのことです。発生条件はサーバ側の処理能力を超えた場合などに発生しうるそうです。

  • RSTパケット(reset packet)とは - IT用語辞典 e-Words
  • 発生箇所は色々考えられますが、 エラーメッセージに read tcp xxxx とある場合はリクエストを送信して、レスポンスを読み込もうとして(read tcpしようとして)発生したと推測できます。

    つまり、今回のログで言うと Post "https://kinesis.ap-northeast-1.amazonaws.com/" のリクエストはサーバ側に届いたものの、レスポンスを受信するタイミングでTPCレイヤーで通信に失敗したと見なせると思います(自信が無いので間違っていましたらご指摘下さい)

    Go側ではRSTパッケージを送られたかどうかは、エラーの文字列に connection reset by peer が含まれているかどうかでも分かりますし、ガンバるのであれば、syscallパッケージで判定できそうです。

    RST判定のサンプル実装
    import (
    "net"
    "os"
    "syscall"
    )

    func IsRSTErr(err error) bool {
    if opErr, ok := err.(*net.OpError); ok {
    if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
    return sysErr.Err == syscall.ECONNRESET
    }
    }
    return false
    }

    本題から少し逸れたので、リトライについて話を戻します。

    AWS SDK for Go側のリトライハンドリングについて

    AWS SDK for Goのリトライ処理についてはカスタマイズ可能です。方法は辻さんが過去にブログを書いてくれています。

  • AWS SDK for Goのリトライアルゴリズムを差し替える方法 | フューチャー技術ブログ
  • デフォルトの仕組みは、DefaultRetryerの ShouldRetry で、どのようなエラーが発生した時に、 リトライすべきか否か を判定しています。 ShoudRetry をさらに追っていくと、 IsErrorRetryable という関数からさらに isErrConnectionReset という関数があることに気が付きます。

    connection_reset_error.goに実装された関数isErrConnectionReset を見ると、かなり興味深い実装です。

    connection_reset_error.goから抜粋
    func isErrConnectionReset(err error) bool {
    if strings.Contains(err.Error(), "read: connection reset") {
    return false
    }

    if strings.Contains(err.Error(), "use of closed network connection") ||
    strings.Contains(err.Error(), "connection reset") ||
    strings.Contains(err.Error(), "broken pipe") {
    return true
    }

    return false
    }

    なんと、 read: connection reset が含まれている場合は、 リトライを行わない 判定になっていました。 read が入っていない connection reset はリトライを行うとは対照的です。

    コミットのハッシュ値からこのコードへの補足を探すと、簡潔に説明しているコメントが見つかります。

  • https://github.com/aws/aws-sdk-go/pull/2926#issuecomment-553196888
  • https://github.com/aws/aws-sdk-go/pull/2926#issuecomment-553637658
  • 書いていることを整理しました。

  • (今回で言うとKinesis)へのサービスへのリクエストの書き込みに成功/失敗について、SDK側は分からない
  •