0

SignalR hub running as an EKS pod handles 700 VUs via ALB but fails at 720+, yet successfully handles 1000 VUs via kubectl port-forward with 85% free CPU and 43% free memory. This confirms the application is healthy. I am new to AWS , I wanted to understand what might be the root cause this issue. I am performing a load test using k6 scripts.

Also, I am connected to Redis cache (AWS Elasticache) instance c6 large as backpane for scalability.

I am trying to understand is it server side resource exhaustion or ALB node scaling issue?

Error Logs-

    INFO[0524] [VU 728] Starting sustained connection test - Target: https://xxxxxxxxxxxxxxxxxxx/hubs/notification source=console
INFO[0524] [VU 728] Will send messages every 15000ms for 20 minutes source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
INFO[0524] [VU 726] WebSocket opened source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
WARN[0524] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0524] [VU 728] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0524] [VU 728] Negotiation failed source=console
INFO[0524] [VU 726] Handshake complete in 1067ms source=console
INFO[0525] [VU 729] Starting sustained connection test - Target: https://xxxxxxxxxxxxxxxxxxx/hubs/notification source=console
INFO[0525] [VU 729] Will send messages every 15000ms for 20 minutes source=console
WARN[0525] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0525] [VU 729] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0525] [VU 729] Negotiation failed source=console
INFO[0525] [VU 727] WebSocket opened source=console
WARN[0525] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0525] [VU 729] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0525] [VU 729] Negotiation failed source=console
WARN[0525] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"
ERRO[0525] [VU 729] NEGOTIATION_HTTP_ERROR: HTTP error during negotiation source=console
ERRO[0525] [VU 729] Negotiation failed source=console
WARN[0525] Request Failed error="Post "https://xxxxxxxxxxxxxxxxxxx/hubs/notification/negotiate\": EOF"

SignalR configuration:-

public static IServiceCollection AddSignalRServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment)
{
    var signalRBuilder = services.AddSignalR(options =>
    {
                    
        options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
           
        options.KeepAliveInterval = TimeSpan.FromSeconds(
            configuration.GetValue<int>("SignalR_KeepAliveSeconds", 30));

        options.HandshakeTimeout = TimeSpan.FromSeconds(
            configuration.GetValue<int>("SignalR_HandshakeTimeoutSeconds", 15));

        options.MaximumParallelInvocationsPerClient =
            configuration.GetValue<int>("SignalR_MaxParallelInvocations", 1);

        options.MaximumReceiveMessageSize =
            configuration.GetValue<long?>("SignalR_MaxMessageSize", 32 * 1024);

        options.StreamBufferCapacity =
            configuration.GetValue<int>("SignalR_StreamBufferCapacity", 10);


        // Adjusted settings for Debugging and performance tuning
        options.MaximumReceiveMessageSize = 32768; 
        options.StreamBufferCapacity = 5; // Reduced from 10

        // Timeouts - balance connection count vs responsiveness
        options.KeepAliveInterval = TimeSpan.FromSeconds(20); 
       
        options.HandshakeTimeout = TimeSpan.FromSeconds(20); 

        // Limit parallel invocations to reduce CPU load
        options.MaximumParallelInvocationsPerClient = 1;

        options.EnableDetailedErrors = false; // Disable in production




    });

    signalRBuilder.AddMessagePackProtocol();

    services.AddRedisBackplane(signalRBuilder, configuration, environment);

    return services;
}
 private static void AddRedisBackplane(
        this IServiceCollection services,
        ISignalRServerBuilder signalRBuilder,
        IConfiguration configuration,
        IWebHostEnvironment environment)
    {
             

        var redisConfig = configuration.GetRedisConfiguration();
        var redisHost = redisConfig.Host;
        var redisPort = redisConfig.Port;
        var redisPassword = redisConfig.Password;

        if (string.IsNullOrEmpty(redisHost))
        {
            Console.WriteLine("Redis backplane is enabled but Redis host is not configured.");
            return;
        }
        var useSSL = configuration.GetValue<bool>("RedisConfig_UseSsl", true);
        var redisConnectionString = BuildRedisConnectionString(redisHost, redisPort, redisPassword, useSSL);

        signalRBuilder.AddStackExchangeRedis(redisConnectionString, options =>
        {
            options.Configuration.ChannelPrefix = StackExchange.Redis.RedisChannel.Literal("SignalR");
            options.Configuration.AbortOnConnectFail = false;
            options.Configuration.ConnectTimeout = 15000;
            options.Configuration.SyncTimeout = 30000;
            options.Configuration.AsyncTimeout = 30000;               
            options.Configuration.KeepAlive = 60;
            options.Configuration.ConnectRetry = 3;
            options.Configuration.ReconnectRetryPolicy = new StackExchange.Redis.ExponentialRetry(5000);
            
            options.Configuration.DefaultDatabase = 0;
            options.Configuration.Proxy = StackExchange.Redis.Proxy.None;
            
           
        });

            }

Kestrel Configuration:-

 ThreadPool.SetMinThreads(200, 200);
        Console.WriteLine("[ThreadPool] Min threads set to 200 for high concurrency (SignalR 1000+ connections)");


        builder.WebHost.ConfigureKestrel(options => 
        {
          
            options.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(280);
            
            options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(60);
            options.Limits.Http2.KeepAlivePingDelay = TimeSpan.FromSeconds(10);

            options.Limits.MaxConcurrentConnections = 5100;  
            options.Limits.MaxConcurrentUpgradedConnections = 5100;
          
            options.Limits.MinRequestBodyDataRate = null;
            options.Limits.MinResponseDataRate = null;
           
            options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
            options.Limits.MinRequestBodyDataRate = null;
            options.Limits.MinResponseDataRate = null;

            options.Limits.MaxRequestBodySize = 10 * 1024;
            options.Limits.MaxRequestBufferSize = 1024 * 1024; 
            options.Limits.MaxRequestHeadersTotalSize = 32 * 1024; 

  
        });

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.