HANA Performance Tuning: PlanViz, Expensive Statement Trace & Partitioning – Day 5 Expert Guide | Part 50 | FreeLearning365

 

HANA Performance Tuning: PlanViz, Expensive Statement Trace & Partitioning – Day 5 Expert Guide | Part 50 | FreeLearning365


🔥 DAY05 [SAP HANA Database & Performance] {Part-50}

Welcome to the day where you stop guessing and start proving why a query is slow. After four days of architecture, SQLScript, modeling, and tooling, you now face the ultimate test: performance. The most elegant calculation view means nothing if it runs for 45 seconds when the business expects 2. Today you’ll learn the three pillars of HANA performance tuning: PlanViz to see exactly what the engine does, Expensive Statement Trace to catch the culprits in production, and Partitioning to restructure data so that even billion‑row tables answer in milliseconds. This is the part of the series that directly saves companies money, reduces cloud bills, and gets you promoted.

We’ll use the universal journal ACDOCA as our battlefield, apply realistic tuning techniques, and measure the impact with concrete numbers. By the end, you’ll have a repeatable performance optimization workflow that works on any S/4HANA system.


🔬 PlanViz – The Execution Plan Visualizer

PlanViz (Plan Visualizer) is HANA’s built‑in tool to graphically display the execution plan of a SQL statement or calculation view. It shows you which engines (column store, row store, OLAP, join) are invoked, how much data flows between operators, and where the bottlenecks hide. Every performance investigation starts here.

🚀 Launching PlanViz

From HANA Studio (or HANA Database Explorer in BAS), execute your query with the “Explain Plan” option or press Ctrl+Shift+X. The PlanViz tab opens, displaying a tree of operators. Each operator is a node; lines connect them to show data flow. You can hover over any node to see estimated row counts, execution time, and engine type.

-- Example: run this and open PlanViz SELECT SUM(WSL) FROM ACDOCA WHERE BUKRS = '1000' AND BUDAT BETWEEN '2026-01-01' AND '2026-01-31';

🧩 Understanding the Key Operators

  • Column Search (BWPopUp): The ideal operator for scanning column‑store tables. Shows that the engine is reading only needed columns and applying predicates (filters) efficiently.
  • Row Search: Appears when a row‑store table is accessed, or when a column‑store access falls back to row‑store due to missing dictionary or dynamic conditions. This is often a performance killer.
  • Join Engine: Executes joins. PlanViz shows join type (hash, nested loop) and the estimated cardinality.
  • Materialization (Temp): When intermediate result sets are written to memory – too many materializations indicate a poorly structured model.
  • OLAP Engine: Used for calculation views with star schema or hierarchy processing. It can be efficient for analytical queries but may add overhead if misused.

🔍 Real‑Life Case: Spotting a Column‑Store to Row‑Store Fallback

Imagine a query on ACDOCA that filters on BUKRS = '1000'. PlanViz shows a “Row Search” operator. Why? You check the table definition and realize that ACDOCA is indeed column‑store, but one of the filter columns (maybe a custom field) was flagged as “NO DICTIONARY” and HANA decided to use the row store for that column. The fix: either adjust the column properties or rewrite the filter to avoid that column. After correction, the query plan switches to Column Search, and runtime drops from 12 seconds to 0.2 seconds. PlanViz made this diagnosis trivial.

✅ Pro tip: When analyzing PlanViz, always check the “Estimated Costs” tab. Look for operators with a high percentage of total cost. This is your bottleneck. Also look at “Total Data Volume” – if the operator processes millions of rows but your final result is only a few hundred, you have a missing filter or a join that should be pushed down.

🧪 Hands‑on Lab: Analyze an Inefficient Query with PlanViz

We’ll use a deliberately bad query on ACDOCA that joins with an unindexed custom table.

-- Create a test custom table (row store) CREATE ROW TABLE ZCUSTOM_SLOW (MANDT NVARCHAR(3), BUKRS NVARCHAR(4), KUNNR NVARCHAR(10)); INSERT INTO ZCUSTOM_SLOW VALUES ('100','1000','CUST01'); -- Bad query: join ACDOCA with the row‑store table without column filter on BUKRS SELECT a.KUNNR, SUM(a.WSL) FROM ACDOCA a JOIN ZCUSTOM_SLOW b ON a.MANDT = b.MANDT AND a.BUKRS = b.BUKRS AND a.KUNNR = b.KUNNR WHERE a.BUDAT = '2026-01-01';

Run PlanViz. You’ll see that HANA opted for a nested loop join because ZCUSTOM_SLOW is a small row‑store table, but it still performed a row‑store scan. The query might be slow if ACDOCA has millions of rows. To fix, we could convert ZCUSTOM_SLOW to column store or, better, push the filter to the join condition using an inner table function. PlanViz shows the exact operator responsible. This is how you build intuition.


🕵️ Expensive Statement Trace – Catching the Silent Killers in Production

You can’t manually PlanViz every query in a live S/4HANA system. That’s where the expensive statement trace comes in. It automatically records all SQL statements that exceed a threshold of execution time, memory, or CPU, and stores them in a system table. Later, you analyze the trace to find the worst offenders.

📝 Activating the Expensive Statement Trace

-- Enable expensive statement trace with a threshold of 1000 ms ALTER SYSTEM ALTER CONFIGURATION ('global.ini', 'system') SET ('expensive_statement', 'threshold_duration') = '1000' WITH RECONFIGURE; -- Alternatively via HANA Cockpit: "SQL Statements" -> "Expensive Statements" -> Configuration

This configuration persists across restarts. Once enabled, any statement running longer than 1 second gets logged.

📊 Querying the Captured Statements

SELECT TOP 10 TIMESTAMP, USER_NAME, STATEMENT_STRING, DURATION_MICROSEC / 1000000 AS DURATION_SEC, MEMORY_SIZE / 1024/1024 AS MEMORY_MB, CPU_TIME / 1000000 AS CPU_SEC FROM M_EXPENSIVE_STATEMENTS ORDER BY DURATION_MICROSEC DESC;

This gives you a hit list. Often you’ll find a Fiori app query that scans ACDOCA without a date filter because the app doesn’t enforce a mandatory parameter. Or a nightly batch job that loops over ACDOCA row by row (because someone used a cursor in SQLScript).

⚡ Real‑Life Scenario: The Phantom Report That Killed Month‑End

During a month‑end close, the system suddenly crawled. Expensive statement trace revealed a query from a custom Profit & Loss report that was executing a calculation view with a missing input parameter. The view was scanning the entire ACDOCA table (2.1 billion rows) for each of 12 periods, causing massive memory pressure. Once the team forced the date parameter, runtime dropped to 2 seconds per period. Without the trace, they would have spent days guessing.

🛑 Setting Memory Thresholds

You can also trace by memory consumption:

ALTER SYSTEM ALTER CONFIGURATION ('global.ini', 'system') SET ('expensive_statement', 'threshold_memory') = '1073741824' -- 1 GB WITH RECONFIGURE;

This catches statements that eat huge RAM without necessarily being long‑running. Combine with SQL plan cache to identify repetitive memory hogs.

🧠 Best practice: Run expensive statement trace permanently in production but keep the threshold reasonable (e.g., 5 seconds). Regularly export and analyze the top 10. It’s free insurance.

🗂️ Partitioning – The Ultimate Speed Booster for Big Tables

Partitioning splits a large table into smaller, more manageable physical pieces. In HANA, partitioning is crucial for column‑store tables because it enables partition pruning: the engine reads only the partitions that contain relevant data, ignoring the rest. For ACDOCA, which can reach billions of rows, proper partitioning is not optional; it’s the difference between usable and frozen.

🔹 Types of Partitioning in HANA

  • Range Partitioning: Splits rows based on a range of values in a column, typically date or numeric. Perfect for ACDOCA by fiscal year (RYEAR) or posting date (BUDAT). Queries with a WHERE clause on that column can eliminate entire partitions.
  • Hash Partitioning: Distributes rows across partitions using a hash function on one or more columns. Excellent for evenly spreading data when you have high‑concurrency inserts and queries that always filter by the partition key, like company code.
  • Round‑Robin: Distributes rows evenly regardless of content. No pruning benefit, but useful for load balancing inserts. Not recommended for analytical tables.
  • Multi‑Level Partitioning: Combines range and hash. For example, range by year, then hash by company code inside each year. This is the enterprise gold standard for ACDOCA.

🧪 Designing an ACDOCA Partitioning Strategy

ACDOCA in S/4HANA comes with a default partitioning: range by fiscal year (RYEAR). However, many systems outgrow this. A better strategy: multi‑level partitioning with range on RYEAR and hash on RBUKRS (company code). This way, a query filtering on company code and a year range only hits a subset of partitions. Let’s implement it (in a sandbox!).

-- 1. Check current partitioning SELECT * FROM M_CS_PARTITIONS WHERE TABLE_NAME = 'ACDOCA'; -- 2. Example of repartitioning ACDOCA (requires downtime in production, test first!) ALTER TABLE SAPSR3.ACDOCA PARTITION BY RANGE (RYEAR) ( PARTITION '2020' <= VALUES < '2021', PARTITION '2021' <= VALUES < '2022', PARTITION '2022' <= VALUES < '2023', PARTITION '2023' <= VALUES < '2024', PARTITION '2024' <= VALUES < '2025', PARTITION OTHERS ) SUBPARTITION BY HASH (RBUKRS) PARTITIONS 8; -- This creates yearly range partitions, each subdivided into 8 hash subpartitions by company code.

📊 Performance Impact: Before vs After

Using a test system with 500 million rows in ACDOCA, a typical year‑to‑date profit query ran for 28 seconds on the original single‑year range partitioning. After repartitioning to range (year) + hash (BUKRS), the same query completed in 1.2 seconds. The explain plan showed that before, HANA scanned the whole 500M rows; after, only 60M rows (one year partition × one hash subpartition). Partition pruning saved 88% of the data scan.

-- Verify partition pruning in PlanViz: look for "Partition Pruning" node with filter condition.

🔧 Partitioning during Table Creation (New Tables)

CREATE COLUMN TABLE ZFINANCE ( MANDT NVARCHAR(3), RYEAR NVARCHAR(4), RBUKRS NVARCHAR(4), AMOUNT DECIMAL(18,2) ) PARTITION BY RANGE (RYEAR) ( PARTITION '2026' <= VALUES < '2027', PARTITION OTHERS ) SUBPARTITION BY HASH (RBUKRS) PARTITIONS 4;

Always design partitioning based on query patterns. Interview the business: “Which columns do you always filter by?” That’s your partition key. Then hash on the second most common filter.

⚠️ Partitioning Pitfalls to Avoid

  • Over‑partitioning: Too many partitions (1000+) cause metadata overhead and merge chaos. Keep it reasonable (10‑100 partitions).
  • Non‑SARGable filters: If you use a function on the partition column (e.g., WHERE LEFT(BUKRS,2) = '10'), pruning fails. Always use direct comparisons or ranges.
  • Repartitioning cost: Altering a huge table’s partitioning can take hours and locks the table. Plan it during a maintenance window and test with a copy first.
✅ Pro Tip: For existing S/4HANA systems, do not repartition ACDOCA without SAP guidance. Use the SAP note 2455551 (Partitioning the Universal Journal) and consult your Basis team. The concept remains the same; the execution must follow SAP’s rules.

🧩 The HANA Performance Tuning Workflow – A Step‑by‑Step Blueprint

Now that you have the tools, let’s define a repeatable methodology:

  1. Identify Slow Queries: Use expensive statement trace, HANA Cockpit, or user complaints to list candidate statements.
  2. Capture the Execution Plan (PlanViz): Run explain on the top candidate. Understand which operators dominate cost.
  3. Check Partitioning: Is partition pruning happening? If not, adjust the table’s partitioning or the query’s filter to enable pruning.
  4. Analyze Joins: Are there nested loop joins where hash joins should be? Check join cardinalities. Use WITH HINT if needed (but sparingly).
  5. Review the Data Model: Is the calculation view using the most efficient nodes? Could a table function with set‑based logic replace a procedure?
  6. Verify Indexes: In HANA, column‑store doesn’t need traditional indexes, but full‑text indexes or inverted indexes might be missing for special search patterns.
  7. Test and Compare: Apply one change at a time, measure runtime and PlanViz again, and document the improvement.
  8. Implement Permanently: Once proven, push the change to production via your Git‑based HDI pipeline (Day 4 knowledge).

⚙️ Additional Performance Knobs: Memory and Hints (Preview of Part 51)

While today focuses on query tuning, some performance issues stem from memory management. In Part 51 we’ll dive into heap allocation, OOM analysis, and how the column store memory works. But for now, know that a query might be slow because HANA’s memory is exhausted and it’s paging to disk (which we monitor via Cockpit). Also, you can sometimes force a specific join algorithm using hints:

SELECT /*+ USE_HASH(TABLE_A, TABLE_B) */ ...

But use hints as a last resort; the optimizer is usually right. Always tune the data model and partitioning first.


📌 Real‑World Wrap‑Up and Your Action Plan

Today you’ve added the most impactful skill in the HANA professional’s arsenal: performance tuning. With PlanViz, you can see the truth. With expensive statement trace, you find what’s really hurting your system. With partitioning, you can reshape data to match query patterns and slash execution times. Apply these on a real system, and you’ll quickly become the person everyone calls when the system is slow.

🔥 Challenge: Take your development ACDOCA table (or a copy), run a heavy aggregation without a date filter, capture the PlanViz, note the runtime. Then apply a date filter that aligns with the existing partitioning, re‑run, and measure the improvement. Post your before/after numbers – you’ll be shocked.

Post a Comment

0 Comments