Domain 7 β€” Module 3 of 3 100%
28 of 28 overall
Domain 7: Implement Security and Optimize Performance Free ⏱ ~15 min read

Trace Parser & Optimization

Use Trace Parser to find bottlenecks, optimise data entity imports, design effective indexes, and tune batch processes with the Async and Sandbox frameworks.

Why trace before you optimise?

Simple explanation

Think of a doctor.

A good doctor doesn’t prescribe medicine based on a guess β€” they run tests first. Trace Parser is your diagnostic tool. It records everything F&O does during an operation (which methods run, which SQL queries fire, how long each takes) and shows you exactly where the time is spent.

Without tracing, you’re guessing. With tracing, you can see: β€œ80% of this batch job’s time is spent in one SQL query that scans 2 million rows because there’s no index.” Now you know exactly what to fix.

Trace Parser: capture and analyse

Capturing a trace

StepAction
1Navigate to the operation you want to trace (e.g. a slow form or batch job)
2Open System Administration β†’ Setup β†’ Trace β†’ enable tracing
3Set trace options: SQL traces (essential), X++ method traces (for code analysis)
4Perform the slow operation
5Stop tracing
6Download the trace file (.etl)
7Open in Trace Parser desktop tool for analysis

What the trace shows you

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Trace Parser                                        β”‚
β”‚                                                      β”‚
β”‚  β”Œβ”€ Call Tree ─────────────────────────────────────┐ β”‚
β”‚  β”‚ processReport()              [Total: 45.2s]     β”‚ β”‚
β”‚  β”‚ β”œβ”€ loadData()                [38.1s] ← HOTSPOT  β”‚ β”‚
β”‚  β”‚ β”‚  β”œβ”€ SELECT FROM InventTrans [32.4s] ← BAD SQL β”‚ β”‚
β”‚  β”‚ β”‚  └─ calculateCost()        [5.7s]             β”‚ β”‚
β”‚  β”‚ └─ formatOutput()            [7.1s]             β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                      β”‚
β”‚  β”Œβ”€ SQL Statements ────────────────────────────────┐ β”‚
β”‚  β”‚ #1: SELECT ... FROM InventTrans WHERE ...       β”‚ β”‚
β”‚  β”‚     Duration: 32.4s | Rows: 2,100,000           β”‚ β”‚
β”‚  β”‚     Table Scan (no index used)  ← ROOT CAUSE    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key metrics to examine

MetricWhat it tells youAction threshold
Method durationHow long each X++ method takes> 1s for interactive, > 10s for batch steps
SQL durationTime spent in individual SQL statements> 500ms for single queries
Row countHow many rows a query returns or scansScan count >> return count = missing index
Call countHow many times a method/query executesRepeated identical queries = caching opportunity
RPC callsClient-server round tripsHigh count = chatty design, consolidate calls
Scenario: Elena traces the slow production report

PacificForge’s production report takes 12 minutes to run. Elena captures a trace and finds:

FindingDurationRoot causeFix
SELECT FROM ProdTable scans entire table6.2sNo index on SchedDate columnAdd non-clustered index on SchedDate
processReport() calls fetchCost() 45,000 times4.1sRow-by-row lookup in a loopReplace with set-based join using insert_recordset
Same CustTable.AccountNum lookup repeated 3,000 times1.8sNo caching on CustTableAlready has Found caching β€” query uses non-indexed field. Fix the WHERE clause to use primary key

After fixes: 12 minutes β†’ 45 seconds.

β€œThe trace told us exactly where to look,” Elena tells Sophie. β€œWithout it, we’d have been guessing.”

Query plan analysis

Reading SQL Server execution plans

When Trace Parser reveals a slow query, the next step is examining the execution plan to understand why:

Plan elementGood signBad sign
Index SeekQuery uses an index efficientlyβ€”
Index Scanβ€”Reading entire index β€” usually means the WHERE clause doesn’t match the index
Table Scanβ€”Reading every row β€” no usable index at all
Nested Loop JoinSmall outer table, indexed inner tableLarge tables on both sides = slow
Hash JoinLarge unsorted datasetsSmall datasets = overhead wasted on hashing
Sortβ€”Sorting in-flight = missing index with the right sort order
Key Lookupβ€”Index doesn’t cover all needed columns (consider INCLUDE columns)
Exam tip: Index Seek vs Index Scan vs Table Scan

The exam frequently tests understanding of these three:

  • Index Seek βœ… β€” jumps directly to matching rows using the index B-tree. Fast: O(log n)
  • Index Scan ⚠️ β€” reads the entire index sequentially. Better than table scan but still reads all index pages
  • Table Scan ❌ β€” reads every row in the table. No index used at all. Always investigate

Pattern: β€œA query on InventTrans WHERE ItemId = β€˜WIDGET-01’ does a table scan. What’s missing?” β†’ A non-clustered index on ItemId.

Index design

Index types in F&O

Index types β€” each serves a different query pattern
Index TypeStructureWhen to useLimitation
ClusteredDetermines the physical sort order of data on disk. One per table.The most frequently used range query or the primary keyOnly ONE per table β€” choose carefully
Non-clusteredSeparate B-tree structure with pointers to data rows. Many per table.Secondary lookup patterns (WHERE, JOIN conditions)Too many indexes slow down INSERT/UPDATE operations
UniqueEnforces uniqueness + serves as an indexPrimary keys and alternate keys that must be uniqueInsert fails if duplicate value exists
Include columnsNon-key columns added to a non-clustered index leaf levelCover queries to avoid key lookups (SELECT columns not in WHERE)Increases index size on disk

Index design guidelines

GuidelineWhy
Index the WHERE clause columnsThe optimiser uses indexes to filter rows β€” unindexed columns force scans
Put the most selective column firstThe column that eliminates the most rows should be the leading column
Include columns to cover the queryIf the SELECT list includes columns not in the index, SQL does a key lookup. Add them as INCLUDE columns to avoid this
Don’t over-indexEach index is updated on every INSERT/UPDATE/DELETE. More indexes = slower writes
Match the clustered index to the most common queryThe clustered index determines physical data order β€” range queries on the clustered key are very fast
Vik designs indexes for a custom table

Vik creates indexes for a new PFQualityResults table at Axion Dynamics:

IndexTypeColumnsReasoning
PFQualityResults_PKClustered uniqueQualityOrderId, TestIdPrimary key β€” most queries filter by quality order
PFQualityResults_ItemNon-clusteredItemId, TestDate INCLUDE Result, StatusReports query by item and date, need result and status in output
PFQualityResults_StatusNon-clusteredStatus, TestDateDashboard queries filter by status within date range

β€œThree indexes is usually the sweet spot for a table,” Vik says. β€œOne clustered for the PK, one or two non-clustered for the most common query patterns. More than five and your writes start suffering.”

Optimising data entity imports

Data Management Framework (DMF) imports can be slow for large datasets. Key optimisation levers:

TechniqueHow it helpsSetting
Set-based processingUses insert_recordset instead of row-by-rowEntity property: EnableSetBasedProcessing = Yes
Skip validationSkips business rule validation for pre-validated dataImport settings: skip validation checkbox
Skip stagingBypasses the staging table (direct insert)Composite entity or skip staging option
Parallel processingMultiple threads process different chunks simultaneouslyData Management β†’ Configure entity execution parameters
Batch size tuningOptimal number of rows per commitDefault 100 β€” increase to 500-1000 for clean data
Disable change trackingAvoids change-tracking overhead during bulk loadTemporarily disable, re-enable after import
Scenario: Elena optimises a product data import

PacificForge needs to import 200,000 product records from their legacy system. Initial import takes 18 hours.

Elena’s optimisations:

ChangeBeforeAfter
Enable set-based processingRow-by-row insertsBulk insert_recordset
Skip staging tableStaging β†’ Target (2 writes)Direct to target (1 write)
Increase batch size to 500100 rows per commit500 rows per commit
Enable 4 parallel threadsSingle-threaded4 parallel workers
Disable change trackingTracking every insertDisabled during import

Result: 18 hours β†’ 1.5 hours.

β€œSet-based processing alone cut it by 60%,” Elena tells DBA Harpreet. β€œThe parallel threads gave us another 3x improvement.”

Batch process optimization

Batch framework basics

ConceptDetail
Batch jobA scheduled task that runs on the batch server (AOS)
Batch taskIndividual units of work within a batch job
Batch groupAssigns tasks to specific AOS instances for load balancing
RecurrenceSchedule: once, daily, hourly, custom cron-like patterns
ParallelismMultiple tasks within a job can run simultaneously on different threads

Async framework

The Async framework allows batch tasks to run asynchronously β€” the calling code doesn’t wait for completion:

// Create an async batch task
SysOperationServiceController controller =
    new SysOperationServiceController(
        classStr(PFInventValuationService),
        methodStr(PFInventValuationService, processWarehouse));

controller.parmExecutionMode(SysOperationExecutionMode::Async);
controller.startOperation();
// Continues immediately β€” task runs in background
BenefitDetail
Non-blockingMain thread continues while async work runs in background
ScalableMultiple async tasks can run in parallel across batch servers
ResilientEach task can fail independently without crashing the parent job

Sandbox framework

The Sandbox framework provides isolated execution for untrusted or resource-intensive code:

FeatureDetail
IsolationCode runs in a separate AppDomain with restricted permissions
Resource limitsMemory and CPU caps prevent a single task from consuming all resources
TimeoutTasks are killed if they exceed the configured time limit
Use casesExternal data transformations, complex calculations, third-party library calls

Batch optimisation patterns

PatternDescriptionWhen to use
Task splittingBreak one large task into many small parallel tasksLarge dataset processing (e.g. inventory valuation per warehouse)
Batch bundlingGroup related small tasks into one executionMany tiny tasks with per-task overhead
Dependency chainsTask B runs only after Task A completesSequential processing requirements
Priority assignmentCritical tasks get higher priority in the batch queueMix of urgent and background tasks
Server affinityPin tasks to specific AOS instances via batch groupsResource-intensive tasks on dedicated servers
Scenario: Elena redesigns the nightly batch

PacificForge’s nightly processing runs 12 batch tasks sequentially β€” total time: 6 hours. Elena redesigns:

BEFORE (Sequential β€” 6 hours):
Task 1 β†’ Task 2 β†’ Task 3 β†’ ... β†’ Task 12

AFTER (Parallel with dependencies β€” 2 hours):
β”Œβ”€ Task 1 (Inventory sync)     ──┐
β”œβ”€ Task 2 (Sales aggregation)    β”œβ”€β”€β–Ά Task 7 (Financial posting)
β”œβ”€ Task 3 (Purchase reconcile)   β”‚       β”‚
└─ Task 4 (Quality processing)  β”€β”€β”˜      β–Ό
                                   Task 12 (Reports)
β”Œβ”€ Task 5 (Production costing)  ──┐
└─ Task 6 (Warehouse movements) ──┴──▢ Task 8 (Cost roll-up)

β”Œβ”€ Task 9  (Email notifications)
β”œβ”€ Task 10 (Data archival)        ← independent, run anytime
└─ Task 11 (Cache warm-up)

Key changes:

  • Tasks 1-4 run in parallel (no dependencies between them)
  • Task 7 waits for Tasks 1-4 (dependency chain)
  • Tasks 9-11 are independent β€” run in parallel with everything
  • Each task assigned to a batch group for server affinity

β€œParallelism gave us 3x improvement. Identifying which tasks could run independently was the real insight,” Elena says.

Performance checklist summary

Elena's performance checklist for PacificForge

Elena creates a standard performance checklist for the development team:

Before writing code:

  • Use set-based operations (insert_recordset, update_recordset, delete_from) wherever possible
  • Choose TempDB temp tables for datasets > 500 rows
  • Design indexes for your query patterns before building forms

After writing code:

  • Run Trace Parser on key scenarios (form load, batch jobs, reports)
  • Check for table scans in SQL analysis β€” add indexes
  • Look for repeated identical queries β€” add caching
  • Look for row-by-row loops that could be set-based

For batch jobs:

  • Identify independent tasks and run them in parallel
  • Set up dependency chains only where truly required
  • Assign batch groups for server affinity
  • Monitor execution times and set alerts for degradation

For data imports:

  • Enable set-based processing on entities
  • Tune batch size (500-1000 for clean data)
  • Enable parallel threads
  • Skip staging if data is pre-validated

Question

What is the Trace Parser workflow for diagnosing F&O performance issues?

Click or press Enter to reveal answer

Answer

1) Enable tracing in System Administration. 2) Reproduce the slow operation. 3) Stop tracing and download the .etl file. 4) Open in Trace Parser desktop tool. 5) Examine call tree for hotspots, SQL statements for slow queries, and call counts for repeated lookups. 6) Fix the top bottleneck. 7) Re-trace to verify improvement.

Click to flip back

Question

What is the difference between Index Seek, Index Scan, and Table Scan?

Click or press Enter to reveal answer

Answer

Index Seek (βœ…) β€” jumps directly to matching rows via the B-tree. O(log n), fastest. Index Scan (⚠️) β€” reads the entire index sequentially. Better than table scan but still reads all pages. Table Scan (❌) β€” reads every row in the table. No index used. Always investigate and add appropriate indexes.

Click to flip back

Question

What are INCLUDE columns on an index and why use them?

Click or press Enter to reveal answer

Answer

INCLUDE columns are non-key columns added to the leaf level of a non-clustered index. They 'cover' the query β€” if all SELECT columns are in the index (key + include), SQL Server doesn't need a key lookup to the clustered index. This avoids expensive bookmark lookups and makes queries faster.

Click to flip back

Question

Name three techniques to optimise data entity imports in the Data Management Framework.

Click or press Enter to reveal answer

Answer

1) Enable set-based processing (insert_recordset instead of row-by-row). 2) Enable parallel processing (multiple threads processing different chunks). 3) Increase batch size (500-1000 rows per commit instead of default 100). Also: skip staging for pre-validated data, and temporarily disable change tracking during bulk loads.

Click to flip back


Knowledge Check

Elena's Trace Parser analysis shows a query on InventTrans that takes 32 seconds and scans 2 million rows but only returns 150. What is the most likely root cause?

Knowledge Check

Vik needs to optimise a data import of 500,000 vendor records. The import currently takes 24 hours. Which combination of settings will have the biggest impact?

Knowledge Check

PacificForge runs 10 nightly batch tasks sequentially (total: 5 hours). Elena identifies that tasks 1-4 have no dependencies on each other. What should she do?

Knowledge Check

A non-clustered index on PFQualityResults has columns (ItemId, TestDate). A report queries SELECT ItemId, TestDate, Result, Status WHERE ItemId = 'WIDGET' AND TestDate > '2025-01-01'. The query does an Index Seek but is still slow due to key lookups. What should Elena add?

Next up: You’ve completed Domain 7! Review security, performance, and optimisation concepts, then test your knowledge across all MB-500 domains.