ECS Service Connect で gRPC を利用した際のリトライポリシーを確認してみる
ECS Service Connect を利用することで、特定条件のレスポンスを受け取った際に自動でリトライ処理を組み込むことができます。
この際、Envoy の管理をしなくてもサービスメッシュを組めるので非常に楽ですが、細かいカスタマイズはできません。
よりカスタマイズの幅が広い App Mesh の場合、gRPC 通信時に下記からリトライイベントを複数選択して設定することが可能です。
- unavailable (一時的にサービスが利用できない)
- internal (内部エラー)
- cancelled (処理がキャンセルされた)
- resource-exhausted (何らかのリソースが使い果たされている)
- deadline-exceeded (操作が完了する前にタイムアウトした)
ECS Service Connect を利用した際にこの辺りの設定値がどうなるかを確認してみました。
試してみる
下記構成で実際にリクエストを送信して試してみます。
ECS Service Connect に属する ECS タスクを 2 つ用意します。
入力に依ってステータスコードを変更できるサーバー用の ECS タスクを用意して、クライアント用の ECS タスクから grpcurl でリクエストを送信します。
サーバー側は grpc 公式のサンプルコードを活かして下記のように作りました。
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
type server struct{
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
// unavailableエラーのシミュレーション
if in.Name == "unavailable" {
return nil, status.Error(codes.Unavailable, "The service is currently unavailable.")
}
// cancelledエラーのシミュレーション
if in.Name == "cancelled" {
return nil, status.Error(codes.Canceled, "The operation was cancelled.")
}
// internalエラーのシミュレーション
if in.Name == "internal" {
return nil, status.Error(codes.Internal, "Internal errors occurred.")
}
// resource-exhaustedエラーのシミュレーション
if in.Name == "resourceExhausted" {
return nil, status.Error(codes.ResourceExhausted, "Some resource has been exhausted.")
}
// deadline-exceededエラーのシミュレーション
if in.Name == "deadlineExceeded" {
return nil, status.Error(codes.DeadlineExceeded, "The deadline expired before the operation could complete.")
}
// 入力値の検証
if in.Name == "" {
return nil, status.Error(codes.InvalidArgument, "名前が空です。有効な名前を指定してください")
}
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
reflection.Register(s)
log.Printf("serving on :50051\n")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
クライアント側でリクエストを送った際に、リトライが行われるかどうかはサーバー側のログから判断します。
unavailable(一時的にサービスが利用できない)の場合
エラーコードが unavailable となるようにリクエストを送信します。
$ grpcurl -v -d '{"name": "unavailable"}' -plaintext app.masukawa-test-ecs-namespace:50051 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
(empty)
Response trailers received:
content-type: application/grpc
date: Sun, 06 Apr 2025 06:03:36 GMT
server: envoy
x-envoy-upstream-service-time: 33
Sent 1 request and received 0 responses
ERROR:
Code: Unavailable
Message: The service is currently unavailable.
クライアント側でのリクエスト 1 回に対して、サーバー側では 3 回分のリクエストが記録されていました。
ECS Service Connect を利用した場合、リトライ回数は 2 回で固定です。
Service Connect は、プロキシを通過して失敗した接続を再試行するようにプロキシを設定し、2 回目の試行では、前の接続のホストを使用しません。これにより、Service Connect を介した各接続が 1 回限りの理由で失敗することがなくなります。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-connect-concepts-deploy.html#service-connect-concepts-proxy
unavailable の場合は、丁度 2 回分のリトライが行われていると考えられます。
internal(内部エラー)の場合
エラーコードが internal となるようにリクエストを送信します。
$ grpcurl -v -d '{"name": "internal"}' -plaintext app.masukawa-test-ecs-namespace:5
0051 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
(empty)
Response trailers received:
content-type: application/grpc
date: Sun, 06 Apr 2025 06:04:33 GMT
server: envoy
x-envoy-upstream-service-time: 1
Sent 1 request and received 0 responses
ERROR:
Code: Internal
Message: Internal errors occurred.
この際、クライアント側のリクエスト 1 回に対して、サーバー側では 1 回分のリクエストが記録されます。
internal の場合では、リトライされないようです。
cancelled(処理がキャンセルされた)の場合
エラーコードが cancelled となるようにリクエストを送信します。
$ grpcurl -v -d '{"name": "cancelled"}' -plaintext app.masukawa-test-ecs-namespace:
50051 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
(empty)
Response trailers received:
content-type: application/grpc
date: Sun, 06 Apr 2025 06:05:40 GMT
server: envoy
x-envoy-upstream-service-time: 0
Sent 1 request and received 0 responses
ERROR:
Code: Canceled
Message: The operation was cancelled.
cancelled の場合もリトライされないようです。
resource-exhausted(何らかのリソースが使い果たされている)の場合
エラーコードが resource-exhausted となるようにリクエストを送信します。
$ grpcurl -v -d '{"name": "resourceExhausted"}' -plaintext app.masukawa-test-ecs-n
amespace:50051 helloworld.Greeter.SayHello
Too few arguments.
Try 'grpcurl -help' for more details.
bash: amespace:50051: command not found
root@ip-10-0-11-140:/app# grpcurl -v -d '{"name": "resourceExhausted"}' -plaintext app.masukawa-test-ecs-namespace:50051 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
(empty)
Response trailers received:
content-type: application/grpc
date: Mon, 07 Apr 2025 02:02:02 GMT
server: envoy
x-envoy-upstream-service-time: 74
Sent 1 request and received 0 responses
ERROR:
Code: ResourceExhausted
Message: Some resource has been exhausted.
resource-exhausted の場合もリトライされないようです。
deadline-exceeded
エラーコードが deadline-exceeded となるようにリクエストを送信します。
$ grpcurl -v -d '{"name": "deadlineExceeded"}' -plaintext app.masukawa-test-ecs-na
mespace:50051 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
(empty)
Response trailers received:
content-type: application/grpc
date: Mon, 07 Apr 2025 02:06:36 GMT
server: envoy
x-envoy-upstream-service-time: 4
Sent 1 request and received 0 responses
ERROR:
Code: DeadlineExceeded
Message: The deadline expired before the operation could complete.
deadline-exceeded の場合もリトライされないようです。
まとめ
下記結果となりました。
ステータス | リトライ |
---|---|
unavailable | する(2 回) |
internal | しない |
cancelled | しない |
resource-exhausted | しない |
deadline-exceeded | しない |
ECS Service Connect で GrpcRetryPolicy の grpcRetryEvents
に当たる設定は App Mesh のデフォルトルート再試行ポリシーと同じ UNAVAILABLE
のみになっているようです。
App Mesh からの移行などを考えるとカスタマイズできると嬉しいですが、現時点では必要に応じてアプリケーション側で上手くリトライ処理を組み込むのが良いと思います。