Finding the queries behind a slow ERPNext save
Frappe Recorder records the SQL queries and Python stack frames behind a single request, so when a finance user's Sales Invoice save or a stock entry submit hangs, you can trace the wait to a specific query and the code that issued it. Before you enable it, rule out the simpler causes. Check CPU, memory, swap and disk I/O on the application and database hosts, and check whether the background queues are backed up. A saturated host or a long queue makes saves slow in ways a query trace will not explain. Recorder earns its place when resources look healthy and one request is still slow, which usually points to custom server code, document hooks or an unusually large set of linked rows.
Recorder does not replace the other work on a slow system. MariaDB buffer pool sizing and server settings belong to the database tuning topic, and a report that scans the General Ledger for years of data needs prepared reports, indexes or a redesign, not a trace. Recorder also adds overhead and stores document values inside captured queries, so run it on a staging copy or a restricted site for a short window, and limit who can view the output. Confirm with your team or host how Recorder is enabled for your version, which roles can open its interface, and whether your hosting plan allows it. Also confirm your Frappe line is still supported, since Frappe's supported versions page lists version 14 as end of life from January 31, 2026.
Recorder options and when each one fits
This table compares the ways you can scope a Frappe Recorder capture: a single user session, a specific request path, or background jobs. Read each row for what it costs in overhead and how much request data it stores. Confirm the exact option names and settings in the Recorder documentation for your ERPNext version before you enable anything.
| Capture mode | Best for | Overhead and data sensitivity | When to choose it |
|---|---|---|---|
| Single user session | Reproducing a slow save or page one user reports | Moderate overhead for that user only; records their queries, document names and field values | When a specific user can repeat the slow action and you need their exact call sequence |
| Specific request path | Isolating one slow endpoint or doctype save | Low to moderate overhead, limited to matching requests; captured SQL may still contain business data | When you know the route or method and want clean, comparable captures across several attempts |
| Background jobs | Slow imports, reposts, scheduled tasks and queued work | Overhead falls on workers; long captures grow large quickly, so keep the window short | When the slowness appears in a queue rather than in a browser request |
| Short time window | Catching intermittent slowness | Bounded overhead and storage; you may miss the event if the window is too narrow | When the problem happens at known times and you can start capture just before them |
| Staging or test copy | Testing fixes and measuring before and after | Lowest risk to production users and data; results may differ if data volumes differ | When you can reproduce the workload on a copy with similar records and indexes |
| Production capture | Confirming a fix under real load | Highest sensitivity; captures can include personal, financial or customer data | Only with an approved change window, access controls on the output and a plan to disable and purge captures |
| Stack trace depth | Linking queries to the code that issued them | Deeper traces add more overhead and make captures larger | When a query pattern is clear but you need to find which script, hook or report triggered it |
Setting up Recorder on a test copy first
This sequence turns a vague complaint about a slow save into a single, reproducible capture of the requests and queries behind it, made on a copy of the site where a slow profile does no harm. The output is a recording that can be read and compared before anything changes in production.
Clone the production site to staging
Ask the site owner or platform engineer to restore a recent backup of the production site into a staging bench, using the same ERPNext and Frappe versions and the same installed apps. Confirm that the staging site loads, that the versions match production in the Desk About page, and that the data is recent enough to contain the record that is slow. Keep the staging site off any live email accounts, payment connectors and integration webhooks, since reproducing a save can trigger those in a copy.
Enable Recorder for the staging site
On the staging site only, turn on the Recorder through the site configuration or the Recorder settings the version provides, and confirm which site and user the recording applies to. Recorder is a diagnostic tool that stores captured request data, so it should stay disabled on production unless the team has a planned window and a plan to switch it off. Check the Recorder page in the Desk to confirm it reports as active before moving on.
Pick a known test record for reproduction
Choose a specific document that is reliably slow to save, such as a Sales Invoice with a known number of item rows, and write down its name, the user who normally saves it, and the exact steps that trigger the delay. Record the row count, the number of linked documents and any custom scripts that run on save. A single, named record gives you a repeatable baseline, which is what makes the later query comparison meaningful.
Reproduce the slow save and capture the request
Log in to staging as the same user who reports the problem, open the test record and perform the save exactly as in the written steps. Note the start and end time of the click on your own clock. Once the save completes or times out, the Recorder should show a captured request for that action. If nothing appears, check that the user, site and request path match what you configured, and repeat the save.
Confirm the capture covers the right window
Open the captured request and confirm it matches your timestamp, the expected user, and the document name in the request path or payload. Make sure the capture does not include unrelated background activity or other users' requests that would clutter the query list. If the capture is too broad or too narrow, adjust the user or window setting and reproduce the save again. The check is complete when the slow request is listed once, with the right user and time.
Verify the slow request appears in the list
Find the slow request in the Recorder list and confirm its duration, query count and the SQL statements it ran are present. Compare the duration against your own timing from the previous step to make sure you are looking at the same action. Keep a copy of the request identifier and the record name in your ticket, so the analysis that follows can be traced back to this single capture.
Turn recording off and confirm it stopped
Disable Recorder on the staging site once the capture is saved, then perform one unrelated page load and confirm that no new request is recorded. Note who turned it off and when in the change record. Recording left on keeps storing sensitive request data and adds overhead, so this step must be checked off before the analysis starts, not assumed.
Reading SQL, stack traces and repeated queries
Start by sorting the captured requests in Recorder by total time rather than by the slowest single request, because a save that runs 40 small queries can cost more than one that runs a single heavy query. Open the top entries and look at the query count alongside the total duration. A high count with short individual queries usually points to an N+1 pattern, where the same query repeats once per child row, such as a link lookup on each item line of a Sales Order. Recorder groups identical queries with their stack traces, so you can see the repeated statement and the code path that issues it in one view.
Separately, look for long single queries. These are the statements that scan large tables, usually Stock Ledger Entry, General Ledger Entry or Bin, and are typically missing an index or filtering on a column that is not indexed. For each suspect query, read the stack trace from the bottom up to find the first frame inside the ERPNext or custom app code, then identify whether it sits in a controller method, a document hook registered in hooks.py, or a server script. Confirm the exact function name in your own copy of the code before editing anything, since line numbers change between minor releases.
The fix path depends on where the code lives. Server Scripts are disabled by default from version 15 and must be enabled with the server_script_enabled setting, and Frappe Cloud public shared benches do not allow them at all. On those hosts, a slow server script can only be changed by moving it into a custom app on a bench you control or by asking the platform team what is available. Confirm your hosting model and whether server scripts are enabled before you plan the change, so the fix is not designed around a path your environment cannot use. The Frappe server script documentation describes the setting and its behavior.
Turning findings into fixes
A profile shows where time goes, but not always what to change. The fixes below map common Recorder findings to the change that usually addresses them, and each one ends with a retest against the same request so you can confirm the gain. Start with the cheapest change that matches the evidence.
Add or check database indexes
Use this when Recorder shows the same filtered query scanning many rows on a large table, often a WHERE or ORDER BY on a field that is not indexed. Run EXPLAIN on the captured SQL to confirm a full scan before changing anything. Index changes add write cost and disk use, so test on a copy of the site first. Retest the original request and compare the query count and total SQL time.
Reduce repeated queries in custom code
Use this when the same frappe.db.get_value or frappe.get_doc call repeats inside a loop, so the query count grows with the number of rows. Move the lookup out of the loop, fetch the set once, and reuse the result in a dictionary. Check any custom Server Script or app hook that runs on save, since a single extra query there repeats on every document. Retest and confirm the repeated query no longer appears in the trace.
Move heavy work to background jobs
Use this when the profile shows a long, non-blocking step such as bulk updates, PDF generation, or stock recalculation running inside the request. Enqueue the work so the user gets a response while a worker completes it. Confirm the queue choice matches the expected duration, and check that the job reports errors somewhere you read. Retest by timing the save from the user's view and checking the job log for completion.
Batch child table reads
Use this when Recorder shows one query per child row, a pattern common in documents with long item tables. Replace per-row fetches with a single query that filters on the parent name and returns all child rows at once. Check whether the lookup is in core code or a customization, because only custom code is yours to change. Retest with a document that has a large item table, not only a small sample.
Choose code or configuration first
Use this when the slow step comes from a setting rather than logic, such as an automation rule firing on every save or a report with broad default filters. Changing configuration is usually faster to review and easier to roll back than a code change. Use code only when the trace shows the cost sits in logic that configuration cannot reach. Retest after each change, one at a time, so each gain can be attributed.
Questions buyers ask about profiling ERPNext
What drives the cost of profiling a slow ERPNext page?
The cost follows the amount of investigation, not the page count alone. The main drivers are the number of slow flows, the size and age of the custom code behind them (custom scripts, custom apps and overridden controllers), and how much of the work your internal staff can absorb. Your staff time is a real cost even when no invoice is raised, because someone must reproduce the slowness, supply test data and review the findings. Ask any partner for a written scope that lists each flow to be profiled, the environment used, the deliverables (captured traces, a ranked list of queries and proposed fixes) and whether implementing the fixes is included or quoted separately.
How long does a first Recorder investigation take?
It depends mostly on whether the slowness can be reproduced on demand. A flow that you can trigger reliably on a staging copy with realistic data is usually quick to capture, and the time then goes into reading the SQL and stack traces. A problem that appears only in production, under load, or for one customer's data can take much longer, because the team first has to find the trigger and then recreate the conditions. Ask for the timeline to be split into reproduction, capture, analysis and fix phases, so you can see where the uncertainty sits. DeployOpen does not publish durations, so the scope agreed in writing is the reference point.
What does the work need from our team?
Plan for four inputs before the work starts:
Can our internal team profile ERPNext without a partner?
Yes, if the team already runs ERPNext in a bench-based or Docker-based setup, has shell access to a non-production site and is comfortable reading SQL execution plans and Python tracebacks. Frappe Recorder is an app you enable on a staging site, so the technical barrier is mostly analysis skill. The harder part is repeatable diagnosis across many customizations, because each custom script or app can add queries that look normal in isolation. Teams with a single well-understood custom app often manage alone. Teams with several customizations, limited database experience or a pending upgrade often find that an outside review pays for itself in fewer repeated investigations.
When should we bring in a partner for ERPNext performance work?
Bring in a partner when the slowness blocks month-end or a key operational flow, when the cause crosses several custom apps or the database, or when your team has already tried the obvious fixes without a clear result. A partner is also useful before a major version upgrade, because upgrade testing often exposes new slow paths. If you already have a retainer for support or testing, ask whether profiling can be scoped as a fixed-scope piece of that work. Any engagement should define what will be changed in production and who approves each change.
Is it safe to run Frappe Recorder on a production site?
Treat Recorder as a staging tool first. It records queries and stack traces for requests and background jobs, which adds overhead and can store sensitive data from documents, so your security team should approve where it runs and how captured output is stored and deleted. Confirm the current behavior, configuration keys and access controls in the Recorder documentation and your own Frappe version before enabling anything in production. Any production capture should be time-limited, limited to the affected users, and cleared afterward.
Does profiling cover fixes such as indexes and custom code changes?
Profiling identifies the cause; fixing it is a separate step that you should scope explicitly. Typical fixes include adding or adjusting indexes, changing a custom script to fetch fewer rows, moving a heavy report to a prepared report or background job, or removing repeated queries inside a loop. Each change needs its own testing, including permission checks and data correctness, and may need a deployment through your normal change process. Ask whether the scope covers the fix, the test evidence and the rollback plan, not only the diagnosis.
How DeployOpen scopes and delivers ERPNext profiling work
DeployOpen approaches slow ERPNext pages as a scoped engineering task: we agree which saves, reports and jobs matter, capture Frappe Recorder evidence from your own environment, and turn the findings into fixes you can review and run. Each engagement is fixed-scope, with deliverables agreed in writing before work starts.
How we scope
- Written scope names the slow screens, saves, reports and background jobs to profile, agreed before any work begins.
- We confirm the ERPNext and Frappe version, bench layout and whether the site runs on Frappe Cloud, Docker or a bench install.
- Scope states which environments we may profile, such as staging with production-like data, and which changes are out of scope.
How we build
- We enable Recorder on a non-production bench first, then capture representative requests and jobs with the users who reported the problem.
- Captured SQL is grouped by query and stack trace, so each finding links to the code path, DocType or script that issued it.
- Each proposed fix, such as an index, a changed query, a script adjustment or a job split, is tested against the same captured requests before it is recommended.
How we hand over
- Deliverables include the recorded traces, a findings report ranked by cost, and the exact changes applied or recommended.
- Runbook steps explain how to start and stop Recorder, what to capture, and how to disable it so it does not remain active in production.
- Knowledge transfer sessions walk your engineers through reading SQL and stack traces so they can repeat the analysis themselves.
How we support
- Ongoing support, upgrades and continuous testing are available as retainers for teams that want profiling repeated after each ERPNext release.
- Retainers can cover re-profiling after upgrades, since query plans and app code change across the frequent minor releases of supported versions.
- Ask any provider to confirm, in writing, who has access to captured traces, how long they are kept, and where they are stored.

