DEV Community

Akshay Kumar
Akshay Kumar

Posted on

Optimizing .NET 8 API Consumption at Scale: A Technical Deep Dive into Concurrency, Batching, and Resilient Retry Mechanisms

When architecting systems that rely on external APIs, it is paramount to anticipate and mitigate potential scaling bottlenecks, such as rate limiting.

This article details the technical strategies employed using .NET 8 to successfully scale the consumption of a third-party API from an initial volume of 500+ requests to over 10,000, significantly reducing processing time and error rates.

429 Error in API

The initial implementation, while adequate for low volumes, failed to scale, resulting in 30-minute processing times for 10,000 requests and a 50% failure rate primarily due to 429 Too Many Requests errors. 
The core constraint was the third-party API's rate limit of two requests per minute per endpoint, compounded by the absence of Retry-After headers in the error responses.
My mandate was to enhance the system's performance and resilience under these constraints.

The Technical Challenge: Rate Limit Evasion Without Retry-After

The process involved iterating over 10,000 unique URLs, each requiring an individual API call www.example.com/get?id=123 
The strict rate limit and the non-informative 429 responses lacking a programmatic guide for delay mandated a proactive and deterministic traffic shaping solution
Solution: A Multi-Layered Strategy for Traffic Shaping
Through performance analysis, it was determined that the API could sustain a higher burst rate than the official limit before returning a 429, but sustained high throughput was the issue. This led to a divide and conquer methodology implemented via three core optimizations:
Batching, Dynamic Delay Calculation, and Controlled Concurrency with Resilience.

1. Batching and Granular Request Segmentation

Instead of processing all requests sequentially or in a naive concurrent flood, a structured batching strategy was introduced:
Batch Size: The 10,000+ requests were divided into sequential meta-batches of 1,000 URLs.
Rate-Limited Window: Within each meta-batch, a maximum of 600 unique requests was processed per minute to remain safely below the API's effective rate limit threshold.
Inter-Batch Delay: A minimum delay of 7–10 seconds was enforced between the processing of consecutive meta-batches to reset the API's internal rate-limiting counter and prevent cumulative rate-limit exhaustion.

2. Dynamic Batch Delay Optimization

To maximize throughput without violating the rate limit, the delay between batches was made dynamic:
Delay Calculation: The delay for the subsequent batch was calculated by subtracting the preceding batch's actual processing time from the target 10-second window.

Formula: $Delay_{next} = 10 \text{ seconds} - ProcessingTime_{previous}$.
Rate-Limit Guardrail: If resilience policies (retries, circuit breaker activation) caused the previous batch's effective processing time to exceed 10 seconds, the subsequent batch would still wait the full 10-second interval to ensure strict adherence to the minute-based rate limit window.

3. Concurrency Control and Resilience Implementation in .NET 8 Concurrency Management:

The .NET 8 SemaphoreSlim primitive was utilized to impose a global limit on concurrent requests, set at 50. This ensured that the client system did not overwhelm the external API and managed memory and thread consumption efficiently.

  1. Asynchronous Processing and Queuing:
    Successfully processed requests were immediately enqueued (e.g., using a message broker or in-memory queue) to decouple the API consumption process from subsequent database updates, enhancing overall system responsiveness.

  2. Resilience Policies with Microsoft.Extensions.Resilience:
    The solution adopted a robust resilience strategy to handle transient and persistent failures:
    Retry Policy (Exponential Backoff): In the absence of Retry-After headers, an exponential backoff strategy was implemented, starting at 2 seconds and doubling up to a maximum of 32 seconds per attempt. A hard limit of five retries per request was set to prevent infinite blocking.

  3. Circuit Breaker Policy:
    Upon detecting a configurable number of consecutive 429 Too Many Requests responses, the circuit was immediately "opened" for a period of one minute. This protected the external API from overload and allowed it time to recover, dramatically reducing the systemic failure rate.

Performance Gains

The combined implementation of batching, dynamic delays, and resilient retry logic yielded substantial performance improvements:
This performance increase was directly attributable to minimizing rate limit violations through controlled request distribution and the efficient handling of inevitable transient failures via the circuit breaker and tailored exponential backoff.
Conclusion and Future Scalability
By strategically combining .NET 8's concurrency primitives (SemaphoreSlim) with a rigorous, data-driven batching mechanism and sophisticated resilience patterns (Microsoft Resilience package), a fragile API consumption pipeline was transformed into a highly efficient and resilient system.
While this optimization provides a solid foundation for handling 10,000 requests, scaling to 50,000+ remains constrained by the hard limit of the third-party API.

Future scalability efforts should focus on:
Rate limits or, critically, to obtain the standard Retry-After header for superior dynamic delay management.
Adaptive Batch Sizing: Implementing an algorithm to dynamically adjust the batch size and concurrency level based on real-time API response times and success rates, further refining the throughput ceiling.

Lessons learned

By combining concurrency, batching, and robust retry logic in .NET 8, I transformed a sluggish API processing pipeline into a more efficient system, cutting processing time by 50%. This experience highlights the importance of understanding API constraints, leveraging batching for scalability, and using resilient patterns like retries and circuit breakers. While third-party rate limits remain a challenge, these optimizations provide a solid foundation for handling large-scale API requests, with room for further refinement as request volumes grow.

Top comments (0)