SNS APIでフォロワー一覧のような大量データを取得するとき、ページング処理をそのまま書くと途中の1回の失敗で全体が落ちます。実際に InvokeTimeoutError で止まり、しかも例外メッセージが空でログから原因が追えなかった話と、その対策を2つ書きます。
ページングは「10回以上のリクエスト」になる
フォロワー一覧のような大量データは、一度のリクエストでは返ってきません。ページングで少しずつ取得していく形になります。
問題はリクエスト回数です。フォロワーが1,000人を超えると、それだけで10回以上のリクエストになります。そしてその10回以上のうち1回でも失敗すると、処理全体が落ちます。9回成功していても関係ありません。
1回のAPI呼び出しなら「たまに失敗する」で済みます。しかしページングは直列に何回も呼ぶので、失敗する確率が回数分だけ積み上がっていきます。ここが見落としやすいところでした。
ページング呼び出しに再試行を入れずに運用していたところ、InvokeTimeoutError で処理が止まりました。取得できたところまでで終わるのではなく、処理全体が落ちました。
もっと困ったのは「ログを見ても分からない」
止まったこと自体は、まだいいのです。困ったのはその後でした。
ログに記録していたのは str(e) だけでした。ところがこの InvokeTimeoutError は str(e) が空文字だったのです。つまりログには、エラーが起きたという事実だけが残り、中身が何も残っていませんでした。
何が起きたのか、どこで止まったのか、ログからは分かりませんでした。例外を捕まえて記録していたのに、実質的に何も記録できていなかったことになります。
str(e) だけをログに書く実装は、中身が空の例外に当たった瞬間に完全に手がかりを失います。「例外は捕まえてログに出している」だけでは安全ではありません。
対策1:ページング呼び出しを再試行でくるむ
まずページング呼び出し自体を再試行で包みました。設定はこうしています。
待ち時間を10秒・20秒・30秒と伸ばしていき、4回まで試す形です。すぐに再試行して同じように失敗するより、間隔を置いたほうが通ることがあるためです。
ポイントは、再試行を入れる場所をページング呼び出しの単位にしたことです。処理全体をやり直すのではなく、失敗したそのページの取得だけを再試行します。10回以上あるリクエストのうち1回が失敗しただけなのに、全部を捨てるのは無駄が大きすぎます。
リクエストが10回以上に増える処理では、再試行はオプションではなく前提です。1回だけの呼び出しと同じ書き方をしていると、いつか必ず途中で落ちます。
対策2:ログに型名とトレースバックの最後の1行を入れる
2つ目は、ログの書き方を変えたことです。str(e) だけでは足りないと分かったので、次の2つを必ず入れるようにしました。
- 例外の型名(メッセージが空でも型名は残ります)
- トレースバックの最後の1行(どこで落ちたかが分かります)
この2つがあれば、メッセージが空文字の例外でも「何が起きたか」「どこで起きたか」の最低線は残ります。実際に InvokeTimeoutError という型名が分かっていれば、あの時点でもう少し早く見当がついたはずでした。
- 例外の型名をログに残す
- トレースバックの最後の1行をログに残す
- ページング呼び出しを再試行で包む
str(e)だけをログに書く- ページング全体を再試行なしで一発勝負にする
分かっていないこと
正直に書いておくと、なぜ InvokeTimeoutError が発生したのか、根本原因は分かっていません。str(e) が空文字だったため当時のログから原因を特定できず、後から遡ることもできませんでした。
今回やったのは、原因の解明ではなく「落ちても止まらないようにする」と「次に起きたら分かるようにする」の2つです。再試行4回で必ず通るという保証もありません。4回とも失敗すれば、やはり処理は落ちます。
再試行を入れたことで、1回の失敗が処理全体を落とすという状態からは抜けられました。ログに型名とトレースバックを入れたことで、次に何かあったときに「何も分からない」状態は避けられます。
同じことをやる人への順番
- 取得件数からリクエスト回数を見積もる(1,000人超えなら10回以上)
- ページング呼び出しの単位を再試行で包む(待ち時間を伸ばしながら4回まで)
- 例外ログに型名とトレースバックの最後の1行を入れる
str(e)だけに頼っている箇所を探して直す
特に4番目は、動いているうちは絶対に気づきません。中身が空の例外に当たって初めて分かります。
- フォロワー一覧のような大量データはページングで取得するため、1,000人超で10回以上のリクエストになる
- 途中で1回失敗すると処理全体が落ちる。実際に
InvokeTimeoutErrorで止まった - その例外の
str(e)は空文字で、ログを見ても原因が分からなかった - 対策1:ページング呼び出しを再試行で包む(10秒・20秒・30秒と待って4回まで)
- 対策2:ログに例外の型名とトレースバックの最後の1行を必ず入れる
str(e)だけを記録すると、中身が空の例外で完全に手がかりを失う- 根本原因は今も分かっていない。やったのは「止まらないようにする」と「次は分かるようにする」だけ


