Chapter 20 · In-Memory OLTP and Memory-Optimized Data Structures

When In-Memory OLTP Helps—and When Well-Tuned Disk-Based Tables Are Better

Choose deliberately among AGs, replication, log shipping, CDC/ETL and application replication from the information and failover contract actually required.

Advanced185–240 minutesrowstore-vs-XTP decision labSQL Server 2025 CU7 · 17.0.4065.4Same durability/business contractDisposable ServiceHubXtpLab · August 2026

Learning outcomes

In-Memory OLTP is now technically available, but the engineering decision is still open. The correct conclusion can be “keep the disk-based table.” ServiceHub needs a repeatable comparison that preserves the same business contract, uses representative concurrency and durability, records operational complexity, and treats rollback as part of the design. A specialized engine is valuable only when its end-to-end benefits exceed its memory, migration and operating costs.

01

Build comparable disk-based and memory-optimized implementations of a small ServiceHub queue.

02

Design a benchmark that records dataset, concurrency, durability, cache/warm-up and client measurements instead of fake numbers.

03

Compare correctness and operational boundaries such as cross-database/distributed transactions, HA and backup behavior.

04

Choose between hash/range XTP access, native procedures and a tuned rowstore baseline based on workload evidence.

05

Define migration, rollback and acceptance criteria that can justify either adoption or rejection of In-Memory OLTP.

Lab baseline. SQL Server 2025 (17.x), CU7 build 17.0.4065.4; database compatibility level 170 unless stated otherwise. In-Memory OLTP is available in Enterprise, Standard, and Express (but not the LocalDB installation option). SQL Server 2025 limits memory-optimized data to 32 GB per database in Standard and 352 MB per database in Express; Enterprise has no edition-specific memory-optimized-data cap beyond available resources. Enterprise Developer and Standard Developer are free for non-production development/test. The mandatory lab stays well below Express limits. Database-scoped XTP diagnostics can require VIEW DATABASE PERFORMANCE STATE and server-scoped XTP diagnostics can require VIEW SERVER PERFORMANCE STATE on modern SQL Server; use least privilege rather than sysadmin for monitoring. SSMS 22.8.2, VS Code + current MSSQL extension, or current sqlcmd are supported paths; Azure Data Studio is retired.

1. Start with equivalent schemas, not a straw-man rowstore

The disk-based baseline deserves appropriate indexes. The XTP version deserves appropriately chosen hash and ordered indexes. Both must implement the same durability and business semantics. If the disk design is missing a primary key or uses an obviously poor scan path, any “win” for XTP is meaningless.

sql · ensure the disposable XTP database exists
USE master;GOIF DB_ID(N'ServiceHubXtpLab') IS NULLBEGIN    CREATE DATABASE ServiceHubXtpLab;END;GOALTER DATABASE ServiceHubXtpLab SET RECOVERY SIMPLE;ALTER DATABASE ServiceHubXtpLab SET COMPATIBILITY_LEVEL = 170;GOUSE ServiceHubXtpLab;GOIF NOT EXISTS (SELECT 1 FROM sys.filegroups WHERE type = 'FX')BEGIN    ALTER DATABASE ServiceHubXtpLab      ADD FILEGROUP ServiceHubXtpFG CONTAINS MEMORY_OPTIMIZED_DATA;    DECLARE @base nvarchar(4000) = CONVERT(nvarchar(4000), SERVERPROPERTY('InstanceDefaultDataPath'));    IF @base IS NULL        THROW 50001, 'InstanceDefaultDataPath is unavailable. Supply a writable SQL Server data path manually.', 1;    DECLARE @folder nvarchar(4000) = @base + N'ServiceHubXtpContainer';    DECLARE @sql nvarchar(max) =      N'ALTER DATABASE ServiceHubXtpLab ADD FILE '      + N'(NAME=N''ServiceHubXtpContainer'', FILENAME=N'''      + REPLACE(@folder,'''','''''')      + N''') TO FILEGROUP ServiceHubXtpFG;';    EXEC sys.sp_executesql @sql;END;GOUSE ServiceHubXtpLab;GOIF OBJECT_ID(N'dbo.DispatchQueue') IS NULLBEGIN  CREATE TABLE dbo.DispatchQueue  (    dispatch_id bigint NOT NULL,    region_code char(3) NOT NULL,    due_at datetime2(0) NOT NULL,    status varchar(16) NOT NULL,    payload nvarchar(200) NULL,    CONSTRAINT PK_DispatchQueue PRIMARY KEY NONCLUSTERED HASH(dispatch_id)      WITH (BUCKET_COUNT=65536),    INDEX IX_DispatchQueue_Due NONCLUSTERED(due_at,region_code)  ) WITH (MEMORY_OPTIMIZED=ON,DURABILITY=SCHEMA_AND_DATA);END;GO
sql · build a fair disk-based comparison table
USE ServiceHubXtpLab;GODROP TABLE IF EXISTS dbo.DispatchQueueDisk;GOCREATE TABLE dbo.DispatchQueueDisk(  dispatch_id bigint NOT NULL CONSTRAINT PK_DispatchQueueDisk PRIMARY KEY,  region_code char(3) NOT NULL,  due_at datetime2(0) NOT NULL,  status varchar(16) NOT NULL,  payload nvarchar(200) NULL);CREATE INDEX IX_DispatchQueueDisk_DueON dbo.DispatchQueueDisk(due_at,region_code);GO;WITH n AS(  SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n  FROM sys.all_objects a CROSS JOIN sys.all_objects b)INSERT dbo.DispatchQueueDisk(dispatch_id,region_code,due_at,status,payload)SELECT 100000+n,       CASE n%4 WHEN 0 THEN 'N01' WHEN 1 THEN 'S02' WHEN 2 THEN 'E03' ELSE 'W04' END,       DATEADD(second,n,'2026-01-01T00:00:00'),       'READY',       CONCAT(N'payload-',n)FROM n;GO

Populate the memory-optimized table with equivalent rows in a fresh lab run. If Express memory capacity is tight, reduce the data set rather than pushing the quota. Benchmark results only apply to the chosen scale, so record it.

sql · seed equivalent rows into the durable memory-optimized table
IF OBJECT_ID(N'dbo.DispatchQueue') IS NULL  THROW 50030,'Run Lesson 1 setup first.',1;GODELETE dbo.DispatchQueue WHERE dispatch_id>=100001;INSERT dbo.DispatchQueue(dispatch_id,region_code,due_at,status,payload)SELECT dispatch_id,region_code,due_at,status,payloadFROM dbo.DispatchQueueDisk;GOSELECT 'disk' AS storage_kind,COUNT(*) AS rows FROM dbo.DispatchQueueDiskUNION ALLSELECT 'memory_optimized',COUNT(*) FROM dbo.DispatchQueue;GO

2. Define workload classes before collecting timing

ServiceHub has at least three materially different access patterns: point claims by dispatch ID, “next due” range scans by time/region, and writes that move status repeatedly. The hash index should help equality access; the nonclustered XTP index supports the range. A native procedure might reduce CPU for a short repeated claim operation. But an analytical report that scans thousands of rows may still belong on rowstore/columnstore rather than in a single-threaded native procedure.

sql · functional point and range queries for both storage engines
DECLARE @id bigint=105000;DECLARE @from datetime2(0)='2026-01-01T01:00:00';DECLARE @to   datetime2(0)='2026-01-01T02:00:00';SELECT dispatch_id,statusFROM dbo.DispatchQueueDiskWHERE dispatch_id=@id;SELECT dispatch_id,statusFROM dbo.DispatchQueueWHERE dispatch_id=@id;SELECT COUNT(*) AS due_rows_diskFROM dbo.DispatchQueueDiskWHERE due_at>=@from AND due_at<@to AND region_code='N01';SELECT COUNT(*) AS due_rows_xtpFROM dbo.DispatchQueueWHERE due_at>=@from AND due_at<@to AND region_code='N01';GO

First verify equal results. Then measure from the client because XTP does not map neatly onto rowstore logical-read metrics. Record client-side elapsed time, throughput and percentile latency for the same statement mix and connection behavior. Include error/retry counts. Run enough warm-up to distinguish one-time compilation/cache effects, then disclose whether the test is cold/warm and whether native procedures have already compiled after restart.

sql · measurement worksheet emitted from SQL Server
SELECT  @@VERSION AS engine_version,  SERVERPROPERTY('Edition') AS edition,  SERVERPROPERTY('ProductVersion') AS product_version,  DATABASEPROPERTYEX(DB_NAME(),'Updateability') AS updateability,  (SELECT compatibility_level FROM sys.databases WHERE database_id=DB_ID()) AS compatibility_level,  (SELECT COUNT(*) FROM dbo.DispatchQueueDisk) AS disk_rows,  (SELECT COUNT(*) FROM dbo.DispatchQueue) AS xtp_rows;GO-- Record externally for each run:-- clients/concurrency | operations | duration | p50/p95/p99 latency-- success count | 4130x conflicts/retries | CPU | log bytes/second | memory

3. Concurrency and durability can reverse a single-session result

A single session under warm cache may show little difference because a well-indexed rowstore lookup is already fast. Under many concurrent writers, XTP can remove row/page locking and latching overhead—but if all clients update the same row, optimistic conflicts can rise and retries may erase the benefit. The benchmark therefore needs the same key distribution and business contention as production.

Durability must also match. Comparing a SCHEMA_ONLY table to a fully durable disk table is comparing different products. For a business queue, use SCHEMA_AND_DATA on both sides and keep normal transaction durability unless the application has separately accepted delayed-durability loss semantics.

sql · collect resource evidence after a representative run
SELECT  OBJECT_NAME(object_id) AS xtp_table,  memory_used_by_table_kb,  memory_used_by_indexes_kbFROM sys.dm_db_xtp_table_memory_statsORDER BY memory_used_by_table_kb + memory_used_by_indexes_kb DESC;GOSELECT  OBJECT_NAME(object_id) AS table_name,  total_bucket_count,empty_bucket_count,avg_chain_length,max_chain_lengthFROM sys.dm_db_xtp_hash_index_statsORDER BY avg_chain_length DESC;GOSELECT * FROM sys.dm_xtp_transaction_stats;GO

4. Operational feature boundaries belong in the decision

Memory-optimized tables participate in backup/restore and SQL Server HA, but they introduce specific recovery behavior. Durable rows must fit in memory during recovery. SCHEMA_ONLY rows disappear after restart/failover. A natively compiled procedure has a restricted T-SQL/query surface and cannot directly access a disk-based table. Cross-database and distributed transactions cannot access memory-optimized tables in the same way ordinary disk tables can. These are architectural constraints even if a microbenchmark looks excellent.

Migration can also affect schema features, ETL, CDC/replication designs and operational tooling. Current SQL Server supports many features that early In-Memory OLTP versions did not, so do not use SQL Server 2014 folklore as a compatibility matrix. Run a schema/feature inventory against the target 2025 build and test every production dependency.

HA/recovery judgment. Availability Groups integrate with durable In-Memory OLTP and can keep memory-optimized state available on secondaries, while FCI recovery can require loading data into memory on the new node. Backup/restore includes durable checkpoint data. Test your actual RPO/RTO; do not assume “memory” means instant recovery.

5. Decision matrix: XTP, native code, or keep rowstore?

Evidence Likely direction Reason
High lock/latch contention on short OLTP operations; low conflict rate Evaluate memory-optimized table Lock/latch-free structures target the measured bottleneck.
Equality-heavy stable key with known cardinality Evaluate hash index Direct bucket lookup can be efficient when collisions/memory are controlled.
Range/order access dominates Use XTP nonclustered index or keep tuned rowstore Hash indexing has no useful key ordering.
Very short, high-frequency supported procedure logic Evaluate native procedure Native compilation can remove interpreter overhead.
Large analytical query / parallel scan Prefer rowstore/columnstore/analytics design Native procedures are not a replacement for analytical execution strategies.
Memory headroom is tight or Standard/Express quota is near Keep/reduce XTP scope Capacity failures are correctness/availability risks, not only performance risks.
Current rowstore already meets SLA simply and reliably Keep rowstore Migration complexity without measurable business gain is negative value.

A defensible “no” is a successful engineering outcome. Specialized engines add cognitive and operating cost. Keep them only where measured benefits survive realistic concurrency, restart/recovery, backup, failover and schema-change tests.

6. Migration and rollback runbook

For a real table, build a new memory-optimized schema rather than assuming an in-place storage flip. Rehearse data copy and synchronization, validate row counts and business invariants, pause/cut over writers through a controlled application release, monitor memory/conflicts/log/latency, and retain the old rowstore path until rollback criteria expire. If rollback triggers, stop new XTP writes, reconcile/copy authoritative changes back under a defined maintenance window, redirect callers, and verify before dropping any object.

sql · chapter cleanup — only for the disposable lab database
USE master;GOIF DB_ID(N'ServiceHubXtpLab') IS NOT NULLBEGIN  ALTER DATABASE ServiceHubXtpLab SET SINGLE_USER WITH ROLLBACK IMMEDIATE;  DROP DATABASE ServiceHubXtpLab;END;GO-- This removes every Chapter 20 lab object and the MEMORY_OPTIMIZED_DATA-- container. It does not modify the established ServiceHubLab database.

Chapter 21 shifts from specialized storage to operational governance: SQL Server Agent jobs, schedules, proxies, integrity checks, evidence-driven maintenance and idempotent runbooks.

Check your understanding

  1. What is the minimum fair baseline for an XTP comparison?
  2. Why should client-side latency and retry counts be recorded?
  3. Why is SCHEMA_ONLY vs durable rowstore an invalid performance comparison for a business queue?
  4. Give one reason to keep rowstore even if XTP is slightly faster.
  5. What makes rollback credible?
Review the answers

1. A competently indexed, correctly configured disk-based design implementing the same business and durability contract.

2. XTP changes storage/concurrency behavior and rowstore logical-read metrics are not a complete comparison; retries can erase apparent speed gains.

3. They promise different durability: SCHEMA_ONLY rows disappear after restart/failover.

4. Examples: current SLA already met, memory/quota risk, unsupported integration/transaction requirements, higher operational complexity, or weak benefit under representative concurrency.

5. A rehearsed synchronization/cutback procedure, preserved rowstore contract, explicit trigger criteria, and verification before destructive cleanup.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.