SfSkills Domains Docs GitHub

Home / flow / flow-get-records-optimization

flow-get-records-optimization

Optimize Get Records in Flow — filters, field selection, sort/limit, caching, avoiding queries in loops. Triggers: get records, flow SOQL, flow query limit. NOT for Apex SOQL tuning — use flow/flow-governor-limits-deep-dive. NOT for loop bulkification failures — use flow/flow-bulkification.

Open the skill folder on GitHub


Flow Get Records Optimization

Performance Symptoms

Out of Scope

The Budget You Are Spending

Per transaction, not per element and not per interview:

GovernorSynchronousAsynchronous
SOQL queries100200
SOQL query rows50,00050,000
CPU time10,000 ms60,000 ms
Heap6 MB12 MB

Record-triggered and schedule-triggered flows run one interview per record, and the platform batches those interviews — up to 200 — into a single transaction sharing one budget. The number that matters is per-interview cost × batch size, so one Get Records in the per-record path is 200 queries at a full batch. Almost every "my flow works in testing and fails in production" report in this domain is that multiplication.

Which column applies is not a detail. A record-triggered flow runs inside the triggering transaction and gets the synchronous column — 200 queries against 100, which fails. A schedule-triggered flow runs under the asynchronous column from runtime version 61.0 onward, per the Improve Scheduled Flow Performance with Updated Limits release update — so the same 200 queries fit against 200, exactly, with nothing left over for the Apex triggers and other flows sharing the transaction. Establish the flow type and its runtime version before you do the arithmetic; a scheduled flow costed against 100 gets rewritten in Apex for no reason. Note also that DML statements are 150 in both columns, so the DML side of the multiplication is often what actually breaches.

The Rules

  1. Never Get Records inside a Loop. Collect Ids in an Assignment, run one

    Get Records with an In filter after the loop, match in a second loop. Flow has no map, so the match is O(n×m) CPU and zero extra queries — the right trade given SOQL is budgeted at 100 (200 asynchronous) and CPU at 10,000 ms (60,000 asynchronous). Queries are the scarcer resource in both columns.

  2. Make the filter selective, and check rather than guess. Selectivity is

    measured in absolute rows: a standard index is selective below 30% of the first million targeted records and 15% beyond, capping at 1,000,000; a custom index below 10% / 5%, capping at 333,333. A filter matching 500,000 rows cannot be made selective by indexing it. Verify in Developer Console → Query Plan; a Cost above 1 means a full table scan.

  3. Know which fields are actually indexed. The standard-indexed set is Id,

    Name, OwnerId, CreatedById, LastModifiedById, lookup and master-detail relationship fields, CreatedDate, and SystemModstamp. Custom fields marked Unique or External Id get a custom index. Nothing else is indexed by default — "standard field" and "standard index" are different things.

  4. Specify fields on large collections, not on every lookup. Heap is 6 MB

    synchronous and a screen flow re-serializes variables between screens, so a named field list is a real saving on a wide object. It is also brittle: a field added downstream and forgotten in the query returns null rather than erroring. Accept automatic storage on single-record lookups.

  5. Push sort and limit into the query for "top N", and remember the limit

    bounds what is returned, not what the optimizer must consider. A limit does not rescue a non-selective filter.

  6. Cache what cannot change; re-query what can. The running user's manager

    and a Custom Metadata row are safe to hold across screens. Record ownership and status are not — and a paused interview makes staleness a matter of days.

  1. Compute per-interview cost × batch size first — against the right column.

    Record-triggered flows are costed against 100 SOQL, schedule-triggered flows on runtime version 61.0+ against 200. If that number is comfortably under the limit, the flow is not the consumer — inventory the object's other automation and read the debug log's cumulative limits section.

  2. Fix loops. Any Get Records reachable from inside a Loop is the first and

    usually only real problem.

  3. Test each filter for selectivity in the Query Plan tool, not by

    inspection. Lead with something genuinely narrow and let the rest reduce the small result.

  4. Replace Contains filters with StartsWith, SOSL, or a pre-computed

    normalized field. A leading wildcard cannot use an index.

  5. Trim field lists on large collections, and trim collections before any

    Pause element.

  6. Handle not-found with a Decision, with assignNullValuesIfNoRecordsFound

    set to true. Zero rows is a successful query and will never take a fault path.

  7. Re-measure. Confirm the SOQL count against the same multiplication you

    started with, in a debug log from a realistic batch.

Official Sources Used

The full annotated list is in references/well-architected.md.

Dependencies

Package files