The Problem: When Parallel Programming Backfires
If you’ve ever optimized your C# API calls with parallel programming, you might know this story.
Your client wants faster performance. You run multiple API requests in parallel. Locally, everything flies — especially late at night when traffic is low. But the next morning, on the final pre-deployment test, disaster strikes.
Random errors. Requests delayed for 45 seconds.
Microsoft Graph or SharePoint has slammed you with throttling and rate limits. Your blazing-fast local solution crumbles under production-like conditions.
TL;DR
- Parallel programming can trigger throttling and rate limiting in production, even if it works locally.
- DevProxy lets you simulate throttling during development so you can fix issues early.
- Use the sample configuration below to test Microsoft Graph and SharePoint API calls.
- Follow Bert Jansen’s throttling guide for proven handling patterns.
- Start testing early — not after problems appear.
How CRUD Operations Can Secretly Burn Through Your Limits — and How to Catch Them with DevProxy
Here’s the thing:
SharePoint Online charges “Resource Units” (RUs) for every request you make. Think of RUs as an invisible currency — every API call you send deducts from your allowance. Run out too fast, and throttling kicks in.
And those “simple” operations? They’re not as cheap as you think.
From Microsoft’s RU table:
| Operation Type | RU Cost | What That Means |
|---|---|---|
| Single item query | 1 RU | Reading one specific list item |
| Multi-item query | 2 RUs | Listing children, filtering, sorting |
| Create / Update / Delete / Upload | 2 RUs | Per individual operation (bulk operations may be more efficient) |
Permission expansion ($expand=permissions) |
5 RUs | Heavy permission lookups |
Source: Microsoft’s official SharePoint throttling documentation
Permission expansions are just one type of “expensive” call. Another common scenario that can impact RU consumption is making multiple separate queries instead of using CAML joins — something I explored in my CAML join post.
While joining multiple lists in a single query is actually more efficient than separate calls, poorly structured queries or retrieving unnecessarily large datasets can still consume RUs quickly if not optimized properly.
Why This Sneaks Up on You
When coding locally, it’s easy to run a handful of queries without noticing.
But in production, with real concurrency and volume, these calls pile up in seconds.
Imagine:
- 10 parallel updates (2 RUs each) = 20 RUs in one burst
- Add a few joins/expansions or large list queries, and you’re suddenly burning through limits 5x faster.
How DevProxy Can Show You the Pain Before Production
Here’s where DevProxy shines.
If you configure it to simulate throttling based on these RU-heavy calls, you’ll see the impact locally:
- Enable throttling plugins in your
devproxy.json(as shown earlier). - Point
urlsToWatchat your SharePoint endpoints. - Run your CRUD-heavy code.
DevProxy will start throwing 429s (Too Many Requests) once your “fake” RU budget is exhausted — just like SharePoint would in production.
The beautiful part?
You can crank the limits down during testing to make expensive patterns obvious. Even a single large query or unbatched Update will light up your logs.
Example DevProxy log when hitting a throttling simulation:
[Warning] Throttling triggered: 2 parallel Create calls exceeded RU limit (RateLimitingPlugin)
Retry after: 30 seconds
Pro tip:
Use DevProxy as a budget meter for your API calls. Treat every 2-RU and 5-RU operation as “spending big” — and redesign those spots before they become a production outage.
What is DevProxy?
DevProxy is an open-source HTTP/HTTPS proxy server that can simulate real-world network issues, including:
It’s free, open source, and designed for developers who want to catch performance bottlenecks before they reach production.
Installing DevProxy
Follow the official setup guide here:
DevProxy Installation Documentation
Basic Usage
Run DevProxy with the default configuration:
devproxy
It will begin intercepting all HTTP/HTTPS requests.
Tip: Start DevProxy before running your own code — otherwise, it won’t capture the traffic.
Simulating Microsoft Graph Throttling
Here’s how to configure DevProxy to reproduce Microsoft Graph and SharePoint throttling issues locally.
1. Create a devproxy.json configuration file
This is the exact configuration I used when testing my problematic parallel code:
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/rc.schema.json",
"rate": 25,
"plugins": [
{
"name": "RetryAfterPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll"
},
{
"name": "RateLimitingPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll",
"configSection": "rateLimitingPlugin"
},
{
"name": "GraphRandomErrorPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll",
"configSection": "graphRandomErrorPlugin"
}
],
"urlsToWatch": [
"https://graph.microsoft.com/v1.0/*",
"https://graph.microsoft.com/beta/*",
"https://graph.microsoft.us/v1.0/*",
"https://graph.microsoft.us/beta/*",
"https://dod-graph.microsoft.us/v1.0/*",
"https://dod-graph.microsoft.us/beta/*",
"https://microsoftgraph.chinacloudapi.cn/v1.0/*",
"https://microsoftgraph.chinacloudapi.cn/beta/*",
"https://*.sharepoint.*/*_api/web/GetClientSideComponents",
"https://*.sharepoint.*/*_api/*",
"https://*.sharepoint.*/*_vti_bin/*",
"https://*.sharepoint-df.*/*_api/*",
"https://*.sharepoint-df.*/*_vti_bin/*"
],
"graphRandomErrorPlugin": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/graphrandomerrorplugin.schema.json",
"allowedErrors": [
429,
503,
504
]
},
"rateLimitingPlugin": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/ratelimitingplugin.schema.json",
"costPerRequest": 2,
"rateLimit": 120
},
"logLevel": "information",
"newVersionNotification": "stable",
"showSkipMessages": true,
"showTimestamps": true
}
2. Start DevProxy with your configuration
devproxy --config-file devproxy.json
Example of Problematic Code
Here’s the snippet that caused my throttling nightmare. It uses Parallel.ForEachAsync to query SharePoint in bulk:
// ❌ This will likely trigger throttling in production
var customers = new ConcurrentBag<CustomerDTO>();
await Parallel.ForEachAsync(departmentIds, async (departmentId, token) =>
{
var clonedContext = _clientContext.Clone(_clientContext.Url);
var query = Camlex.Query()
.ViewFields(new CustomerDTO().ViewFields().ToArray().Append("DepartmentId"))
.LeftJoin(x => x["DepartmentLookup"].ForeignList(DEPARTMENT_LIST_GUID))
.ProjectedField(x => x["DepartmentId"].List(DEPARTMENT_LIST_GUID).ShowField("ID"))
.Where(x => x["DepartmentId"] == departmentId);
var departmentCustomers = await SharePointService.GetItemsFromListByQuery<CustomerDTO>(
CUSTOMER_LIST_GUID,
clonedContext,
query);
if (departmentCustomers?.Any() == true)
{
foreach (var customer in departmentCustomers)
{
customers.Add(customer);
}
}
});
Why This Code Fails in Production
- No throttling safeguards — Multiple parallel requests overwhelm SharePoint.
- No retry logic — Requests fail instead of recovering.
- No request limiting — All departments are processed simultaneously.
- Silent failures — Errors are ignored without logging or fallback.
The Better Way
Instead of hard-coding fixes here, I recommend Bert Jansen’s detailed guide on throttling and rate limit handling:
Throttling & Rate Limit Handling Patterns
Key principles:
- Limit concurrency with
SemaphoreSlim. - Use exponential backoff for retries.
- Monitor rate limit headers to adjust requests dynamically.
- Handle errors explicitly to prevent silent failures.
Conclusion
DevProxy has become an essential tool in my Microsoft 365 development workflow. It helps me:
- Catch throttling issues before production.
- Test error handling and retry logic locally.
- Deliver applications that can survive real-world API limits.
If I’d used DevProxy from the start, I could have avoided the last-minute throttling meltdown entirely.
💡 Pro tip: Make DevProxy part of your early development process, not your emergency toolkit.