SfSkills Domains Docs GitHub

Home / flow / flow-governor-limits-deep-dive

flow-governor-limits-deep-dive

Compute and budget governor-limit consumption per Flow type with worked math: SOQL, DML rows, CPU time, heap.

Open the skill folder on GitHub


Flow Governor Limits Deep Dive

This skill answers one question: what does this flow spend, and who else is spending it?

It owns the per-transaction meter table as it applies to Flow, the mapping from each flow element to the meter it consumes, how a flow shares one budget with triggers and Apex, how to read the four limit-usage debug events, and the levers that move spend into another transaction.

It does not own where transaction boundaries belong — that is flow/flow-transactional-boundaries. It does not own the DML-staging refactor (flow/flow-bulkification), loop mechanics (flow/flow-loop-element-patterns), recursion design (flow/flow-record-save-order-interaction), or reading a debug log in general (flow/flow-debugging). This skill prices the choices those skills make.


Before Starting


Questions to Ask Before Configuring

AskWhy it mattersWhat a good answer adds
"What else is Active on this object and trigger type, and what does the Apex trigger already spend?"Meters are per transaction. The save order runs before-save flows, triggers, Process Builder, after-save flows and roll-ups before the single commit (apexdev.txt L15440–L15478), all from one budgetA co-tenant list with a number beside each — from FlowDefinitionView or from the checker's W2 rule — so the budget's TOTAL column is real (Gotcha 1)
"What is the p99 number of records in one save, and how many child rows per record?"Four meters scale with rows, not elements: SOQL rows 50,000, DML rows 10,000, heap, CPU (apexdev.txt L19546, L19556, L19577, L19579). A flow can be at 3% on DML statements and 60% on DML rowsTwo numbers that turn every element count into a row count, which is the half of the worksheet that actually moves (references/examples.md)
"Which meter does the platform event this flow publishes spend?"Publish After Commit "is counted as one DML statement"; Publish Immediately counts "against a separate event publishing limit of 150" (apexrefguide.txt L214524–L214528). Identical XML, different budgetsThe event's configured publish behavior, recorded beside the element — not a guess from the flow file (Gotcha 5)
"What does the invocable Apex action itself consume, and what is its flowTransactionModel?"CurrentTransaction "keeps the invocable action running in the same transaction" (api_meta.txt L68475–L68476), so the class's whole consumption lands in this flow's budgetThe class's own SOQL/DML/callout counts, plus an explicit model value rather than an omitted field the checker flags as A1 (Gotcha 2)
"Does any of this work have to be rolled back if a later step fails?"It is the only question that decides whether NewTransaction or AsyncAfterCommit is available. Async paths are step-20 post-commit logic (apexdev.txt L15479, L15489) — nothing there is rolled backA per-branch yes/no that turns a limits problem into a design decision, and an idempotency requirement for every "no" (Gotcha 16)
"Has anyone measured CPU and heap at peak batch size, or are those numbers estimated?"No developer guide publishes a per-element cost, and CPU excludes database and callout wait time (apexdev.txt L19652–L19656). Ten meters are arithmetic; these two are notA debug-log excerpt or a Limits.getCpuTime() assertion instead of a plausible millisecond figure (Gotcha 21, Anti-Pattern 9)
"Is any automation here from an installed managed package, and is that package certified?"A certified package "gets its own 150 DML statements… its own 100-SOQL-query limit" but shares CPU (apexdev.txt L19671–L19682); a non-certified one spends yours (L19686–L19688)The certification status per package, which decides whether that automation belongs in your budget's TOTAL column or only in the CPU row (Gotcha 9)

What a proper configuration adds over just doing it: a budget whose TOTAL column counts every automation on the object rather than only the flow in front of you, CPU and heap left blank until they are measured instead of guessed, and a transaction split chosen from what must be atomic — so the flow that fails first in production is one you predicted, not one you discover.


The per-transaction meters

Every one of these is shared by every flow, trigger, validation rule, Apex class and package automation in the transaction. Ceilings from the Apex Developer Guide's governor table (apexdev.txt L19542–L19599).

MeterSynchronousAsynchronousLine
SOQL queries100200L19544
SOQL query rows50,00050,000L19546
Database.getQueryLocator rows10,00010,000L19548 — no Flow element spends it
SOSL queries2020L19550 — no Flow element spends it
DML statements150150L19554
DML rows (incl. Approval.process)10,00010,000L19556
Stack depth, recursive trigger-firing save1616L19559–L19561
Callouts100100L19563
@future methods per invocation500 in batch/future, 50 in queueableL19568–L19571
System.enqueueJob501L19573
sendEmail invocations1010L19575
Heap6 MB12 MBL19577 (email services: 50 MB, footnote 4 L19650)
CPU time10,000 ms60,000 msL19579
EventBus.publish, publish-immediately events150150L19598–L19599

Two things this table does not say, and both matter more than any row in it:

Which flow element spends which meter is the meter-by-element table in references/metadata-examples.md § 1, with a fully annotated flow beneath it. Read that before budgeting anything; the short version is: Get Records → SOQL + rows; Create/Update/Delete → DML + rows; Loop/Assignment/Decision/processors → CPU only; email actions → the 10-invocation meter; Apex action → whatever the class spends, unless NewTransaction; a __e create → DML or the publish meter, depending on the event.


Interview vs transaction

These are different units and confusing them is the most common budgeting error.

UNVERIFIED (2026-09-05): the widely-quoted figure of 200 record-triggered interviews per transaction is not stated in apexdev.txt, api_meta.txt or the App Limits cheat sheet. The one documented 200 in this area is FlowScheduledPath.maxBatchSize, "the maximum number of scheduled path interviews to execute in a single batch, from 1 to 200. Default is 200" (api_meta.txt L71397–L71398). Budget the async side from that number; confirm the synchronous side from FLOW_START_INTERVIEWS_BEGIN, which logs the request count.


The transaction-splitting levers

Four, in the order you should try them. The full trade table is in references/well-architected.md § The Budget Ladder.

  1. Spend less — hoist DML and SOQL out of loops. The only lever that reduces the bill rather than

    relocating it. Owned by flow/flow-bulkification and flow/flow-loop-element-patterns.

  2. flowTransactionModel NewTransaction on an actionCalls element — "creates a transaction

    before the invocable action is executed" (api_meta.txt L68477–L68478, API 51.0+). Isolates that action's meters. Costs atomicity.

  3. scheduledPaths with pathType AsyncAfterCommit — the branch runs post-commit

    (api_meta.txt L71412–L71414; apexdev.txt L15489 lists it as step-20 post-commit logic). Tune maxBatchSize (1–200, default 200) to decide how many interviews share the async transaction.

  4. Screens and Pause elements. UNVERIFIED (2026-09-05): neither api_meta.txt nor apexdev.txt

    states that a screen or a waits element ends the transaction; that rule lives only on the Flow help pages. Documented and adjacent: FlowRecordRollback, which "rolls back the current transaction and cancels its pending record changes", is "available only in screen flows" (api_meta.txt L71247–L71250), and a screen-flow callout action can make the flow "commit the current transaction, start a new transaction, and make the call" when Transaction Control is set to let the flow decide (apexdev.txt L26866–L26875).

What none of them do: a subflow does not get its own budget. flowTransactionModel is declared on FlowActionCall alone (api_meta.txt L68472); no field in Flow metadata gives a subflow a transaction.


Reading the four limit-usage events

Set the Workflow log category to FINER or above. Four events, all reporting the same twelve meters (apexdev.txt L38730–L38744, L38795–L38820, L38821–L38834, L38874–L38888):

EventAnswers
FLOW_START_INTERVIEW_LIMIT_USAGEwhat was already spent before this flow ran — your real headroom
FLOW_ELEMENT_LIMIT_USAGEwhich element spent what, per interview
FLOW_BULK_ELEMENT_LIMIT_USAGEwhat one element cost across the whole batch
FLOW_INTERVIEW_FINISHED_LIMIT_USAGEthis interview's total, and the denominator — which is how you find out whether it was metered at synchronous or asynchronous ceilings

The twelve: SOQL queries, SOQL query rows, SOSL queries, DML statements, DML rows, CPU time in ms, heap size in bytes, callouts, email invocations, future calls, jobs in queue, push notifications. No element count appears in any of them, which is the evidence that the "2,000 executed elements" limit is retired (references/gotchas.md Gotcha 8). An annotated excerpt, plus the guide's one verbatim LIMIT_USAGE_FOR_NS block, is in references/metadata-examples.md § 7a.


Review Checklist

  1. Enumerate the co-tenants, not just the flow. Run

    python3 skills/flow/flow-governor-limits-deep-dive/scripts/check_flow_governor_limits_deep_dive.py --manifest-dir <source tree> to get the per-flow element counts and the W2 cross-flow DML total for the object. Add the Apex trigger's consumption by hand — the checker reads flows only.

  2. Map every element to its meter using the table in references/metadata-examples.md § 1. Two

    elements need an answer from outside the XML: the platform event's publish behavior, and the invocable action's own consumption plus its flowTransactionModel.

  3. Fill the arithmetic rows of the worksheet in references/examples.md, multiplying element

    counts by p99 batch size and rows-per-record. Leave CPU and heap blank. If any row is above 70%, stop and fix the spend before considering a split.

  4. Clear checker ERRORs, then decide the split. E1 (DML in loop) is a defect; W1 (SOQL in

    loop) is nearly always one. Only once the shape is right do A2 and the levers in § The transaction-splitting levers apply — async relocates a failure, it never shrinks it.

  5. Measure CPU and heap. Deploy the flow Draft to a sandbox, run the bulk Apex test in

    references/metadata-examples.md § 7b at p99 batch size, and read FLOW_INTERVIEW_FINISHED_LIMIT_USAGE at Workflow FINER (§ 7a). Fill the measured rows and note the denominator.

  6. Write the FlowTest and the budget assertions. The FlowTest (§ 4) proves correctness at bulk

    size 1; the Apex test asserts the counters at 200. Both are needed — neither substitutes.

  7. Record what the guides could not settle in the flow's <description> and in

    templates/flow-governor-limits-deep-dive-template.md: the async path's ceiling denominator, the interviews-per-transaction number, and any help-page figure you had to rely on.


Salesforce-Specific Gotchas

Full statements with grounding in references/gotchas.md; the index:

  1. Limits are shared; your flow does not own 100 SOQL.
  2. A subflow spends the parent's meters, and no Flow field can change that.
  3. A Get Records with relatedRecords costs more than one query.
  4. The CPU meter does not count the time your flow is actually slow for.
  5. Two identical-looking __e creates spend different meters.
  6. A fault path cannot catch a limit breach.
  7. Email alerts are capped at 10 for the transaction, not 10 per flow.
  8. There is no executed-elements limit any more, and the flow's own logs prove it.
  9. A certified managed package gets its own SOQL and DML but shares your CPU.
  10. A flow's DML re-enters the save order, and the stack depth is 16.
  11. A recursive save skips after-save flows but not before-save flows.
  12. triggerOrder sequences flows; it does not budget them.
  13. A single Get Records can legally consume 40% of the row budget.
  14. The App Limits cheat sheet has no Flow row at all.
  15. maxBatchSize is a limits lever wearing a throughput costume.
  16. Async paths are post-commit, so nothing they do is rolled back.
  17. Get Records stores every queried field, and heap is 6 MB.
  18. Platform-Event subscriber batches can surprise.
  19. A Scheduled Path's fresh budget is fresh, not larger.
  20. Async meters reset per execution, not per job.
  21. Nobody publishes what an element costs, so nobody can predict CPU.
  22. The costs nobody has documented, stated as unknowns.
  23. Scheduled-path batches are concurrent, and concurrent writes contend.

Proactive Triggers

Surface these WITHOUT being asked:


Output Artifacts

ArtifactDescription
Limits budget worksheetThe per-object worksheet from references/examples.md, arithmetic rows filled, CPU/heap left for measurement
Meter attributionEvery element in the flow mapped to the meter it spends, including the event publish behavior and the Apex action's model
Checker reportERROR/WARN/ADVISORY findings from scripts/check_flow_governor_limits_deep_dive.py, mapped to the Review Checklist
Split recommendationWhich branches move to NewTransaction or AsyncAfterCommit, with the atomicity each one gives up
Measured baselineThe FLOW_INTERVIEW_FINISHED_LIMIT_USAGE excerpt and the Apex budget-test counters at p99 batch size
Deployable XMLThe annotated flow and its split variant from references/metadata-examples.md, plus the FlowTest and package.xml

Reference Files

FileRead it when
references/metadata-examples.mdYou are writing or reviewing actual *.flow-meta.xml: the element-to-meter table, a fully annotated record-triggered flow, the same flow split across AsyncAfterCommit and NewTransaction with the budget before and after, a FlowTest, package.xml, deploy order, the four limit-usage debug events, and the bulk Apex budget test
references/gotchas.mdThe flow deploys and then breaches something you did not budget — shared meters, the publish-behavior split, the uncatchable limit exception, the managed-package CPU rule, the recursive-save skip, and the things the guides genuinely do not document
references/llm-anti-patterns.mdYou are reviewing generated Flow advice or your own — including the retired element limit, invented CPU figures, System.assertTrue, and "a subflow gets its own limits"
references/examples.mdYou need the limits budget worksheet, or a narrative walk-through of a SOQL-in-loop breach, a shared-transaction forecast, or an async offload
references/well-architected.mdYou need the pillar framing, the Budget Ladder trade table, or the source and guide line behind any claim in this skill
templates/flow-governor-limits-deep-dive-template.mdYou are recording a budget or a review for someone else to act on
scripts/check_flow_governor_limits_deep_dive.pyBefore every deploy and against any fixture directory. --manifest-dir <source tree>, optional --dml-budget N and --strict; exits 1 on any ERROR

Official Sources Used

The full list, with the specific claim each source supports, is in references/well-architected.md § Official Sources Used.

Package files