Insufficient System Memory: Msg 701 in a Resource Pool

Msg 701 means there is insufficient system memory in a resource pool, and the pool is what ran out. This post reproduces the error on SQL Server 2025 with a memory-optimized table. It also shows a second message that you get when rows fill the pool.

Gouache painting: a small wooden coat rack in a hallway with every peg full and a stack of folded coats leaning against the wall, one last coat in a basket with nowhere to hang; one vermilion scarf on the basket coat

What the Error Says

The full text reads: There is insufficient system memory in resource pool ‘X’ to run this query. SQL Server raises it as Msg 701, Level 17. The pool name tells you which pool ran dry.

Memory-optimized tables are a clean way to reproduce it. They never page out, so their rows and their hash index buckets stay in RAM. When the database is bound to a small pool, the pool can refuse an allocation. The server can still have plenty of free memory.

On SQL Server 2025 the same script printed Msg 701 in some runs and Msg 41805 in others. Both mean that the pool refused memory. SQL Server 2014 reported the row case as Msg 701 with State 103, and 2025 still does in some runs.

Build a Small Pool

The first script records the Resource Governor state and creates a pool of 2 percent. The reconfigure switches the feature on if it was off. The cleanup at the end switches it off again, but only when it was off before.

SELECT is_enabled INTO #rg_before FROM sys.resource_governor_configuration;
CREATE RESOURCE POOL SmallPool WITH (MIN_MEMORY_PERCENT = 2, MAX_MEMORY_PERCENT = 2);
ALTER RESOURCE GOVERNOR RECONFIGURE;

The next script creates the database. It reads the default data folder of the instance, so it runs without edits. Keep the same query window open until the cleanup, because the temporary table lives in that session.

IF DB_ID(N'Msg701Demo') IS NULL
BEGIN
    DECLARE @path nvarchar(260) = CONVERT(nvarchar(260), SERVERPROPERTY('InstanceDefaultDataPath'));
    DECLARE @sql nvarchar(max) = N'CREATE DATABASE Msg701Demo
ON PRIMARY (NAME = Msg701Demo_data, FILENAME = N''' + @path + N'Msg701Demo.mdf''),
FILEGROUP Msg701Demo_mod CONTAINS MEMORY_OPTIMIZED_DATA (NAME = Msg701Demo_mod, FILENAME = N''' + @path + N'Msg701Demo_mod'')
LOG ON (NAME = Msg701Demo_log, FILENAME = N''' + @path + N'Msg701Demo.ldf'')';
    EXEC (@sql);
END

Now bind the database to the pool. The binding becomes active when the database goes offline and online. Then create a table with 8,000-byte rows.

EXEC sys.sp_xtp_bind_db_resource_pool @database_name = N'Msg701Demo', @pool_name = N'SmallPool';
USE master;
ALTER DATABASE Msg701Demo SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE Msg701Demo SET ONLINE;
GO
USE Msg701Demo;
GO
CREATE TABLE dbo.Pages (Id int NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024), Body char(8000) NOT NULL) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY);

Read the Ceiling First

SELECT name, target_memory_kb, max_memory_kb, used_memory_kb FROM sys.dm_resource_governor_resource_pools WHERE name = N'SmallPool';
nametarget_memory_kbmax_memory_kbused_memory_kb
SmallPool21,74439,63212,232

The pool already holds 12,232 KB, the cost of the database with one empty table. The max_memory_kb column is the ceiling. Read it before you test, because it moved between my runs. For pools of 1 to 10 percent, it was sometimes that percent of server memory and sometimes about 1.1 GB.

A small pool fails early because the database costs memory before the first row. The overhead here was 12,232 KB. At 2 percent of a server, that fixed cost is a large part of the pool. On a production pool of many gigabytes, it disappears into the noise, so test the share, not only the size.

Reproduce Msg 701

The first repro asks for more than the server can give. A hash index allocates 8 bytes per bucket at creation. The script sizes the index at twice the memory target of the server, so the request cannot succeed. It skips itself when that size would pass the maximum bucket count of 1,073,741,824.

DECLARE @target bigint = (SELECT committed_target_kb FROM sys.dm_os_sys_info);
DECLARE @buckets bigint = @target * 256;
IF @buckets <= 1073741824
BEGIN
    BEGIN TRY
        DECLARE @ddl nvarchar(max) = N'CREATE TABLE dbo.BigIndex (Id int NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = ' + CONVERT(nvarchar(20), @buckets) + N')) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)';
        EXEC (@ddl);
    END TRY
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS Severity, ERROR_STATE() AS State, ERROR_MESSAGE() AS Message, @buckets AS BucketsAsked;
    END CATCH
END
ErrorNumberSeverityStateMessageBucketsAsked
70117137There is insufficient system memory in resource pool ‘SmallPool’ to run this query.293,351,424

That is Msg 701, Level 17, State 137. The table does not exist afterwards. Other runs printed a different number for the same refusal, as the next sections show.

Fill the Pool With Rows

The second repro is the classic one. It inserts one row at a time until the pool says no. It runs only when the pool ceiling is under 2 GB, so it cannot eat a large server. The inserts are single rows, so every earlier insert stays committed.

SET NOCOUNT ON;
DECLARE @i int = 0;
IF (SELECT max_memory_kb FROM sys.dm_resource_governor_resource_pools WHERE name = N'SmallPool') < 2097152
BEGIN
    WHILE @i < 500000
    BEGIN
        BEGIN TRY
            INSERT INTO dbo.Pages (Id, Body) VALUES (@i + 1, 'x');
            SET @i += 1;
        END TRY
        BEGIN CATCH
            SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS Severity, ERROR_STATE() AS State, ERROR_MESSAGE() AS Message, @i AS RowsInserted;
            BREAK;
        END CATCH
    END
END
ErrorNumberSeverityStateMessageRowsInserted
70117103There is insufficient system memory in resource pool ‘SmallPool’ to run this query.6,685

This is Msg 701, Level 17, State 103, the same numbers as in the old error. The pool accepted 6,685 rows of 8,000 bytes and refused the next one. The count depends on the ceiling at the time.

Two Messages, One Cause

I ran this draft five times on the same instance, with no change to the script. Some runs printed Msg 701 and others printed Msg 41805. The two messages never mixed inside one run. The 41805 text reads: There is insufficient memory in the resource pool ‘SmallPool’ to run this operation on memory-optimized tables.

RunIndex reproFill reproRows inserted
1Msg 701Msg 7016,685
2Msg 701Msg 7017,007
3Msg 41805Msg 418057,329
4Msg 41805Msg 4180511,200
5Msg 701Msg 70114,630

I did not find out what decides the number. Treat 701 and 41805 as one problem. If you write error handling for memory-optimized loads, catch both.

Fix It

Four fixes exist, and I tested two. Delete rows you no longer need. Size the hash index for the real row count. Move cold data to a disk-based table. Or give the pool more room by raising MAX_MEMORY_PERCENT and running the reconfigure again. I did not test the last two. The ceiling moved by itself between my runs, so a before and after would prove nothing.

Delete first when the rows are old. It is the fix that needs no design change and no restart. It also shows you how the pool behaves, which is useful before you touch the settings.

Deleting does not free the memory at once. A background garbage collector removes the old row versions. The script below deletes every row, reads the pool, waits ten seconds and reads again. Then it proves the pool accepts an insert, and creates an index with a sane bucket count.

DELETE FROM dbo.Pages;
SELECT used_memory_kb AS UsedRightAfterDelete FROM sys.dm_resource_governor_resource_pools WHERE name = N'SmallPool';
WAITFOR DELAY '00:00:10';
SELECT used_memory_kb AS UsedTenSecondsLater FROM sys.dm_resource_governor_resource_pools WHERE name = N'SmallPool';
INSERT INTO dbo.Pages (Id, Body) VALUES (1, 'x');
SELECT COUNT(*) AS RowsNow FROM dbo.Pages;
GO
CREATE TABLE dbo.SmallIndex (Id int NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 131072)) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY);
SELECT name FROM sys.tables WHERE name IN (N'BigIndex', N'SmallIndex');
ReadingValue
UsedRightAfterDelete (KB)73,392
UsedTenSecondsLater (KB)23,280
RowsNow1
Tables foundSmallIndex only

The memory was still high right after the delete and lower ten seconds later. The next insert worked. The 131,072-bucket index needs a 1 MB array and was created without trouble.

Card titled Msg 701: Pool Out of Memory: Msg 701: Level 17, insufficient system memory in pool; Also seen: Msg 41805, same cause; Fill test: 6,685 rows of 8,000 bytes, then State 103; Check: max_memory_kb and used_memory_kb of the pool; Fix: delete rows, size BUCKET_COUNT, raise MAX_MEMORY_PERCENT. Tip: Catch both 701 and 41805 in error handling.

Is More Memory the Answer?

You could say the answer is always more memory. Fair point, when the data is needed and the server has room. But a bucket array of several gigabytes for a few thousand rows is a design error. It is not a shortage.

A message about insufficient system memory is a sizing question before it is a hardware question. Check the bucket count first. It is the cheapest fix. Then look at the rows: old data in a memory-optimized table is the most expensive storage you own.

A Short Checklist

  • Read max_memory_kb and used_memory_kb of the pool before you load data.
  • Size BUCKET_COUNT near the expected row count, not near the biggest number you can type.
  • Watch the used share of the pool as the data grows.
  • Remember that deletes free memory later, not at once.
  • Record the Resource Governor state before a test and restore it after.

When you finish testing, remove what you built. The script puts Resource Governor back to the state it had at the start.

USE master;
GO
DROP DATABASE Msg701Demo;
DROP RESOURCE POOL SmallPool;
ALTER RESOURCE GOVERNOR RECONFIGURE;
IF (SELECT is_enabled FROM #rg_before) = 0 ALTER RESOURCE GOVERNOR DISABLE;

Msg 701 is not a fault in your query, it is a pool telling you how much room it has.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

In-Memory OLTP, Resource Governor, SQL Error Messages, SQL Memory, SQL Scripts
Previous Post
Bind Resource Governor to a Memory-Optimized Database
Next Post
SQL SERVER – Error: Fix: Msg 5133, Level 16, State 1, Line 2 Directory lookup for the file failed with the operating system error 2(The system cannot find the file specified.) – Part 2

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.