Most SAP HANA memory investigations start in the wrong place: someone looks at the operating system, sees HANA using nearly all the physical RAM, and concludes the system is out of memory. HANA is designed to do exactly that. It is not evidence of a problem.
HANA preallocates a pool of memory from the operating system and manages it internally. The pool grows over time up to a predefined global allocation limit and is then reused efficiently for table data, thread stacks, temporary results, and system data structures. When the memory manager needs more than the pool currently holds, it asks the OS for more — up to the limit. High resident memory is the normal steady state, not a symptom.
Getting this distinction right is the difference between a useful investigation and an expensive hardware order.
The three limits that matter
Global allocation limit. The ceiling for the whole database on a host. HANA will not preallocate beyond it.
Service allocation limit. Each service — indexserver, nameserver, and the rest — has its own limit. Because all services together cannot exceed the global limit, each one also has an effective allocation limit: how much it can realistically consume given what the other services are using right now.
That third number is the one worth watching. A service can be well under its own configured limit and still be constrained, because another service on the same host has taken the headroom.
Statement memory limit. A per-statement ceiling. Without one, a single badly written query can consume the memory that thousands of well-behaved ones are sharing. With one, that query fails and everything else keeps running.
If your production HANA has no statement memory limit configured, that is usually the highest-value change available, and it costs nothing. Configure it high enough that legitimate work is unaffected, and you convert "the system went down" into "one report failed."
What happens when the limit is reached
This is the part worth understanding precisely, because the behaviour is graceful right up until it isn't.
When the allocation limit is reached and the pool is exhausted, the memory manager cannot allocate without giving something up. In order:
- Buffers and caches are released.
- Column store tables are unloaded — column by column, in least-recently-used order — down to a preset lower limit.
- If that is still not enough, an out-of-memory failure occurs.
Two things follow from this.
Unloads are the early warning. By the time you see an OOM dump, the system has already been shedding data for a while. M_CS_UNLOADS is where this shows up, and a rising unload count is the most reliable leading indicator HANA gives you. It is worth alerting on.
Unloads cost you later, not now. Unloading is instant relief. The bill arrives when a query needs that column and has to reload it from disk — which turns a millisecond column scan into something users notice. This is why "HANA got slower over the last month and nothing changed" is so often an unload problem: nothing changed except that data no longer fits.
On a scale-out landscape, unloading is handled host by host, so column partitions are unloaded only on hosts with an acute shortage. This is one reason memory problems in scale-out systems can look inconsistent — one node is shedding data while another has headroom.
Reading an out-of-memory dump
When HANA does hit OOM, it writes a dump to the trace directory. The dump names the allocator that failed and shows the memory breakdown at the moment of failure.
The allocator that failed is often not the cause. Memory is shared. A statement that materialises a huge intermediate result can exhaust the pool, and the allocation that actually fails may belong to something entirely unrelated that happened to ask next. Reading the failing allocator as the culprit sends investigations in the wrong direction routinely.
What to look for instead:
- The largest consumers at the time of the dump. Is one allocator disproportionately large, or is the total simply at the limit with everything proportionate?
- The statement running at the time. A single statement holding gigabytes points at a query problem. Nothing conspicuous points at a sizing or growth problem.
- The trend before the event. Was memory climbing steadily for hours, or did it spike in seconds? Steady climb means growth or a leak-shaped pattern; a spike means one statement.
The system views worth knowing:
| View | Tells you |
|---|---|
M_SERVICE_MEMORY | Per-service consumption against the effective allocation limit |
M_HEAP_MEMORY | Usage broken down by allocator |
M_CS_TABLES | Memory held per column store table |
M_CS_UNLOADS | What has been unloaded, and when |
M_EXPENSIVE_STATEMENTS | Statements that crossed the cost threshold — including memory |
SAP maintains a collection of SQL statements for HANA diagnostics (distributed through their support notes) that wraps most of these into usable reports. If you are running HANA in production and have not deployed it, do that before writing your own.
The four causes, in the order they actually occur
1. A runaway statement
One query builds an enormous intermediate result. Common shapes: a join with a missing or ineffective filter, a Cartesian product from an incorrect join condition, an aggregation over a fact table with no time restriction, or a CDS view stacked deeply enough that the optimizer materialises far more than the final result set.
*Signal:* memory spikes in seconds; M_EXPENSIVE_STATEMENTS shows one statement with very high memory; the system recovers on its own after the statement ends or is killed.
*Fix:* fix the statement. Then set a statement memory limit so the next one is contained.
2. Delta merge backlog
HANA writes to a write-optimised delta store and periodically merges it into the compressed main store. If merges are not keeping up — because they are failing, disabled, or contending with load — the delta grows, uncompressed, and consumes memory far out of proportion to the data volume.
*Signal:* memory climbs steadily across a day; specific tables show large delta sizes; the pattern correlates with load rather than with a particular query.
*Fix:* find out why merges are not completing. This is far more often a symptom of something else — a lock, a failing job, an overloaded system — than a merge configuration problem.
3. Genuine growth beyond the sizing
The estate has grown. The fact tables that were sized three years ago are now larger than the node. Nothing is wrong; the system has simply outgrown its hardware.
*Signal:* unloads increasing gradually over months; no single statement responsible; table sizes tracking business growth.
*Fix:* this is the case where more memory is the right answer — or, better, moving data that does not need to be hot. Native Storage Extension (NSE) makes columns page-loadable so warm data lives on disk with a buffer cache rather than fully in memory, and data aging moves historical data out of the hot set entirely. Both are usually cheaper than another node, and both require deciding what is genuinely hot, which is the actual work.
4. Partitioning that has not kept up
A single large table on one node, unpartitioned, eventually cannot be held. HANA has per-table and per-partition size constraints, and an unpartitioned table approaching them creates memory pressure that no amount of query tuning fixes.
*Signal:* one table dominating M_CS_TABLES; problems concentrated on one node in a scale-out system.
*Fix:* partition it, on a key that matches how it is queried. Partitioning on a column nobody filters by adds overhead and solves nothing.
What not to do
Raising the allocation limit to stop the errors. If the limit is already close to physical memory, raising it moves the failure from HANA's controlled unloading into the operating system's uncontrolled memory pressure, where the outcome is worse and less diagnosable.
Restarting to clear memory. It works, briefly, and it destroys the evidence. It also means the first hours after restart are slow while data reloads — so the visible symptom often gets worse before it gets better.
Preloading everything. Marking tables for preload so they are always resident sounds like it prevents unload-related slowness. In a system that is already under pressure it removes the memory manager's ability to make good choices, and makes the eventual failure sharper.
Treating unloads as normal. In a correctly sized system running well-behaved statements, routine column store unloads should not be happening. Once they are background noise, you have lost your best early warning.
A monitoring baseline
If you take nothing else from this, monitor these four:
M_CS_UNLOADS— count over time, alerting on any sustained increase- Used memory against the effective allocation limit per service, not against physical RAM
- Delta store sizes for your largest tables
- Statement memory high-water marks from
M_EXPENSIVE_STATEMENTS
That set catches all four causes above well before the first OOM dump, and it distinguishes between them, which is the part that determines whether the fix is a query change, a job fix, or a purchase order.
Memory work of this kind is a standing part of our SAP HANA optimization service, and it is one of the areas where a few hours of proper diagnosis routinely saves a hardware upgrade that would not have fixed the problem.
Related reading
- SAP HANA Performance Tuning: Techniques That Actually Work — query-level optimization
- SAP HANA Performance Troubleshooting — diagnosing a slow system