Files
ResourcePool.Docs/GRPC_CLIENT_RESET_DEBUG.md

219 lines
5.9 KiB
Markdown

# gRPC "Client Reset Request Stream" Debug Guide
## ❌ Lỗi
```
System.IO.IOException: The client reset the request stream.
```
## 🔍 Nguyên nhân
Lỗi này xảy ra khi **gRPC client đóng/hủy connection trước khi server xử lý xong**.
### Các nguyên nhân phổ biến:
#### 1. **Client Timeout** ⏱️ (Phổ biến nhất)
- Client có timeout ngắn hơn thời gian server xử lý
- Ví dụ: Client timeout 5s, nhưng server cần 10s để xử lý
**Cách kiểm tra:**
```bash
# Xem logs với thời gian xử lý
grep "GetProxy completed" logs.txt
grep "GetProxy cancelled" logs.txt
```
**Giải pháp:**
- Tăng timeout ở client
- Tối ưu performance ở server
#### 2. **Server xử lý chậm** 🐌
Các nguyên nhân server chậm:
- Database query chậm
- Redis cache chậm
- ClickHouse logging chậm (đã fix)
- Logic phức tạp trong `RequestResource()`
**Cách kiểm tra:**
```bash
# Xem thời gian xử lý trung bình
grep "GetProxy completed" logs.txt | grep -oP '\d+ms' | sort -n
```
**Giải pháp:**
- Profile code để tìm bottleneck
- Add index vào database
- Optimize queries
- Cache kết quả
#### 3. **Network Issues** 🌐
- Connection bị drop
- Load balancer timeout
- Firewall rules
**Cách kiểm tra:**
```bash
# Kiểm tra network errors
kubectl logs <pod-name> | grep -i "connection\|network\|timeout"
```
#### 4. **Client Implementation Issues** 🐛
- Client không đợi response đủ lâu
- Client có retry logic và cancel request cũ
- Client crash/restart
## 📊 Monitoring với logs mới
Sau khi deploy code mới, bạn sẽ thấy logs như:
### Successful request:
```
[abc12345] GetProxy started - AccessToken: token123
[abc12345] GetProxy completed in 150ms - ProxyId: 456
```
### Cancelled request:
```
[xyz67890] GetProxy started - AccessToken: token456
[xyz67890] GetProxy cancelled by client after 5000ms - AccessToken: token456
```
### Failed request:
```
[def11111] GetProxy started - AccessToken: token789
[def11111] GetProxy failed after 2000ms - AccessToken: token789
```
## 🔧 Cách debug
### Bước 1: Xác định thời gian xử lý
```bash
# Xem requests bị cancel và thời gian của chúng
kubectl logs <pod-name> | grep "cancelled by client"
# Nếu thấy nhiều requests bị cancel sau ~5s, 10s, 30s
# => Client timeout ở các mốc đó
```
### Bước 2: So sánh với requests thành công
```bash
# Xem thời gian xử lý của requests thành công
kubectl logs <pod-name> | grep "completed in" | grep -oP '\d+ms'
# Nếu hầu hết < 1000ms nhưng có vài cái > 5000ms
# => Có vấn đề performance không ổn định
```
### Bước 3: Kiểm tra client timeout
```csharp
// Ở phía client, kiểm tra timeout config
var channel = GrpcChannel.ForAddress("http://server:5002", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
}
});
// Hoặc set deadline cho mỗi call
var response = await client.GetProxyAsync(request, deadline: DateTime.UtcNow.AddSeconds(30));
```
## ✅ Giải pháp đã áp dụng
### 1. Cấu hình Kestrel HTTP/2 (Program.cs)
```csharp
// HTTP/2 limits để tránh connection faults
options.Limits.Http2.MaxStreamsPerConnection = 100;
options.Limits.Http2.InitialConnectionWindowSize = 131072; // 128KB
options.Limits.Http2.InitialStreamWindowSize = 98304; // 96KB
// Tăng timeout
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
// Disable rate limits cho gRPC
options.Limits.MinRequestBodyDataRate = null;
options.Limits.MinResponseDataRate = null;
```
**Lợi ích:**
- Tránh "HTTP/2 connection faulted" errors
- Cho phép nhiều concurrent streams hơn
- Tăng window size để handle large messages
- Không bị timeout khi network chậm
### 2. Cấu hình gRPC (Startup.cs)
```csharp
services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = 10 * 1024 * 1024; // 10MB
options.MaxSendMessageSize = 10 * 1024 * 1024; // 10MB
options.EnableDetailedErrors = true;
});
```
**Lợi ích:**
- Tránh lỗi message size limit
- Detailed errors giúp debug dễ hơn
### 3. Thêm logging chi tiết (ProxyService.cs)
- Track thời gian xử lý từng request với Stopwatch
- Log khi client cancel request
- Request ID để trace từng request
- Catch `OperationCanceledException` riêng
### 4. Error handling
- Log warning thay vì error cho client cancellation
- Không crash service khi client disconnect
- Continue service khi ClickHouse quá tải
## 📈 Recommended Actions
### Ngay lập tức:
1. ✅ Deploy code mới với logging
2. ✅ Monitor logs để xác định pattern
3. ✅ Xác định thời gian xử lý trung bình
### Nếu thấy nhiều cancellations:
#### Option A: Tăng client timeout
```csharp
// Client code
var deadline = DateTime.UtcNow.AddSeconds(30); // Tăng từ 5s lên 30s
var response = await client.GetProxyAsync(request, deadline: deadline);
```
#### Option B: Tối ưu server performance
1. Profile `RequestResource()` method
2. Add caching cho hot data
3. Optimize database queries
4. Consider async logging (fire-and-forget)
#### Option C: Implement timeout gracefully
```csharp
// Server code - return cached/fallback data nếu quá lâu
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
return await GetFreshData(cts.Token);
}
catch (OperationCanceledException)
{
// Return cached data as fallback
return await GetCachedData();
}
```
## 🎯 Expected Metrics
Sau khi fix:
- ✅ Cancellation rate < 1%
- ✅ P95 response time < 1000ms
- ✅ P99 response time < 3000ms
- ✅ No service crashes due to client disconnects