skill-performance
Skill115 starsChanged 10 days agoMITView on GitHub
AL performance optimization patterns for Business Central. Use when optimizing queries with SetLoadFields, working with FlowFields and CalcFields, profiling codeunits, or resolving performance issues.
In this repository55 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- claude-plugin/CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- instructions/copilot-instructions.mdCopilot instructions
- agent-builderSkill
- al-agent-build-instructionsSkill
- al-agent-createSkill
- al-agent-instructionsSkill
- al-agent-taskSkill
- al-agent-testSkill
- al-buildSkill
- al-context-createSkill
- al-initializeSkill
- al-memory-createSkill
- al-pr-prepareSkill
- al-spec-createSkill
- architectSkill
- auditSkill
- conductSkill
- developSkill
- presalesSkill
- reviewSkill
- skill-agent-instructionsSkill
- skill-agent-task-patternsSkill
- skill-agent-toolkitSkill
- skill-al-review-pipelineSkill
- skill-apiSkill
- skill-contribution-assistantSkill
- skill-copilotSkill
- skill-debugSkill
- skill-estimationSkill
- skill-eventsSkill
- skill-migrateSkill
- skill-pagesSkill
- skill-performanceSkill
- skill-permissionsSkill
- skill-testingSkill
- skill-translateSkill
- specSkill
- triageSkill
- skill-agent-instructionsSkill
- skill-agent-task-patternsSkill
- skill-agent-toolkitSkill
- skill-al-review-pipelineSkill
- skill-apiSkill
- skill-contribution-assistantSkill
- skill-copilotSkill
- skill-debugSkill
- skill-estimationSkill
- skill-eventsSkill
- skill-migrateSkill
- skill-pagesSkill
- skill-permissionsSkill
- skill-testingSkill
- skill-translateSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 5 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 31
Always filter before finding, and load only needed fields.
- Distinctive: line 73
Avoid manual loops for aggregation — push the sum to the database.
- Distinctive: line 339
Do NOT modify source code automatically during triage — generate report and get approval first
- Distinctive: line 340
Do NOT clear profile codelenses without human confirmation
- Distinctive: line 341
Do NOT save triage report without human gate review
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Skill: AL Performance Optimization
Purpose
Identify, analyze, and fix performance bottlenecks in AL code: inefficient queries, FlowField issues, loop anti-patterns, and data-volume problems in Business Central.
When to Load
This skill should be loaded when:
- A page, report, or batch process is slow or timing out
- CPU profiling is needed to identify hotspots
- A static triage of the codebase is requested for performance issues
- A feature involves large-dataset processing or high-frequency code paths
- AL0896 (circular FlowField) errors appear
- Architecture review requires performance analysis of a new design
Core Patterns
For BC29 query or isolated-storage concurrency work, read the query/storage
sections of AL18 capability checks.
Confirm supported declarations and test transaction behavior before changing
isolation; DataScope and transaction isolation are different concepts.
Pattern 1: SetLoadFields + Early Filtering
Always filter before finding, and load only needed fields. Order matters.
// ✅ Correct: SetRange first, SetLoadFields before Find
Item.SetRange("Third Party Item Exists", false);
Item.SetLoadFields("Item Category Code", Description);
if Item.FindSet() then
repeat
// Only "Item Category Code" and Description loaded from DB
until Item.Next() = 0;
// ❌ Wrong: SetLoadFields after SetRange (ignored), loads all fields
Item.SetLoadFields("Item Category Code");
Item.SetRange("Third Party Item Exists", false);
Item.FindFirst();
// ❌ Wrong: No filter — full table scan
procedure GetCustomersByCity(CityFilter: Text): Integer
var
Customer: Record Customer;
Count: Integer;
begin
if Customer.FindSet() then // loads entire Customer table
repeat
if Customer.City = CityFilter then
Count += 1;
until Customer.Next() = 0;
end;
// ✅ Correct: Filter pushed to DB
procedure GetCustomersByCity(CityFilter: Text): Integer
var
Customer: Record Customer;
begin
Customer.SetRange(City, CityFilter);
Customer.SetRange(Blocked, Customer.Blocked::" ");
exit(Customer.Count());
end;Pattern 2: Set-Based Aggregation (CalcSums / CalcFields)
Avoid manual loops for aggregation — push the sum to the database.
// ❌ Loop accumulation — N rows fetched and processed in AL
procedure GetTotalSales(CustomerNo: Code[20]): Decimal
var
Entry: Record "Cust. Ledger Entry";
Total: Decimal;
begin
Entry.SetRange("Customer No.", CustomerNo);
if Entry.FindSet() then
repeat
Total += Entry.Amount;
until Entry.Next() = 0;
exit(Total);
end;
// ✅ CalcSums — single aggregation query at DB level
procedure GetTotalSales(CustomerNo: Code[20]): Decimal
var
Entry: Record "Cust. Ledger Entry";
begin
Entry.SetRange("Customer No.", CustomerNo);
Entry.CalcSums(Amount);
exit(Entry.Amount);
end;For FlowFields accessed outside a page context, always call CalcFields before reading:
Customer.SetLoadFields("Balance (LCY)");
Customer.Get(CustomerNo);
Customer.CalcFields("Balance (LCY)"); // required — not auto-calculated in codePattern 3: Temporary Tables / Dictionary / List
Pre-load data once, then process in-memory multiple times.
// ✅ Temporary table — structured record data, multi-pass processing
procedure ProcessSalesData(var TempSalesLine: Record "Sales Line" temporary)
var
SalesLine: Record "Sales Line";
begin
SalesLine.SetLoadFields("No.", Quantity, "Unit Price", Amount);
if SalesLine.FindSet() then
repeat
TempSalesLine := SalesLine;
TempSalesLine.Insert();
until SalesLine.Next() = 0;
// Process in-memory — zero additional DB hits
ApplyDiscounts(TempSalesLine);
CalculateTotals(TempSalesLine);
end;
// ✅ Dictionary — key-value lookup cache
procedure CacheCustomerNames(): Dictionary of [Code[20], Text]
var
Customer: Record Customer;
Cache: Dictionary of [Code[20], Text];
begin
Customer.SetLoadFields("No.", Name);
if Customer.FindSet() then
repeat
Cache.Add(Customer."No.", Customer.Name);
until Customer.Next() = 0;
exit(Cache);
end;
// ✅ List — simple value collection
procedure GetBlockedCustomerNos(): List of [Code[20]]
var
Customer: Record Customer;
Result: List of [Code[20]];
begin
Customer.SetRange(Blocked, Customer.Blocked::All);
Customer.SetLoadFields("No.");
if Customer.FindSet() then
repeat
Result.Add(Customer."No.");
until Customer.Next() = 0;
exit(Result);
end;Pattern 4: Batch Processing and Avoiding Nested DB Calls
Move database operations outside loops; batch writes.
// ❌ Nested DB call inside loop — O(n²) database hits
if MainTable.FindSet() then
repeat
OtherTable.SetRange(Field, MainTable.Field);
if OtherTable.FindSet() then // DB call per iteration
repeat
// process
until OtherTable.Next() = 0;
until MainTable.Next() = 0;
// ✅ Pre-load into temp table, then join in memory
if OtherTable.FindSet() then
repeat
TempOther := OtherTable;
TempOther.Insert();
until OtherTable.Next() = 0;
if MainTable.FindSet() then
repeat
if TempOther.Get(MainTable.Field) then
// process — no DB hit
until MainTable.Next() = 0;Batch writes — collect changes, write once:
// ✅ Calculate all values first, then single Modify
procedure UpdateCustomerStats(CustomerNo: Code[20])
var
Customer: Record Customer;
TotalBalance: Decimal;
LastPaymentDate: Date;
begin
CalculateCustomerTotals(CustomerNo, TotalBalance, LastPaymentDate);
Customer.SetLoadFields("Balance (LCY)", "Last Payment Date");
if Customer.Get(CustomerNo) then begin
Customer."Balance (LCY)" := TotalBalance;
Customer."Last Payment Date" := LastPaymentDate;
Customer.Modify(true); // single write
end;
end;Scale-aware processing:
procedure UpdatePricesForItems(var Item: Record Item)
begin
if Item.Count() > 1000 then
UpdatePricesInBatches(Item) // job queue / chunked
else
UpdatePricesDirectly(Item);
end;Pattern 5: FlowField Optimization (AL0896)
Circular FlowField references cause infinite evaluation (AL0896 error):
❌ Circular dependency:
Table Customer: FlowField "Total Sales" → CalcFormula from Sales Statistics
Table Sales Statistics: FlowField "Customer Balance" → CalcFormula from Customer
Compiler error AL0896: recursive dependency detectedResolution strategies (in order of preference):
- Break the chain — convert one FlowField to a regular field updated via trigger
- Use CalcSums — replace FlowField with explicit
CalcSumsin code - Restructure — move the calculation to a dedicated codeunit called on demand
- SumIndexFields — use SIFT keys for frequently summed values
Avoid FlowFields:
- With complex nested
CalcFormulaexpressions - Called inside
repeat...untilloops (callCalcFieldsonce, outside loop if possible) - Where the source table is large and unfiltered
Workflow
Step 1: Triage (Static Code Analysis)
Scan the codebase before profiling to identify structural issues:
Patterns to detect manually or with search + problems:
FindSet()/FindFirst()without precedingSetRange/SetFilterSetLoadFieldsplaced afterSetRange(wrong order)- Database calls (
Get,FindSet,FindFirst) insiderepeat...until CalcFieldsinside loops- FlowField
CalcFormulareferencing tables that reference back (AL0896) Commitinside loops (locks + performance risk)- Missing keys for columns used in
SetRange
Severity assessment:
- 🔴 Critical — DB call in loop over large table, circular FlowField, timeout-causing query
- 🟡 High — Missing
SetLoadFieldsin high-frequency path, nested loops - 🟢 Medium — Suboptimal aggregation (loop instead of
CalcSums) - 🔵 Low — Best-practice suggestion, minimal current impact
Step 2: Profile (Runtime Measurement)
Generate CPU profile for runtime bottleneck identification:
Capture a CPU profile in VS Code (VS Code command — not an agent tool)Analyze profile for:
- Hotspots — procedures with highest cumulative execution time
- Frequency — procedures called most often (especially in loops)
- DB operations — expensive
FindSet/Getcalls - FlowField evaluations — unexpectedly costly
CalcFields
Compare before/after optimizations by re-profiling after each fix.
⚠️ Human Gate — cleanup: Before clearing codelenses, confirm all findings are documented:
Clear profile codelenses in VS Code ← VS Code command (not an agent tool), only after approvalStep 3: Fix
Apply fixes in priority order (critical first). For each fix:
- Apply targeted change (Pattern 1–5 above)
- Rebuild:
al_build - Re-profile to verify improvement
Performance targets:
| Metric | Target |
| Page load | < 2 seconds |
| Report generation | < 10 seconds (standard dataset) |
| API response | < 500 ms |
| Batch processing | > 1,000 records/minute |
Step 4: Document Findings
For significant optimizations, create a triage report at .github/plans/perf-triage-<scope>.md:
# Performance Triage — <Scope>
**Date**: YYYY-MM-DD
**Analyzed**: [path or objects]
## Findings
| Severity | Location | Issue | Recommendation |
|---|---|---|---|
| 🔴 | `File.al:42` | Nested DB call in loop | Pre-load into TempTable |
| 🟡 | `File.al:87` | Missing SetLoadFields | Add SetLoadFields("No.", Name) |
## Changes Applied
1. [Change] — before/after metric
2. [Change] — before/after metric
## Remaining Items
[Issues not yet fixed, prioritized]⚠️ Human Gate — report: Review findings before saving; confirm no sensitive code patterns are exposed.
References
- SetLoadFields — Microsoft Docs
- Performance Best Practices for AL
- AL0896 — Circular FlowField
- SumIndexFields (SIFT)
- CPU Profiler
Constraints
- This skill covers analysis and fix patterns — it does NOT duplicate the passive rules in
al-performance.instructions.md(auto-applied to all.alfiles) - Do NOT modify source code automatically during triage — generate report and get approval first
- Do NOT clear profile codelenses without human confirmation
- Do NOT save triage report without human gate review
- For runtime debugging of a specific issue (breakpoints, snapshots) → load
skill-debug.md - For event subscriber performance issues → load
skill-events.md
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

