ABAP Debugging & Performance Tuning – ST05, SAT, EXPLAIN, Index Strategy: A 25,000+ Word Hands-On Masterclass
Day 10 of the S/4HANA ABAP Development & Fiori Track
By @FreeLearning365 and Tech Partner @techbook24
Introduction: The Dashboard That Almost Died
It’s 8:55 AM. The procurement dashboard that Arjun proudly deployed last week is already under fire. Mr. Keller, the procurement manager, clicks the “Aging Analysis” tile. He waits. 5 seconds. 15 seconds. 30 seconds. The screen finally loads with data, but Mr. Keller has already opened a ticket: “Dashboard is too slow – unacceptable.” The same CDS views and OData services that looked fast in the sandbox with 50 POs are now gasping under 50,000 POs in production. Arjun’s reputation is on the line.
Performance tuning is not an afterthought; it’s a core discipline. In S/4HANA, the immense power of HANA can mask bad code — until it doesn’t. A missing index, a SELECT inside a loop, or an inefficient CDS association can bring even the fastest hardware to its knees. In this tutorial, we will diagnose and fix the aging report step by step, using every major performance tool in the ABAP ecosystem. You’ll learn not only how to use the debugger, ST05, SAT, and EXPLAIN, but also how to think like a performance engineer — anticipating bottlenecks and building for speed from day one. Every scenario is grounded in real TechBook24 data, and every fix has measurable results. Let’s save the dashboard.
1. ABAP Debugger – The First Line of Investigation
1.1 Setting Strategic Breakpoints
Before touching any trace tool, Arjun needs to understand what’s happening. He opens the report ZR_PO_AGING in SE80, sets a breakpoint at the START-OF-SELECTION event, and executes. The debugger opens. He uses F5 (step into) to trace the flow. He notices the program calls a function module to fetch vendor names one by one — a red flag.
Screenshot Description: ABAP Debugger showing the breakpoint hit at the function call, with the internal table displayed on the right.
1.2 Watchpoints – Tracking Variable Changes
He suspects that a field lv_total is being recalculated unnecessarily. He creates a watchpoint: in the debugger, go to the “Watchpoints” tab, enter LV_TOTAL, and set it to “Changed”. The next time the value changes, the debugger stops exactly at that line. This reveals that a loop is modifying lv_total multiple times due to a duplicate record. The watchpoint saved him hours of stepping.
1.3 Debugging Background Jobs and Update Tasks
The aging report also runs as a nightly job, and sometimes it’s slow in background. Arjun shows how to debug a background job: from SM37, select the job and click “Debug”. For update task debugging, he uses /H in the session and selects “Update” in the debugging options. He rarely needs this, but it’s vital knowledge.
1.4 The “Jump To” Trick and Memory Analysis
In the debugger, you can right-click a statement and select “Jump to” (or use F6 to jump to the cursor). This allows skipping time-consuming sections during testing. Arjun uses it to bypass the email sending part while tuning the data selection. He also uses the memory analysis tool (Goto → Memory Analysis) to see which internal table is consuming the most memory — a crucial clue for performance issues.
2. ST05 – SQL Trace, The Truth Revealer
2.1 Activating and Reading an SQL Trace
Arjun activates the SQL trace: ST05 → Activate Trace (with options “SQL Trace” checked). He runs the report again. Then ST05 → Deactivate Trace → Display Trace. The list shows every database access, its duration (in microseconds), and the exact SQL statement.
Screenshot Description: ST05 trace list with several entries, one highlighted in red due to high duration.
2.2 Spotting the Expensive SQL – A Real Example
The trace shows a statement taking 18 seconds:
SELECT * FROM EKKO WHERE BUKRS = '1000' AND AEDAT > '20250101'
Arjun notices there’s no index on BUKRS and AEDAT combined in EKKO. The system is performing a full table scan on 1.2 million records. He also sees the statement executed 15 times (once per vendor?), which multiplies the damage. This is the primary bottleneck.
2.3 Interpreting Execution Plans with “Explain” in ST05
From the trace list, he selects the expensive statement and clicks “Explain”. The system shows the execution plan in a tree or graphical view (PlanViz). He sees “TABLE SCAN” on EKKO with an estimated cost of 45,000. He now knows exactly what to fix.
3. SAT – ABAP Runtime Analysis for Hotspot Detection
3.1 Running a SAT Trace
ST05 only shows database time. For overall ABAP performance, Arjun uses SAT (Runtime Analysis). He starts the measurement, runs the report, and ends the measurement. The result screen shows a call tree: which programs, methods, and function modules consumed the most gross/net time.
Screenshot Description: SAT result with the top consumers: a function module READ_LFA1 taking 22% and the main SELECT loop taking 60%.
3.2 Analyzing the Call Hierarchy
He drills down into the highest-cost method. It’s a loop calling a function module to read vendor data. The call count is 4,500 — one per PO line. He realizes the module does a SELECT SINGLE each time. This is a classic “SELECT in LOOP” anti-pattern. The SAT tool makes it blindingly obvious.
3.3 Comparing Before and After
After Arjun’s optimizations (replacing the loop with a single SELECT FOR ALL ENTRIES and joining vendor data in the CDS), he runs SAT again. The total time drops from 52 seconds to 0.8 seconds. He can save the trace and compare, demonstrating the improvement to the team. SAT is his proof of performance.
4. HANA Execution Plans – EXPLAIN, PlanViz, and Index Strategy
4.1 Understanding the HANA Plan
In S/4HANA, SQL statements are executed by HANA. The EXPLAIN PLAN (or using the Data Preview “Explain” tab in ADT) shows how HANA processes the query. Arjun uses the visual PlanViz tool (callable from ST05 or ADT). For his expensive statement, the plan shows:
- “COLUMN SEARCH EKKO.BUKRS” (column store scan, fast for column store), but because no combined index exists, the result set is large, and then a second “COLUMN SEARCH EKKO.AEDAT” filters with high cardinality, causing materialization overhead.
- A “JOIN” with LFA1 that uses a nested loop, probing LFA1 per row.
He realises that adding a composite index on BUKRS, AEDAT would significantly reduce the initial scan cost, even on HANA (though column store doesn’t use B-tree indexes in the same way, HANA uses inverted indexes; a secondary index can still help for specific key/value lookups). He carefully weighs the decision.
4.2 When to Create an Index (and When Not To)
In HANA, primary key is automatically the clustered column store key. Secondary indexes are less critical than in traditional row-store databases, but they can help for:
- Single-record access patterns (READ TABLE with a specific value not part of the primary key).
- Range scans on non-key columns when combined with pruning.
- Legacy SQL not optimized by the CDS view and execution engine.
Arjun decides to create a composite index on EKKO (BUKRS, AEDAT) because the reporting often filters on company code and date range. He creates it in SE11 → Indexes tab. After activation, the explain plan shows an “INDEX SEARCH” instead of full column scan, and estimated cost drops dramatically.
4.3 Over-Indexing Danger
He also warns about over-indexing: each additional index consumes memory and slows down write operations (INSERT/UPDATE/DELETE). Only create indexes that are justified by access patterns. Use the SQL Monitor (ST05/SQLM) to find queries that would benefit.
5. The Performance Anti-Patterns – Real-World Examples and Fixes
5.1 SELECT * (Asterisk) – The Database Hog
The aging report originally used SELECT * FROM EKKO. That fetched 150 columns, 90 of which were never used. HANA had to move all that data through the network. Arjun changes it to SELECT ebeln, lifnr, aedat, netwr, waers. The result set size drops by 80%. SAT shows a proportional decrease in time.
5.2 SELECT in a LOOP – The Cardinal Sin
Arjun already spotted the vendor function call inside a loop. He refactors: before the main loop, he collects all vendor numbers into a sorted internal table, then does a single SELECT ... FOR ALL ENTRIES IN @lt_vendors INTO TABLE @lt_vendor_data. Then inside the loop, he reads from lt_vendor_data using a sorted/hashed table. The number of database round-trips goes from 4500 to 1. This is the single most impactful change.
5.3 FOR ALL ENTRIES Without Sorted Key
He also notices that the internal table lt_vendors was not sorted before being used in FOR ALL ENTRIES. In older ABAP, FOR ALL ENTRIES ignores duplicates but doesn’t require sorting; however, the resulting SQL may perform better if the table is sorted and duplicates removed. He adds SORT lt_vendors BY lifnr. DELETE ADJACENT DUPLICATES FROM lt_vendors.
5.4 MOVE-CORRESPONDING in Large Loops
Inside a loop processing 50,000 rows, he finds a MOVE-CORRESPONDING to map EKPO to a custom structure. While convenient, it has overhead. He replaces it with direct field assignments or uses the newer CORRESPONDING #( ) expression, which is slightly faster but still costs. For maximum speed, he accesses fields directly via field symbols — zero copying. He demonstrates the difference with SAT.
5.5 Missing ORDER BY vs Sorting in ABAP
The report sorts the final list by vendor name. Initially, the SELECT added ORDER BY vendor_name, which forced the database to sort. With 50,000 rows, that’s expensive. Arjun removes the ORDER BY, fetches unsorted data, and lets the SALV grid sort client-side (users can click columns). The database load drops, and the first screen appears faster. Sorting in ALV is done in the UI thread, which is acceptable for moderate datasets.
6. Tuning the CDS Views – Beyond ABAP
6.1 CDS Performance Traps
Arjun examines the CDS view ZC_PurchaseOrderItem from Part 37. It has multiple associations: to EKKO, LFA1, MARA. When accessed from the OData service or ABAP, the associations are resolved at runtime. If the consumer only needs vendor name, but the view defines associations to all three, HANA still has to prepare the join possibilities. He creates a consumption view that only exposes the necessary associations and fields, reducing complexity.
6.2 Using Literals and Constants in CDS
He finds that the CDS view uses a hardcoded company code filter (BUKRS = '1000') directly in the definition. That’s fine for a single-company system, but if TechBook24 expands, it’s a bottleneck. He replaces it with a system variable or parameter, but more importantly, he ensures that the selection is pushed down and the pruning is effective. He uses the HANA execution plan for the CDS view to verify that the filter is applied at the earliest stage.
6.3 CDS Performance Testing in ADT
In Eclipse ADT, he right-clicks the CDS view and selects “Data Preview” → “Explain”. The plan shows the query execution. He can test different filter combinations directly. This is a powerful, quick way to validate CDS performance without writing a full program. He demonstrates it for the team.
7. OData Service Performance – The Gateway Bottleneck
7.1 The $top and $skip Importance
The Fiori List Report calls the OData service with $top=25 by default, but the backend OData implementation must honor this. Arjun checks the DPC method: it uses io_tech_request_context->get_top( ) and passes it to the SELECT as UP TO … ROWS. If this isn’t implemented, the backend fetches all rows and the framework pages them, causing memory and time waste. He ensures the code is correct.
7.2 Expanding Nested Entities – Avoiding N+1 Queries
When $expand=ToItems is used, the OData service must fetch items efficiently. In RAP, the framework handles it using composition, but in manual SEGW implementations, developers often make the mistake of looping over headers and fetching items one by one (the N+1 problem). Arjun checks the generated code and refactors to a single batch read of items for all headers in the current result set. He shows the ST05 trace before and after: a huge reduction in round-trips.
7.3 OData Service Compression and Caching
He also enables GZIP compression in the gateway (SICF → service node → Handler List → enable compression). For static metadata, he ensures that the metadata is cached (using ETags). This reduces perceived latency for Fiori apps.
8. The Full Performance Optimization Lab – From Slow to Blazing
We'll now consolidate the tuning of the procurement aging report into a step-by-step lab that you can replicate.
- Baseline measurement: Run SAT and ST05 on the original report ZR_PO_AGING. Record total time (52 sec) and top SQL statements.
- Fix SELECT * : Narrow the field list in the main SELECT. Re-measure: time drops to 38 sec.
- Replace function call in loop: Collect vendor keys, do one FOR ALL ENTRIES SELECT, build a hashed table, and read. Time drops to 12 sec.
- Remove ORDER BY: Let ALV handle sorting. Time drops to 8 sec.
- Add secondary index on EKKO (BUKRS, AEDAT): Time drops to 2.5 sec.
- Refactor CDS view to use a consumption view with only required associations and fields. Time drops to 1.2 sec.
- Implement paging in the OData for the Fiori version: ensure $top and $skip are respected. First page loads in 0.3 sec.
- Final SAT trace: 0.8 sec total for the full ABAP report; 0.2 sec for the Fiori list. Record and present results.
Screenshot Description: Side-by-side SAT comparison: before and after traces with dramatic reduction in bars.
9. Proactive Performance – Building for Speed
9.1 Code Review Checklist
Arjun creates a checklist for his team:
- No SELECT * in any production code.
- No SELECT inside a LOOP.
- Use FOR ALL ENTRIES with sorted, deduplicated key tables.
- Always specify UP TO n ROWS with paging.
- Validate indexes on key filter columns using ST05.
- Use SAT before transporting to QA.
- For CDS, use associations sparingly in consumption views; push calculations to the database but don’t overload.
9.2 Monitoring in Production – SQLM and Technical Monitoring
He also sets up SQL Monitor (SQLM) to capture expensive statements in production over time, allowing proactive index creation. He integrates with Solution Manager for alerting when critical transactions exceed thresholds. This ensures that performance doesn’t degrade as data grows.
10. Common Pitfalls and How to Avoid Them
- Relying solely on HANA’s speed: HANA is fast, but bad SQL can still bring it down. Always trace and measure.
- Forgetting to activate indexes in all clients: Indexes are client-independent, but test in the same landscape.
- Using
UP TO 1 ROWSwithout ORDER BY: Can return random rows and mask missing data; use with care. - Ignoring the ABAP memory consumption: A 10-million-row internal table will dump even if the SELECT is fast. Use packages (
SELECT ... INTO TABLE @DATA(lt_pack) UP TO 10000 ROWSand process in chunks).
11. Conclusion – The Speed Mindset
Arjun has rescued the procurement dashboard. Mr. Keller clicks the tile and sees results instantly. The aging report runs in under a second, even with a year’s worth of data. Arjun’s systematic approach — debugger, ST05, SAT, EXPLAIN — turned a potential crisis into a demonstration of his skill. He now has a reputation for building not just functional, but fast, scalable solutions. And you now have the toolkit to do the same.
In the next parts (41-45), we’ll tackle Advanced ABAP: Function Modules & RFCs, ALE/IDocs, Workflow, BRF+, and ABAP Cloud (RAP full model). Each topic builds on the foundation you’ve laid. But performance tuning will be the thread that runs through all of them — because fast code is good code.
Keep tracing, keep accelerating.
End of Part 40 – ABAP Debugging & Performance Tuning. Brought to you by @FreeLearning365 and tech partner @techbook24.

0 Comments
thanks for your comments!