PD2 06 - Performance¶
Writing code and designing automation that performs at scale. This is what most distinguishes PD2 from PD1: the exam asks not only whether code works, but whether it works on 200 records, and on two million.
The governor-limit mindset¶
Performance on the platform is inseparable from governor limits. Code that is slow usually also approaches a limit, and code that respects limits is usually fast enough.
- Design for bulk from the start - assume every entry point (trigger, batch, API) can receive many records.
- Minimize queries and DML - the scarce resources. Query once, process in memory, write once.
- Watch CPU time - complex in-memory processing, nested loops, and heavy string work consume the 10,000 ms synchronous budget. CPU-time limit failures are common in poorly structured triggers.
- Manage heap - large query results and collections consume the 6 MB heap. Use the SOQL for loop to process in batches.
Efficient Apex patterns¶
- Bulkify - collections in, collections out. No SOQL or DML in loops.
- Use maps for correlation - build a
Map<Id, SObject>to relate records without nested loops or per-record queries. Turns an O(nΒ²) nested-loop pattern into O(n). - Query only what you need - select only required fields and filter selectively, both to reduce heap and to enable index use.
- Avoid repeated describe calls - cache
Schemadescribe results rather than recomputing them. - Asynchronous for volume - move large-volume work to Batch Apex, gaining fresh limits per chunk.
- Short-circuit - exit early from loops and methods when further work is unnecessary.
- Static caching within a transaction - cache expensive results in static variables for reuse during the same transaction.
The map-based correlation pattern is worth internalizing: whenever you find yourself looping over one list inside a loop over another, a map keyed by the join field removes the inner loop.
Query performance and large data volumes¶
- Selective queries - filter on indexed fields with selective values so the optimizer uses the index. Non-selective queries on large objects fail with a timeout.
- Indexes - standard on Id, Name, owner, foreign keys, audit fields; custom on External ID and unique fields; and support-created for others.
- The query plan tool - shows the cost and whether an index is used. The way to diagnose a slow query.
- Avoid negative and wildcard-leading filters -
!=,NOT, andLIKE '%term'cannot use an index. - Data skew - ownership, lookup, and account skew cause lock contention and slow sharing recalculation. Distribute ownership and children across parents.
- Skinny tables and indexes - request from Salesforce support for very large, read-heavy objects.
- Archiving and big objects - move cold data out of the transactional store; Big Objects hold billions of records for archival and are queried with async SOQL.
Trigger and automation performance¶
- One trigger per object with a handler - predictable and testable.
- Recursion control - a static flag prevents a trigger re-entering and multiplying queries.
- Consolidate automation - many overlapping flows, workflow rules, and triggers on one object each re-run the save logic and compound CPU cost. Consolidating them is both a maintainability and a performance improvement.
- Order of execution awareness - understand that workflow field updates re-fire triggers, doubling their cost.
- Asynchronous offloading - move non-immediate work (callouts, heavy computation, notifications) out of the synchronous transaction.
Concurrency and locking¶
- Record locks - DML locks the records it touches for the transaction. Two transactions updating related records can deadlock or throw
UNABLE_TO_LOCK_ROW. FOR UPDATE- explicitly locks queried rows to serialize access and prevent lost updates.- Ownership skew and locking - many child records under one parent means updates contend for the parent lock. A frequent cause of intermittent
UNABLE_TO_LOCK_ROWerrors at scale. - Reduce lock scope - process in smaller batches, and order operations consistently to avoid deadlock.
UNABLE_TO_LOCK_ROW under load usually traces back to data skew or to unordered concurrent updates on shared parents. The exam presents this as a scenario.
Asynchronous processing at scale¶
- Batch Apex chunking - default 200 records per chunk, tunable down for heavy per-record work that risks CPU limits, or the chunk size adjusted to balance throughput against limit headroom.
- Batch chaining and Queueable chaining - sequence long pipelines while respecting concurrency limits on queued and scheduled jobs.
- Platform Events for decoupling - offload downstream work to subscribers so the publishing transaction stays fast.
- Async limits - concurrent async jobs and the daily async execution limit are themselves governor limits to design around.
Diagnosing performance problems¶
- Reproduce with a realistic data volume, not one record.
- Capture a debug log and read the execution timeline and limit usage.
- Use the query plan tool for slow SOQL.
- Identify the scarce resource being exhausted: SOQL, DML, CPU, or heap.
- Apply the matching fix: bulkify, add a selective filter, move to async, or reduce chunk size.
Exam pointers¶
- Nested loops over two lists should become a map keyed by the join field.
- Non-selective queries on large objects fail; filter on selective indexed fields.
!=,NOT, and leading-wildcardLIKEprevent index use.UNABLE_TO_LOCK_ROWat scale usually means data skew or unordered concurrent updates.- Batch Apex gives fresh limits per chunk; reduce chunk size when per-record CPU is high.
- Consolidate overlapping automation on an object to cut compounded save-time cost.
- Move callouts and heavy computation to asynchronous processing.
Official documentation¶
π Platform Developer II exam guide - authoritative objectives π Large Data Volumes Best Practices - selectivity, skew, indexing π Apex Best Practices - bulkification and efficiency