BYOC Edge 503 errors during high concurrency test

Does anyone know why POST /api/v2/edge/instances returns 503 Service Unavailable when pushing 200 concurrent WebSocket connections through JMeter 5.6.2? I am testing the BYOC edge capacity from the Singapore datacenter and it works fine at 50 calls but fails consistently at 200. My config is pretty basic just trying to validate throughput limits.

503s usually mean the edge proxy is choking on handshake overhead, not throughput. check your JMeter thread group settings. you’re likely flooding the connection pool before the previous batch closes. switch to Concurrent Thread Group with a Ramp-Up delay. also, ensure you’re reusing TCP connections. default HTTP sampler doesn’t.

503s on BYOC edges often stem from connection churn, not just volume. i’ve seen similar issues when migrating Five9 IVR logic to GC Data Actions where the client drops connections too aggressively. try forcing keep-alive and limiting concurrent threads per IP to avoid the proxy choking.

Connection: keep-alive

check your JMeter HTTP Request Defaults settings.

the keep-alive header is good but 503 usually means the edge node itself is rejecting the load because it thinks it’s a DDoS or just overloaded on handshake processing. in our java integration we had similar issues when testing kafka webhook ingestion at high concurrency. the api layer starts dropping packets if the rate exceeds the allowed burst limit for that specific edge id.

you need to look at the Retry-After header in the 503 response. it tells you exactly how long to wait. if you ignore it and keep hammering, the edge will block your ip for longer.

here is a simple java snippet using the GC SDK to handle this gracefully. it checks for 503 and sleeps for the duration specified in the header. this prevents the “thundering herd” problem that causes the 503s in the first place.

try {
 HttpResponse response = client.post("/api/v2/edge/instances", payload);
 if (response.getStatusCode() == 503) {
 String retryAfter = response.getHeader("Retry-After");
 if (retryAfter != null) {
 long seconds = Long.parseLong(retryAfter);
 System.out.println("Hit 503. Waiting " + seconds + "s");
 Thread.sleep(seconds * 1000);
 // retry logic here
 }
 }
} ch (Exception e) {
 e.printStackTrace();
}

also check your edge configuration in the admin portal. there is a setting for “Max Concurrent Sessions” per edge node. if you are pushing 200 concurrent connections and the node is sized for 100, it will 503 immediately. we had to increase the session limit in the BYOC config before we could pass our load tests.

the jmeter ramp up is definitely part of it but the backend limit is the real bottleneck. try lowering the concurrency to 50 and see if the 503s stop. if they do, it’s a capacity issue not a handshake issue.

Cause:
the 503 isn’t just handshake overhead. it’s the edge proxy rejecting bursts that exceed the configured rate limit for that specific edge ID. jMeter’s default HTTP sampler opens and closes TCP connections per request by default. this creates massive churn. the edge sees 200 new sockets in milliseconds. it flags it as a potential DDoS or just simple overload. you’re hitting the burst limit.

Solution:
force connection reuse. set Connection: keep-alive in your headers. this is non-negotiable for high concurrency tests. also, switch to a Concurrent Thread Group with a ramp-up delay. don’t fire all 200 threads at once. let them stagger. here’s the exact JMeter config tweak that fixed our load tests.

<!-- JMeter HTTP Request Defaults -->
<elementProp name="HTTPsampler.connect-timeout" elementType="String">
 <stringProp name="TestElement.setProperty">10000</stringProp>
</elementProp>
<elementProp name="HTTPsampler.response-timeout" elementType="String">
 <stringProp name="TestElement.setProperty">30000</stringProp>
</elementProp>
<elementProp name="HTTPsampler.parameters" elementType="Arguments">
 <collectionProp name="Arguments.arguments">
 <elementProp name="Connection" elementType="HTTPArgument">
 <boolProp name="HTTPArgument.useEquals">false</boolProp>
 <stringProp name="Argument.value">keep-alive</stringProp>
 <stringProp name="Argument.metadata">=</stringProp>
 </elementProp>
 </collectionProp>
</elementProp>

you also need to check the Retry-After header in the 503 response. it tells you exactly how long to wait. ignore it and you’ll get banned. we wrapped our client in a simple retry loop.

async function fetchWithRetry(url, options, retries = 3) {
 try {
 const response = await fetch(url, options);
 if (response.status === 503) {
 const retryAfter = response.headers.get('Retry-After');
 if (retryAfter) {
 console.log(`Waiting ${retryAfter} seconds...`);
 await new Promise(r => setTimeout(r, retryAfter * 1000));
 return fetchWithRetry(url, options, retries - 1);
 }
 }
 return response;
 } ch (error) {
 if (retries > 0) {
 return fetchWithRetry(url, options, retries - 1);
 }
 throw error;
 }
}

this keeps the connection pool stable. the edge stops choking. throughput limits are usually higher than you think once you stop burning sockets.