Slide 1

Slide 1 text

そのリトライ、 死んだコネクションを使い回していませんか ── 実プロダクト障害から考える Go HTTPクライアントのタイムアウト設計 REALITY株式会社 エンジニア 松田祐佐

Slide 2

Slide 2 text

松田祐佐(まつだ ゆうさ) REALITY株式会社 バックエンドエンジニア ● 2020年グリー株式会社(現 グリーホール ディングス株式会社)新卒入社 ● 2022年からグリーグループのREALITY株式 会社でVTuberの配信プラットフォームアプリ 「REALITY」の開発に従事 REALITY上での アバターです! 2

Slide 3

Slide 3 text

Goのnet/httpにあるHTTPクライアントって たくさんタイムアウト設定ありますよね 3

Slide 4

Slide 4 text

PingTimeout、IdleConnTimeout、 net.Dialer.Timeout、... 4

Slide 5

Slide 5 text

これらのパラメータに設定している値、 皆様は自信を持ってこれはこうしているんだと 言えますでしょうか? 5

Slide 6

Slide 6 text

弊社ではできていないです😭 6

Slide 7

Slide 7 text

ただ、このパラメータは、こういう背景があって この値にしているんだという根拠を持って 設定することはできています 7

Slide 8

Slide 8 text

リトライを考慮したタイムアウト設計 目指すべきリトライの姿 SendPingTimeout + PingTimeout < Contextのタイムアウト 8

Slide 9

Slide 9 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 9

Slide 10

Slide 10 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 10

Slide 11

Slide 11 text

あるネットワーク上の障害 背景 ● REALITYでは、親会社であるグリーグループで広く使われている課金・ 認証基盤を利用 ● この課金認証基盤のAPIサーバに対して、REALITYのサーバから HTTP/2でAPIリクエストを投げる形で利用 起きた障害 ● ある日突然、課金認証基盤へのAPIリクエストが、context.Contextを用 いてREALITY側で設定したタイムアウトにより大量に失敗する現象が 発生 ● エラーメッセージは「context deadline exceeded」 11

Slide 12

Slide 12 text

前提:システムの構成図 REALITY 課金認証基盤 k8s Pod Pod Pod API サーバ HTTP/2 ロード バランサ API サーバ API サーバ 12 12

Slide 13

Slide 13 text

前提:システムの構成図 REALITY 課金認証基盤 k8s Pod Pod API サーバ HTTP/2 ロード バランサ API サーバ API サーバ Pod ここ起因で障害が起きた! 13 13

Slide 14

Slide 14 text

障害の特徴 今回の障害の特徴 1. 定常的ではなく、急にバースト的に発生して、しばらくすると 自然回復する 2. 一度発生したらほぼ全てのAPIリクエストについてタイムアウトする 3. 課金認証基盤側のロードバランサにそもそもリクエストが 到達していない 早期に解決したいが、なかなか原因も対策もつかめず、 毎日補填対応をしながら調査を並行して進めるという期間が続きました... 14 14

Slide 15

Slide 15 text

問題が起きていたコード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... req, err := http.NewRequestWithContext( ctx, "POST", "/api/v1/coin/issue", bytes.NewReader(data) ) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { return err } } 15 15

Slide 16

Slide 16 text

問題が起きていたコード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... req, err := http.NewRequestWithContext( ctx, "POST", "/api/v1/coin/issue", bytes.NewReader(data) ) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { return err } } 16 16

Slide 17

Slide 17 text

問題が起きていたコード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... req, err := http.NewRequestWithContext( ctx, "POST", "/api/v1/coin/issue", bytes.NewReader(data) ) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { return err } } 17 17

Slide 18

Slide 18 text

問題が起きていたコード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... req, err := http.NewRequestWithContext( ctx, "POST", "/api/v1/coin/issue", bytes.NewReader(data) ) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { return err } } 18 18

Slide 19

Slide 19 text

問題が起きていたコード func createHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() … (DialContextだったり IdleConnTimeoutだったりその他パラメータも調整 ) } transport.HTTP2 = &http.HTTP2Config{ SendPingTimeout: 10 * time.Second, PingTimeout: 10 * time.Second, } return &http.Client{ Transport: transport, } var client = createHTTPClient() 19 19

Slide 20

Slide 20 text

問題が起きていたコード func createHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() … (DialContextだったり IdleConnTimeoutだったりその他パラメータも調整 ) } transport.HTTP2 = &http.HTTP2Config{ SendPingTimeout: 10 * time.Second, PingTimeout: 10 * time.Second, } return &http.Client{ Transport: transport, } AIに推奨されたパラメータに推奨値をそのまま入れていた var client = createHTTPClient() 20 20

Slide 21

Slide 21 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 21

Slide 22

Slide 22 text

対策:一時的な問題に見えるので、リトライする ● 最初に思ったこと:一時的なネットワーク起因の問題なのでは? ○ 障害時も課金認証基盤側のシステムに問題が起きていないことは確認 ○ APIリクエストがタイムアウトする状態は一定時間経過すると自然回復 ● そもそも ○ 問題が起きていた箇所ではAPIリクエストを一度しか呼び出さないコード になっていた 仮説 一時的なネットワーク起因の問題のように見えるので、リトライするように 直せばOKなのでは? 22 22

Slide 23

Slide 23 text

対策:リトライを入れた擬似コード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { for i := range 2 { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { ... continue } } return errors.New("リトライしても失敗 ") } 23 23

Slide 24

Slide 24 text

対策:リトライを入れた擬似コード var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { for i := range 2 { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { ... continue } } return errors.New("リトライしても失敗 ") } 24 24

Slide 25

Slide 25 text

対策:結果 解決しなかった! リトライしても成功せず、1回目と同じ 「context deadline exceeded」が発生 25

Slide 26

Slide 26 text

Podのログを観察してみると... ● とりあえずで対策した案がうまくいかないのでPodのログを 眺めてみる ● 以下のような現象が確認された ● ⼀度課⾦認証基盤へのAPIアクセスが「context deadline exceeded」で タイムアウトするようになると、そのpodからのAPIアクセスは リトライしてもほぼ成功しない状態が続く ● ただAPIリクエストの中に「http2: client connection lost」という エラーが出ると、そこからはAPIリクエストが成功するようになる 26

Slide 27

Slide 27 text

Podのログを観察してみると... ● コネクションが切断されたログ「http2: client connection lost」がでたら、 再度うまくいくようになった ● つまりコネクションが新規に接続され直したらうまくいくようになった 仮説 今のリトライ処理はネットワーク的に不調なコネクションを 使いまわしているからうまくいかないのではないか? 27

Slide 28

Slide 28 text

解決につながりそうな仮説 ● 仮説:今のリトライ処理はネットワーク的に不調なコネクションを 使いまわしているのではないか? ● ということは 使用しているコネクションがネットワーク不良の時、 「http2: client connection lost」が出る(つまり切断する)ように してから、APIリクエストを送ればうまくいくのではないか net/httpのコネクションの死活管理の仕組みについて深掘りしよう 28

Slide 29

Slide 29 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 29

Slide 30

Slide 30 text

HTTP/2の基礎:フレーム ● HTTP/1ではリクエストやレスポンスは基本的に一塊のデータとしてやりとり ○ transfer-encoding: chunkedなどの部分的な例外は存在 ● HTTP/2ではリクエストやレスポンスは「フレーム」という単位で細切れに されてやりとり HTTP/1 HTTP/2 30

Slide 31

Slide 31 text

HTTP/2の基礎:ストリーム ● HTTP/1では1本のTCPコネクションには同時に一つのリクエスト・レスポンスの やりとりしかできない ● HTTP/2では1本のTCPコネクション上で同時に複数のリクエスト・レスポンスの やりとりをできる ○ リクエスト/レスポンスのペアをストリームという単位で識別し、各ストリームごとのフレーム を混ぜて送っている HTTP/2 HTTP/1 * HTTP/1に関して「パイプライン」もあるがここでは省略 31

Slide 32

Slide 32 text

HTTP/2におけるnet/httpのclient.Do概観 コネクションプール map[string][]*ClientConn 送信用 goroutine A client.Do A “example.com:443” コネクション1 送信用 goroutine B Aのフレーム Bのフレーム Aのフレーム Aのフレーム リクエスト Bのフレーム レスポンス ⋮ client.Do B 受信ループ用 goroutine The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). ⋮ 32

Slide 33

Slide 33 text

HTTP/2におけるnet/httpのclient.Do概観 コネクションプール map[string][]*ClientConn 送信用 goroutine A client.Do A “example.com:443” コネクション1 送信用 goroutine B Aのフレーム Bのフレーム Aのフレーム リクエスト Bのフレーム レスポンス ⋮ http2: client connection lost client.Do B のキーマン 受信ループ用 goroutine The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). Aのフレーム ⋮ 33

Slide 34

Slide 34 text

「http2: client connection lost」が出るタイミング ● net/httpはHTTP/2コネクションごとに対応する受信ループ用の goroutineをもち、レスポンスや制御用のフレームを読み続けている ● 受信ループごとにヘルスチェック処理を特定のタイミングで実行しており、 ヘルスチェックに失敗すると対応するコネクションが死んだと判定される このとき処理中だったリクエストは「http2: client connection lost」で 失敗する コネクションはプールから切断‧破棄され、次のリクエストは新しい コネクションを確⽴する 34

Slide 35

Slide 35 text

なぜこのヘルスチェック処理が必要なのか ● TCPの切断は必ずしも相手に通知されるとは限らない ○ 経路上の障害、ロードバランサの退役など ● 上記のような場合、片方だけが「切れた」と認識している状態が発生する →「half-open connections」 ○ 送信側: ローカルのバッファに書き込めてしまい、エラーにならない ○ 受信: 何も届かないだけなのでレスポンスに時間がかかっているだけの場合と区別がつかない ● したがってhalf-open connectionsの場合には能動的に検知をしなければ、 気づきようがない このヘルスチェック処理はhttp.DefaultClientでは動かないような 設定になっている 35

Slide 36

Slide 36 text

net/httpの能動的なコネクション死活管理の仕組み ● ヘルスチェック処理は以下のようなステップで行われる ○ 受信ループのgoroutineで一定時間フレームを読み込んでいない場合に ヘルスチェック処理を発火 ○ 受信ループが対応しているコネクション上でPingフレームという制御用のフレー ムを送る ○ Pingフレームに対し、一定時間以内に ■ ACKが返された場合はコネクションを「Healthy」とする ■ ACKが返されなかった場合はコネクションを「Unhealthy」とする ○ 「Unhealthy」となったコネクションはコネクションプールから除外され、そのコネク ション上のストリームを使っていた処理は「http2: client connection lost」エラー で中断される 36

Slide 37

Slide 37 text

net/httpのヘルスチェック処理 client.Do A 送信⽤ goroutine A コネクション1 Aのフレーム Aのフレーム The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). Aのフレーム リクエスト レスポンス 受信ループ⽤ goroutine 37

Slide 38

Slide 38 text

net/httpのヘルスチェック処理 受信ループのgoroutineで一定時間フレームを読み込んでいない場合に ヘルスチェック処理を発火 client.Do A 送信⽤ goroutine A コネクション1 Aのフレーム リクエスト レスポンス 最近フレーム読み込んでないな... The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). 受信ループ⽤ goroutine 38

Slide 39

Slide 39 text

net/httpのヘルスチェック処理 受信ループのgoroutineで一定時間フレームを読み込んでいない場合に ヘルスチェック処理を発火 送信⽤ goroutine A client.Do A コネクション1 Aのフレーム リクエスト レスポンス ヘルスチェック⽤ goroutine 起動 The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). 念の為ヘルスチェックするか! 受信ループ⽤ goroutine 39

Slide 40

Slide 40 text

net/httpのヘルスチェック処理 受信ループが対応しているコネクション上でPingフレームという制御用の フレームを送る 送信⽤ goroutine A client.Do A コネクション1 Pingフレーム Aのフレーム リクエスト レスポンス ヘルスチェック⽤ goroutine The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). 受信ループ⽤ goroutine 40

Slide 41

Slide 41 text

net/httpのヘルスチェック処理 一定時間内にACKが返された場合はコネクションを「Healthy」とする 送信⽤ goroutine A client.Do A コネクション1 Pingフレーム Ping ACK Aのフレーム リクエスト レスポンス ヘルスチェック⽤ goroutine 通知 ⼀定時間内にPing ACK 返ってきました! The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). よし!コネクション⼤丈夫だな! 受信ループ⽤ goroutine 41

Slide 42

Slide 42 text

net/httpのヘルスチェック処理 一定時間内にACKが返されなかった場合はコネクションを「Unhealthy」とする 送信⽤ goroutine A client.Do A コネクション1 Pingフレーム Ping ACK Aのフレーム リクエスト レスポンス ヘルスチェック⽤ goroutine 通知 ⼀定時間内にPing ACK 返ってきませんでした... The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). え、このコネクションダメだ... 受信ループ⽤ goroutine 42

Slide 43

Slide 43 text

net/httpのヘルスチェック処理 「Unhealthy」となったコネクションはコネクションプールから除外され、 そのコネクション上のストリームを使っていた処理は「http2: client connection lost」 エラーで中断される client.Do A http2: client connection lost 除外だ! このコネクション使っているやつに連 絡! The Go gopher was designed by Renée French. Gopher illustrations by Egon Elbre (CC0). 受信ループ⽤ goroutine 43

Slide 44

Slide 44 text

net/httpの能動的なコネクション死活管理の仕組み ● ヘルスチェック処理は以下のようなステップで行われる ○ 受信ループのgoroutineで一定時間フレームを読み込んでいない場合に ヘルスチェック処理を発火 ○ 受信ループが対応しているコネクション上でPingフレームという制御用のフレー ムを送る ○ Pingフレームに対し、一定時間以内に ■ ACKが返された場合はコネクションを「Healthy」とする ■ ACKが返されなかった場合はコネクションを「Unhealthy」とする ○ 「Unhealthy」となったコネクションはコネクションプールから除外され、そのコネク ション上のストリームを使っていた処理は「http2: client connection lost」エラー で中断される 44

Slide 45

Slide 45 text

net/httpの能動的なコネクション死活管理の仕組み ● ヘルスチェック処理は以下のようなステップで行われる ○ 受信ループのgoroutineで一定時間 フレームを読み込んでいない場合に ヘルスチェック処理を発火 ○ 受信ループが対応しているコネクション上でPingフレームという制御用のフレー http.HTTP2Config.SendPingTimeout ムを送る ○ Pingフレームに対し、一定時間 以内に ■ ACKが返された場合はコネクションを「Healthy」とする ■ ACKが返されなかった場合はコネクションを「Unhealthy」とする ○ 「Unhealthy」となったコネクションはコネクションプールから除外され、そのコネク ション上のストリームを使っていた処理は「http2: client connection lost」エラー で中断される http.HTTP2Config.PingTimeout 45

Slide 46

Slide 46 text

SendPingTimeoutとPingTimeoutでなんとかなりそう 元々の仮説 使⽤しているコネクションがネットワーク不良であるならば、「http2 client connection lost」 を出るようにしてから、APIリクエストを投げればうまくいくのではないか 導出したアプローチ ● リクエストを投げてから終わるまでの間に、ヘルスチェックが終了することを保証すれば良 い ● そうすると⼀回⽬のリクエストのタイムアウト前までに 「http2: client connection lost」エラーが出る(コネクションが死んでいる場合) ● その状態でリトライしたら、新しいコネクションを使ってくれるのでうまくいくようになる はず! 46

Slide 47

Slide 47 text

SendPingtimeoutとPingTimeoutを活用したリトライ 目指すべきリトライの姿 47

Slide 48

Slide 48 text

SendPingtimeoutとPingTimeoutを活用したリトライ 目指すべきリトライの姿 SendPingTimeout + PingTimeout < Contextのタイムアウト となるようにすればよい!! 48

Slide 49

Slide 49 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 49

Slide 50

Slide 50 text

最終的な設定値:SendPingTimeoutとPingTimeout func createHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() … (DialContextだったり IdleConnTimeoutだったりその他パラメータも調整 ) } transport.HTTP2 = &http.HTTP2Config{ SendPingTimeout: 5 * time.Second, PingTimeout: 3 * time.Second, } return &http.Client{ Transport: transport, } SendPingTimeout: 10秒から5秒に PingTimeout: 10秒から3秒に var client = createHTTPClient() 50

Slide 51

Slide 51 text

最終的なコード:API呼び出し部分 var client = createHTTPClient() // デフォルトからカスタマイズした http.Client // コインを付与する処理 func issueCoin(coinAmount uint) error { for i := range 2 { ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) ... res, err := client.Do(req) // コインを付与する APIを叩く if err != nil { ... continue } SendPingTimeout(5s) + PingTimeout(3s) }

Slide 52

Slide 52 text

結果 ● うまくいった! ○ 一回目のリクエストで失敗する際にエラーが 「context deadline exceeded」から 「http2: client connection lost」に変わった ○ 二回目のリクエスト(リトライ)が成功するように! ● 今回の障害に起因する補填作業の件数が0になった! ● ここまできてようやく最初の設定でうまくいかない理由もわかった 52

Slide 53

Slide 53 text

問題が起きていたコード(再掲) func createHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() … (DialContextだったり IdleConnTimeoutだったりその他パラメータも調整 ) } transport.HTTP2 = &http.HTTP2Config{ SendPingTimeout: 10 * time.Second, PingTimeout: 10 * time.Second, } return &http.Client{ Transport: transport, } var client = createHTTPClient() 53

Slide 54

Slide 54 text

問題が起きていたコード(再掲) func createHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() … (DialContextだったり IdleConnTimeoutだったりその他パラメータも調整 ) transport.HTTP2 = &http.HTTP2Config{ SendPingTimeout: 10 * time.Second, PingTimeout: 10 * time.Second, } return &http.Client{ Transport: transport, } SendPingTimeoutが PingTimeoutがともに10秒 var client = createHTTPClient() つまりContextのタイムアウト時間より⼤ } 54

Slide 55

Slide 55 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 55

Slide 56

Slide 56 text

ここまでを振り返って:パラメータ設計は難しい ● ● HTTPのタイムアウトパラメータをいい感じに設計するのは難しい(HTTP に限らないが) ○ 多少良くなかったとしても基本的に問題なく動いてしまう ○ 今見た通り相互作用するものがあり、単独で決められるとは限らない ○ うまくいっている他の会社のパラメータをそのまま真似すればいいと いうものでもなさそう ○ パラメータごとに負荷試験やカオスエンジニアリングをして、検証す るというのもハイカロリー 例えばk8sでもHTTP/2のタイムアウトパラメータ周りに関して、過去に 問題が起きていたりする ○ https://github.com/kubernetes/kubernetes/issues/121793 ○ ヘルスチェック時間間隔の兼ね合いで、 ノードが意図せずNotReady になってしまうケースがあった 56

Slide 57

Slide 57 text

「よい」タイムアウトを設定することは難しい だけれどせめて根拠を持った「納得できる」パラメータに することはできないだろうか 57

Slide 58

Slide 58 text

今回のパラメータ設定は納得できるものだった そこから一般化できないだろうか 58

Slide 59

Slide 59 text

パラメータが中でどのように使われているか理解する ● ● ● 各種タイムアウトパラメータが内部でどのように使われているかを理解す る 単にドキュメントを読むだけではなく、コードを読んで各種フローの中で どのように使われているのかを把握できるとよい ドキュメントだけではどのパラメータとどのパラメータが関係している か、あるいは独立かなどといった深い情報は理解できない 59

Slide 60

Slide 60 text

パラメータに関する不等式をたくさん見つける ● パラメータに関する不等式をたくさん見つける ○ ● 自分一人で考えても限界があるので、他所からネットワーク関係の問題が 起きた事例をいっぱい引っ張ってくる ○ ○ ● 今回のSendPingTimeout + PingTimeout < コンテキストのTimeoutもその一つ https://blog.cybozu.io/entry/2024/08/08/183405 https://developersblog.dmm.com/entry/2024/12/06/110000 計測によって下界を定める不等式を作り上げることもできる 60

Slide 61

Slide 61 text

パラメータに関する不等式をたくさん見つける ● 例 ○ ○ ○ ○ ○ ● 実測したPingのラウンドトリップ時間の99.99%tile + 実測したGoランタイムの待ち時間 の99.99%tile < PingTimeout Podの最大数 / SendPingTimeout < Ping flood判定をされる1秒あたりのPing回数の閾 値 クライアントのIdleConnTimeout < ロードバランサー側のIdleTimeoutの設定値 実測した値のnet.Dialの99.9%tile < net.Dialer.Timeout WriteByteTimeout < 16分(詳細はCybozuさんの事例参照) 各不等式の中からどれを採用するかは意思を持って決める 不等式一つ一つが、そのパラメータに関する意思決定と対応するようになる 61

Slide 62

Slide 62 text

固定しやすいパラメータを固定する ● ● パラメータによって固定しやすさに差がある 例えば ○ ○ ● リクエストのタイムアウトはアプリケーション要件なのでSLOなどにしたがって固定でき る(この辺は現場による) PingTimeoutやnet.Dialer.Timeoutなどは実測ベースで決められるので比較的固定しやす い 選択した不等式群を満たすように、固定しやすいパラメータを固定する 62

Slide 63

Slide 63 text

残りのパラメータを不等式に従い埋める ● 不等式を満たすように残りのパラメータを埋めていく ○ ○ ● ● SendPingTimeout + PingTimeout < contextのタイムアウトなら、PingTimeoutを3 秒、contextのタイムアウトを10秒で設定しているので SendPingTimeoutは0秒以上7秒 未満のどれか 各パラメータについて許容範囲を狭めた状態での設定ができる 以上のプロセスを踏むことで、パラメータの設定、あるいはパラメータの 事後検査が可能 「最適」ではないかもしれないが、複数の明示的な意思決定のフィルター を通したパラメータが得られる ○ 不等式一つ一つがパラメータを支える根拠となる 63

Slide 64

Slide 64 text

パラメータ・不等式の見直し ● ● ● パラメータは一度決めて終わりではない 新たな条件や問題が見つかったり、サービスのスケールなどにより今まで 設定していたパラメータでうまくいかなくなったりもでてくるかもしれな い 定期的な情報収集と見直しが必要 ○ まだうちではできていないので、やっていきます... 64

Slide 65

Slide 65 text

アジェンダ ● REALITYが遭遇したあるネットワーク上の障害 ● 効果がなかった対応策、そしてたてられた仮説 ● 障害解決のための知識と導出されたアプローチ ● 導出されたアプローチ、その結果 ● 納得できるタイムアウト設計 ● まとめ 65

Slide 66

Slide 66 text

まとめ ● ● ● タイムアウトの設定値によっては、リトライしても死んだコネクションを 使いまわしてしまうと言った問題が起こり得る HTTPクライアントの各種パラメータは最適を目指すことが難しくても、 意思をもって根拠のある値を設定することが大事 根拠のあるタイムアウト設計の考え方の一つを紹介した 今一度自分たちの HTTPClientのコードにどのようなパラメータ設定が されているか確認していただき、改めてそのパラメータにしている理由などに想 いを馳せるきっかけになれば幸いです。 66

Slide 67

Slide 67 text

ご清聴ありがとうございました 67

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Appendix ● https://github.com/egonelbre/gophers ● https://blog.cloudflare.com/ja-jp/the-complete-guide-to-golang-net-ht tp-timeouts/ ● https://blog.cybozu.io/entry/2024/08/08/183405 ● https://developersblog.dmm.com/entry/2024/12/06/110000 69