Azure Load Balancer vs Application Gateway for Elasticsearch
08:44 08 Dec 2025

After deploying the DEMAND ES Azure Web App in PaaS, we started seeing:

  • Slow responses from ElasticSearch.

  • ElasticSearch node overload.

  • Search thread pool queue saturation.

  • 429 errors and intermittent downtime.

However, this issue never happened when the same workload ran in VMs. Previously this was an IIS hosted APIs in 4 different VMs. We migrated this API in Azure Web App.

Why This Never Happened in VMs (IaaS):

Now Recommendation APIs run in these four servers:

·       VW800ABC

·       VW800EFG

·       VW800HIJ

·       VW800LMN

Each VM has:

  • Its own dedicated outbound IP address.

  • Its own full range of network ports.

  • Direct connections to ElasticSearch.

  • No shared outbound networking.

Load Balancer Role (Azure ALB):

  • The Common Elastic URL points to an Azure Load Balancer in front of the three ElasticSearch nodes.

  • ALB distributes requests evenly across all three nodes based on the source IP and source port.

  • Since each VM has a unique IP and many available ports, requests naturally spread across all nodes.

Result: No single ElasticSearch node becomes overloaded, even during heavy traffic.
Outcome: IAAS-based apps never experienced slowness or 429 errors.

Why the Problem Appeared Only After Migration to PaaS (Web Apps):

PaaS Setup:
Web App (5 instances) ---> Common Elastic URL ---> Azure Load Balancer ---> ES Cluster (3 nodes)

alb does not support round robin, so one particular node went slow/down. Node hotspotting occurs only in PaaS because of shared IP + limited ports, not because of ElasticSearch or code issues.

When I check with chatgpt, it seems Azure Application Gateway is not the recommended approach.

public class ElasticSearchManager : IElasticSearchManager
    {
        private readonly IConfiguration _configuration;
        private readonly IKeyVaultHelper _keyVaultHelper;

        private readonly Lazy> _lazyClient;

        public ElasticSearchManager(IConfiguration configuration, IKeyVaultHelper keyVaultHelper)
        {
            _configuration = configuration;
            _keyVaultHelper = keyVaultHelper;

            _lazyClient = new Lazy>(InitializeClientAsync);
        }

        private async Task InitializeClientAsync()
        {
            var password = await _keyVaultHelper.GetSecretAsync(
                _configuration["Values:Password"],
                _configuration["Values:AzureKeyVaultURL"]);

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

            var pool = new SingleNodeConnectionPool(new Uri(_configuration["Values:ElasticURL"]));

            var settings = new ConnectionSettings(pool)
                .DefaultIndex(_configuration["Values:ResourceIndex"])
                .BasicAuthentication(_configuration["Values:SuperUser"], password)
                .EnableHttpCompression()
                .DisableAutomaticProxyDetection()
                .SniffOnStartup(false)
                .SniffOnConnectionFault(false)
                .SniffLifeSpan(TimeSpan.Zero)
                .DisableDirectStreaming(false)
                .RequestTimeout(TimeSpan.FromSeconds(60))
                .PingTimeout(TimeSpan.FromSeconds(3))
                .EnableTcpKeepAlive(TimeSpan.FromMinutes(2), TimeSpan.FromSeconds(20))
                .MaximumRetries(0)
                .MaxRetryTimeout(TimeSpan.FromSeconds(40))
                .ConnectionLimit(300)
                .ServerCertificateValidationCallback((o, cert, chain, errors) => true);

            return new ElasticClient(settings);
        }

        public async Task GetClientAsync()
        {
            return await _lazyClient.Value;
        }
    }

Nodes:
10.2xx.xxx.89
10.2xx.xxx.90
10.2xx.xxx.91

c# elasticsearch azure-application-gateway azure-webapps azure-load-balancer