ElasticBeanstalk worker環境 の 3つのタイムアウトの違いについて

## 概要

ElasticBeanstalk の worker environment には VisibilityTimeout, ErrorVisibilityTimeout, InactivityTimeout というよく似た名前の設定値があり，それぞれ何を意味するのかについて整理しました．

## 公式ドキュメントの説明

<cite>[公式ドキュメント][1]</cite>には以下のように記載されています:


パラメータ | 説明
---|---
VisibilityTimeout      | 「Amazon SQS キューからの着信メッセージが処理のためにロックされる時間」
ErrorVisibilityTimeout | 「明示的なエラーで処理が失敗した後、Elastic Beanstalk が Amazon SQS キューにメッセージを返すまでの経過時間」
InactivityTimeout      | 「アプリケーションへの既存の接続が応答するまでの待機時間」


## VisibilityTimeout

VisibilityTimeout とは SQS API のパラメータで, sqsd が GetMessage APIを呼び出すときに引数として設定します．設定値の意味は，メッセージが可視(visible) である時間の上限です．

message の可視性(visibility) というのは SQS の概念で，メッセージの排他処理を実現するための機構です．message が visible なときはすべての worker がメッセージを見ることができますが，ある worker が GetMessage すると，メッセージは一定の期間不可視(invisible) となり，他の worker からは取得できなくなります．このメッセージが不可視となる期間が VisibilityTimeout です．

メッセージが取得されてから VisibilityTimeout を経過したメッセージは，SQS マネージドサービスによって再び可視(visible) 状態となり，ほかの worker によって再処理されます.後述する ErrorVisibilityTimeout が設定されていない場合，VisibilityTimeout がメッセージの再処理間隔と同義になります．(詳しい説明は<cite>[公式ドキュメント][2]</cite>を参照)


## ErrorVisibilityTimeout

次に，ErrorVisibilityTimeout というのは VisibilityTimeout と似ていますが，こちらはエラー処理のときに使われる設定値です．デフォルトでは未設定です．

ErrorVisibilityTimeout が設定されている場合，アプリケーションが明示的にエラーを返した場合に， sqsd は SQS の API ChangeMessageVisibility を呼び出し，メッセージの可視期間を ErrorVisibilityTimeout に設定された値に変更します．

すなわち，明示的なエラーとなった場合に，あらかじめ設定された VisibilityTimeout の値とは独立して再処理間隔を設定することができます．VisibilityTimeout は worker の処理時間よりも十分長い値が設定されているので(デフォルト値は 300 sec)，一度エラーを返したメッセージが次に再処理されるまでに長く待つ必要があります．一方で， ErrorVisibilityTimeout が設定されていれば，最初に設定した VisibilityTimeout の経過を待たずに，エラーが起きた時刻を起点として再処理間隔を設定することができます．

また，注意しなければならないのは，アプリケーション側のミドルウェアがタイムアウトした場合です．通常，sqsd とアプリケーションの間には nginx や WSGI のようなミドルウェアが存在する構成にすると思いますが，例えば nginx でタイムアウトした場合，sqsd は nginx のステータスコードをもとにエラーとして処理しますが，アプリケーションは処理を継続しています．したがって，ErrorVisibilityTimeout が短いと，アプリケーションが処理中にも関わらず，アプリケーションを2重に起動してしまう可能性が高くなります．（通常はアプリケーションを冪等に設計するはずなので，実際には2重起動は問題にならないかもしれません.）


## InactivityTimeout

最後に， InactivityTimeout ですが，他の2つが SQS API を呼び出すときのパラメータなのに対し，こちらは SQS の worker daemon (sqsd) に閉じたパラメータです．

sqsd が既に確立されたアプリ側とのコネクションに対して POST メッセージを送ってから，アプリケーションが応答を返すまでの待ち時間です．（同様に，アプリ側とのコネクションを確立するときの待ち時間として ConnectionTimeout があります）


## シーケンス図による説明

以上が言葉による説明ですが，具体的なケースで実際に sqsd がどのように振る舞うかを理解すると，
よりいっそう理解が深まると思うので，以下では 4つのケースについてシーケンス図で説明します．

1. アプリケーションが正常に応答する場合
1. アプリケーションがエラーレスポンスを返す場合(ErrorVisibilityTimeout が設定されていない場合)
1. アプリケーションがエラーレスポンスを返す場合(ErrorVisibilityTimeout が設定されている場合)
1. アプリケーションが InactivityTimeout を超えても応答しなかった場合

### 1. アプリケーションが正常に応答する場合

![sqsd001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239145/8fd160b7-2716-88f6-83a2-2dfb196fedae.png)

まず，アプリケーションが正常な応答を直ちに（タイムアウトよりも十分短い時間で）返す場合です．

sqsd は SQS のキューをポーリングし，メッセージがキューに入ると，GetMessage API を呼び出してメッセージを取得します．取得されたメッセージはSQS上では不可視(invisible)状態，すなわち，他の daemon プロセスからは処理できない状態となります．

次に，sqsd はこのメッセージをパースして HTTP リクエストに変換し，アプリ側に POST リクエストとして送信します．アプリ側が正常なレスポンス(ステータス200)を返した場合は，sqsd は処理が成功したとみなし，DeleteMessage API を呼び出して SQS 上からメッセージを削除します．

以上が最も基本的なシーケンスです．


### 2. アプリケーションがエラーレスポンスを返す場合(ErrorVisibilityTimeout が設定されていない場合)

![sqsd002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239145/3b24f3c6-8f82-319d-3a97-240b44ae9127.png)

次に，アプリケーションが明示的にエラー応答を返す場合で，かつ，ErrorVisibilityTimeout が設定されていない場合について説明します．

sqsd がアプリケーションに POST リクエストを投げるまでは，1つめのシーケンスと同じです．アプリケーションが明示的なエラー(5xx など)を返した場合，sqsd は処理に失敗したとみなしますが，ErrorVisibilityTimeout が設定されていないため，可視性の変更はせず，VisibilityTimeout が経過するのを待ちます．VisibilityTimeout が経過すると，メッセージはSQS上から再び可視(visible)状態になるため，worker プロセスによって再処理されます．

### 3. アプリケーションがエラーレスポンスを返す場合(ErrorVisibilityTimeout が設定されている場合)

![sqsd003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239145/ab966de4-547e-aaf6-6a76-aaace7997be3.png)

以下は，アプリケーションが明示的なエラー応答と返す場合で，かつ，ErrorVisibilityTimeout が設定されている場合のシーケンスです．

アプリケーションが明示的なエラーレスポンスを返すまでは2つめのシーケンスと同じです．sqsd は処理に失敗したとみなし，ChangeMessageVisibility API を呼び出して，ErrorVisibilityTimeout で設定した値でメッセージの可視性タイムアウト(VisibilityTimeout) を再設定します．ChangeMessageVisibility API が呼び出されてから ErrorVisibilityTimeout だけ経過したら，メッセージは再び可視(visible) 状態となり，worker プロセスによって再処理されます.

### 4. アプリケーションが InactivityTimeout を超えても応答しなかった場合

![sqsd004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239145/730f5acb-d8a5-3175-2414-abe3bb715e24.png)

最後に，アプリケーションが InactivityTimeout を超えて応答を返さなかった場合のシーケンスを説明します．

sqsd がアプリケーションに POST リクエストを送るまでは1-3つめのシーケンスと同じですが，アプリケーションが応答を返すまでに， InactivityTimeout 以上経過した場合，sqsd はメッセージの処理に失敗したとみなします．その後の処理は，ErrorVisibilityTimeout が設定されているかどうかによって決まり，設定されていない場合は2つめ，設定されている場合は3つめのシーケンスにしたがって処理されます．


## 終わりに

説明は以上ですが，最後に，sqsd のソースコードの場所を共有しておきます．ubuntu 16.04 の場合，EC2インスタンスの以下のディレクトリにあります．

* `/opt/elasticbeanstalk/lib/ruby/lib/ruby/gems/2.2.0/gems/aws-sqsd-2.3`

ruby の gem で提供されているようですが， public なリポジトリではないようです．

AWSのドキュメントを読みながらだと，細かい仕様が理解しずらかったのですが，ソースコードを見ながらだとすんなり理解することができました．

## 参考

- [Elastic Beanstalk worker environments][1]
- [Amazon SQS visibility timeout][2]


<!-- Reference Links -->
[1]: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html
[2]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html