Operations
From the field archive · Learned in 2020
Dynamics 365 Performance Troubleshooting: Portals, Plug-ins, Slow Queries and API Throttling
"The system is slow" is the least useful bug report there is. Here's the method I used with customers in 2020 to turn it into something we could fix, and the tools I'd use for the same job today.

In 2020 a good share of my week went to performance escalations. A customer portal took too long to load. A simple API call from an integration took 30 seconds. A staging environment got slower every week. An operations team wanted to know, right now, whether they were being throttled. Different customers, different symptoms, but the same pattern: everyone knew it was slow, and nobody could say where the time went.
What I learned from those escalations is that performance work is mostly about narrowing the problem down before you try to fix anything. In this post I go through the method I used then, the most common causes I found, and the tools that make the same work much easier now.
Dynamics 365 portals are now Power Pages. Power Pages has a CDN, a Web Application Firewall, a Site Checker that flags performance issues, and site search built on Dataverse search (the older Lucene .NET search is deprecated). Dataverse service protection API limits are still enforced per user, per web server, in a five-minute sliding window, and the documented defaults are 6,000 requests, 20 minutes of combined execution time, and 52 concurrent requests. Microsoft notes that these can change, so check the current article. You can now export Dataverse and model-driven app telemetry to your own Application Insights resource without writing code (Managed Environments only), and Live monitor shows model-driven app events as they happen. The 2011 SOAP endpoint is deprecated. It still works, but code should go through the supported service clients or the Web API.
01Start by locating the time
When someone reports slowness, I ask four questions before I look at anything else:
- Who is affected? One user, one integration, or everyone?
- Where does it happen? A specific form, page, query, or API call?
- When exactly? I ask for timestamps in UTC and, where possible, request or activity IDs.
- What changed? A release, a data load, a new integration, or more users?
Then I split the time into three parts: the client (browser or calling app), the network, and the server. Most "Dynamics is slow" reports turn out to be one of those three, and the fix is completely different for each.
A genomics company reported that some Dataverse transactions were taking far too long. When we compared server-side timings for the exact requests they flagged, the transactions were completing in a few seconds. The rest of the time was being spent in how the calling application built and sent the requests. That moved the conversation from "the platform is slow" to "let's look at the client's call pattern," which is where the fix was. I couldn't have done that without exact timestamps and request IDs from them.
02Portals: the usual suspects
Portal performance was the most frequent escalation I handled that year. The causes repeated across customers:
| Symptom | Likely cause | What I'd check today |
|---|---|---|
| Slow startup and slow first page loads | Too many web file records | Site Checker flags sites with more than 500 active web files. Consolidate or move static assets. |
| Slow under load, especially headers and footers | Header or footer output caching turned off | Site Checker flags this too. Turn the caching site settings back on. |
| Heavy home page | Large images and many components on one page | Test with a stripped-down page to measure the difference, then enable the CDN for static content. |
| Changes slow to appear | Server-side cache | Power Pages caches data and refreshes it automatically within 15 minutes. Avoid clearing the whole cache on a busy site. |
| Search results out of date for a long time | The old search index | Make sure the site uses Dataverse search. |
For a large automaker's employee portal, we captured browser network traces and then asked the developers to build a version of the home page with almost nothing on it. Comparing the two told us how much of the load time came from page content and how much came from the platform and network. I also found more than 500 web file records in their site, which was a known cause of slow startup. The point of the stripped-down page wasn't to ship it. It was to get a clean baseline.
Run Site Checker before you open a support case. It catches the configuration issues that cause most portal slowness, and it saves everyone a week.
03Throttling: is it really us?
One afternoon an operations lead asked me to check, urgently, whether they were being throttled in production. A downstream system was failing and everyone was pointing at Dynamics. We checked: nothing had been throttled in the previous two hours, and their own logs then showed the failing calls weren't even reaching the service that queried Dynamics. It wasn't Dynamics.
We did find one user who had been throttled earlier that day. My hypothesis was that a form was making too many calls on load, enough to hit the concurrent request limit. At that point that was only a hypothesis. To confirm it, we needed to see what that user did right before the throttle.
Here's what the service protection limits look like today, from the Microsoft Learn article:
| Measure | Default limit per web server | When I see it |
|---|---|---|
| Number of requests | 6,000 in a five-minute sliding window | Bulk operations, chatty integrations |
| Combined execution time | 20 minutes (1,200 seconds) in the same window | Large batches, complex queries, slow plug-ins |
| Concurrent requests | 52 or higher | Parallel integrations, scripts firing many calls at once |
A few details from the documentation that matter in practice:
- Limits are evaluated per user. An integration running as one application user shares one budget, and application users get the same limits as everyone else.
- The Web API returns 429 Too Many Requests with a Retry-After header. Your code should wait that long and retry, not fail.
- The concurrency limit returns an error immediately. The request and execution time limits can be exceeded briefly before they take effect.
- Requests made inside plug-ins don't count toward the limits, but their execution time is added to the request that triggered them.
- Portals can hit these limits because anonymous traffic often runs through one service principal.
- These are separate from Power Platform request entitlements, which are evaluated over 24 hours.
Microsoft's advice for throughput is to let the server tell you how much it can handle: ramp up gradually, honor Retry-After, and avoid large batches. The current SDK service clients handle retries for you. Older custom clients often don't.
04Slow queries and integrations
A biotech customer sent me a "fairly simple" API call from a Logic App that had taken 30 seconds. When I looked at the request, it wasn't that simple. It was a FetchXML query with several levels of outer joins across custom tables to return one record's details.
What I check when a query is slow:
- Joins. How many link-entities, how deep, and are they outer joins? Each one adds work.
- Columns. Select only what the caller uses. Never select all columns.
- Filters. Is the query filtered on indexed columns like the primary key or lookups?
- Volume. How many rows does each joined table have? A query that's fast in test can be slow with production data.
- Splitting. Sometimes two simple queries are faster than one complex one.
Another team had long-running queries and wanted to fix them by updating their SDK version. That was a good idea, but I didn't think it was the root cause. Their code called the deprecated 2011 SOAP endpoint directly through a custom client. My advice was to move to the supported client first, measure again, and only then look for the next bottleneck. Change one thing at a time, or you won't know what fixed it.
In 2022 I gave similar advice to a team still on Dynamics 365 on-premises: review the code that retrieves records, use ExecuteMultiple for bulk loads, replace deprecated methods, and get a code review before tuning the server. Most performance gains I've seen came from the code, not the infrastructure.
05Plug-ins and errors that add up
In a staging environment that kept getting slower, I ran a report on request outcomes. More than 8% of requests were returning HTTP 500 errors from a few legacy services, with an error about input in the wrong format. Errors aren't free. Each failed call still uses server time, and retries multiply it. Before we looked at anything else, I asked the team to find out which component was sending malformed requests.
For plug-ins, the rules I give every team:
- Remember the hard two-minute limit for a Dataverse message operation, including its synchronous plug-ins. If it's exceeded, the whole operation rolls back.
- Prefer asynchronous registration when the user doesn't need the result immediately.
- Set an explicit timeout on external calls. The .NET default is 100 seconds, which leaves almost no room.
- Use filtering attributes so plug-ins only run when the columns they care about change.
- Throw InvalidPluginExecutionException with a clear message, and trace what the plug-in does.
06The toolkit I use now
In 2020, most of the visibility came from the support side. Today customers can see much of it themselves.
Live monitor
Shows model-driven app events as they happen, including script errors with the web resource and solution, and warnings like synchronous requests during form load.
Application Insights export
Dataverse and model-driven app telemetry in your own resource, with operation IDs you can share with support. Managed Environments only.
ILogger
Write your own plug-in telemetry to the same Application Insights resource.
Site Checker, CDN, WAF
Find configuration issues, cache static content closer to users, and filter malicious traffic.
Admin center Monitor
Daily health metrics and recommendations for apps and flows. See the governance toolbox post.
Response headers
The Web API returns headers showing remaining requests and execution time. Use them for debugging only.
Where I'd start
Get exact timestamps and request IDs for the slow operation, then work out whether the time is in the client, the network, or the server. Turn on Application Insights export for production. For portals, run Site Checker and enable the CDN. For integrations, make sure every client honors Retry-After and uses a supported service client. And fix errors before you tune anything, because errors are often the slowness.
Happy to go deeper on any of this.
— K.G
Sources
- Microsoft Learn: Service protection API limits (Microsoft Dataverse); Dataverse API limits overview
- Microsoft Learn: Site Checker performance (Power Pages); How server-side caching works in Power Pages; Configure a site with Content Delivery Network
- Microsoft Learn: Search (Power Pages)
- Microsoft Learn: Analyze model-driven apps and Dataverse telemetry with Application Insights; Export data to Application Insights
- Microsoft Learn: Debug a model-driven app with Live monitor
- Microsoft Learn: Use the SDK for .NET (legacy SOAP endpoint); Transition apps to Dataverse ServiceClient
- Microsoft Learn: Analyze plug-in performance; Set Timeout when making external calls in a plug-in; Best practices for plug-in and workflow development
Get new posts by email
Provider: your email provider



