AWS LambdaでAWSサービスのロングポーリングAPIを呼ぶときには使用するSDKの設定を確認するといい

既定の設定ではロングポーリングAPIでクライアントがタイムアウトしたうえに自動で再試行する。1回のAPI呼び出しでタイムアウトまで待機してさらに間隔を延しながら5回リトライするから数分に渡って制御が返ってこない状態になる。AWS Lambdaなどでは課金がだだ漏れになってしまう。

具体的には、boto3(Python)でAWS Step Functions(SFN)のGetActivityTaskを呼び出したら遭遇した。Step FunctionsのGetActivityTaskは呼び出すと最大で60秒間サーバーが接続を保持して待機(ロングポーリング)する。このAPIはサーバーの待機時間を指定できない。

各言語向けのAWS SDKは自動再試行を実装している。AWS SDK for Pythonのboto3にもbotocoreパッケージで自動再試行が実装されている。なお、AWS CLIもbotocoreパッケージを使用しているのでbotocoreの設定ファイルはboto3と共有する。

再試行の条件と回数、間隔は設定ファイルとしてbotocoreパッケージに含まれる。パッケージレベルの設定ファイルはbotocore/model/retry.json。パッケージレベルの設定はAWS_SDK_PATHにretry.jsonを置いて上書きすることができる。再試行設定はサービス、APIごとに設定できる。S3などには用途に合せた設定があらかじめ用意されている。Step Functionsについてはいまのところ用意されていないので既定の設定が適用される。既定の再試行設定はエクスポネンシャルバックオフ(指数関数的後退)アルゴリズム(失敗するごとに再試行の間隔が長くなる)で最大5回。

qiita.com

そもそも再試行が行なわれるのは、リクエストがタイムアウトで終了するから。ロングポーリングでサーバーが接続を保持したまま待機している間にクライアントの接続がタイムアウトで終了してReadTimeout例外が発生する。boto3の既定の設定は、ReadTimeout例外で再試行する。

クライアント接続のタイムアウト設定は2つ。connect_timeoutとread_timeout。connect_timeoutはサーバーに接続するまでの時間、read_timeoutは接続してから応答まで(応答完了までか?)の時間。どちらも秒単位で設定する。ロングポーリングAPIを呼び出すにはread_timeoutがサーバーの待機時間より大きくしなければならない。read_timeoutの既定は60。

qiita.com

Step FunctionsのGetActivityTaskのドキュメントによるとクライアントの接続タイムアウト設定は、サーバーの待機時間60秒に5秒以上足した65秒以上を設定することとある。

ちゃんと設定するなら、GetActiviTaskを呼ぶときはread_timeoutを65秒にして、ReadTimeoutの場合はリトライをしない、とするべきだろう。