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

Migration Candidates, Memory Sizing, Garbage Collection, and Operational Monitoring

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

Advanced180–230 minutesXTP memory/GC/CFP monitoring labSQL Server 2025 CU7 · 17.0.4065.4Standard 32 GB/db · Express 352 MB/dbBackup/HA implications · August 2026

Learning outcomes

A successful proof-of-concept can fail in production if memory sizing and version cleanup are treated as afterthoughts. Memory-optimized rows, historical row versions, indexes, hash bucket arrays and system metadata all need memory; durable objects also need checkpoint-file storage and log throughput. The correct migration question is therefore not “how large is this table on disk?” but “what concurrency problem are we solving, what live and historical memory will the engine need, what durable storage/recovery load follows, and what happens at the quota boundary?”

01

Identify evidence that makes a disk-based table/procedure a credible In-Memory OLTP candidate.

02

Estimate memory for rows and indexes, including fixed hash-bucket arrays and growth/headroom.

03

Use supported XTP DMVs to observe object memory, checkpoint files, transactions and garbage collection.

04

Explain why long transactions can retain row versions and delay memory reclamation.

05

Design quota/headroom, backup/recovery and rollback monitoring before production migration.

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. Candidate selection starts with a measured bottleneck

Good candidates are usually short OLTP transactions where lock/latch contention, tempdb pressure from transient table structures, or high-frequency interpreted code has been identified as a material constraint. A table is not a candidate merely because it is “important,” small enough to fit in memory, or frequently queried. Ordinary disk tables already benefit from the buffer pool, so a read-mostly table whose pages are cached might gain little from a storage-engine migration.

Collect evidence on the existing rowstore design first. Operational index statistics can show row/page lock waits and latch waits; wait statistics and Query Store can establish whether those waits actually align with user-visible latency. Make sure indexing and transaction scope are already reasonable, or you risk comparing a tuned XTP design with an intentionally weak baseline.

sql · example candidate evidence for a disk-based table
USE ServiceHubLab;GOSELECT  OBJECT_SCHEMA_NAME(ios.object_id) AS schema_name,  OBJECT_NAME(ios.object_id) AS table_name,  i.name AS index_name,  ios.row_lock_wait_count,  ios.row_lock_wait_in_ms,  ios.page_lock_wait_count,  ios.page_lock_wait_in_ms,  ios.page_latch_wait_count,  ios.page_latch_wait_in_msFROM sys.dm_db_index_operational_stats (DB_ID(), OBJECT_ID(N'ops.WorkOrder'), NULL, NULL) AS iosJOIN sys.indexes AS i  ON i.object_id=ios.object_id AND i.index_id=ios.index_idORDER BY ios.page_latch_wait_in_ms DESC, ios.row_lock_wait_in_ms DESC;GO

These counters are cumulative and reset with engine/object lifecycle events. They establish observed waiting, not causation by themselves. Correlate them with workload timing and plan/transaction evidence before redesigning storage.

2. Memory sizing includes indexes, versions and fixed arrays

For a memory-optimized table, the rough live footprint is row memory plus every index. Hash indexes are particularly easy to underestimate because their bucket arrays are allocated according to the configured bucket count, rounded up to the next power of two, with approximately eight bytes per bucket before considering row/index links. Memory-optimized nonclustered indexes scale more with key size and row count. Row headers also contain timestamp/version and index-link metadata.

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;GO
sql · measure the actual Chapter 20 object footprint
USE ServiceHubXtpLab;GOSELECT  OBJECT_NAME(object_id) AS table_name,  memory_allocated_for_table_kb,  memory_used_by_table_kb,  memory_allocated_for_indexes_kb,  memory_used_by_indexes_kbFROM sys.dm_db_xtp_table_memory_statsORDER BY memory_allocated_for_table_kb + memory_allocated_for_indexes_kb DESC;GOSELECT  OBJECT_NAME(h.object_id) AS table_name,  i.name,  h.bucket_countFROM sys.hash_indexes AS hJOIN sys.indexes AS i  ON i.object_id=h.object_id AND i.index_id=h.index_idORDER BY h.bucket_count DESC;GO

Plan for peak active rows, normal version churn, indexes, transient growth during migrations/DDL, and safety headroom. Never size exactly to today's steady-state DMV result. Standard and Express impose per-database memory-optimized-data quotas, so approaching 32 GB or 352 MB respectively can produce quota errors such as 41823. Enterprise removes the edition-specific cap, not the physical-memory requirement.

3. Garbage collection is version cleanup, not checkpoint-file cleanup

When an in-memory row is updated or deleted, older versions cannot be reclaimed until no active transaction could still need them. The In-Memory OLTP garbage collector identifies obsolete row versions and work is processed by transaction schedulers and an idle worker. A long-running transaction can therefore hold back cleanup and inflate memory even though the business table's current row count looks stable.

sql · observe active transactions and garbage-collection queues
SELECT  transaction_id,  session_id,  begin_tsn,  end_tsn,  state_descFROM sys.dm_db_xtp_transactionsORDER BY begin_tsn;GOSELECT  queue_id,  current_queue_depth,  total_enqueues,  total_dequeuesFROM sys.dm_xtp_gc_queue_statsORDER BY current_queue_depth DESC;GOSELECT * FROM sys.dm_xtp_gc_stats;GO

Do not interpret a nonzero GC queue as a failure: work is expected to flow through the queues. Investigate when queue depth remains elevated, memory grows, or old active transactions prevent progress. Killing sessions solely because a queue is nonzero is as misguided as rebuilding an index because fragmentation crossed an arbitrary number.

Checkpoint-file merging is a separate persistent-storage lifecycle. Memory garbage collection can reclaim obsolete row versions before the corresponding on-disk data/delta files become removable. Checkpoints, merge policy and transaction-log truncation determine when persistent checkpoint files transition and can be deleted.

sql · correlate in-memory size with durable checkpoint storage
SELECT  state_desc,  file_type_desc,  COUNT(*) AS file_count,  SUM(file_size_in_bytes)/1024.0/1024.0 AS allocated_mb,  SUM(file_size_used_in_bytes)/1024.0/1024.0 AS used_mbFROM sys.dm_db_xtp_checkpoint_filesGROUP BY state_desc,file_type_descORDER BY state_desc,file_type_desc;GODBCC SQLPERF(LOGSPACE);GO

4. Backup, restore, HA and recovery memory are part of sizing

Regular SQL Server backups include durable memory-optimized data. A full backup contains relevant data/delta checkpoint-file contents plus the active log; differential backups apply their own checkpoint-file selection rules. During restore or crash recovery, durable memory-optimized data must be loaded back into memory before the database is fully available. If sufficient memory is unavailable, recovery can fail and the database can become suspect. Recovery-time objective (RTO) therefore includes storage throughput and memory reconstruction, not just backup file size.

In-Memory OLTP integrates with Availability Groups and Failover Cluster Instances. AG secondaries can maintain durable memory-optimized state in memory, but SCHEMA_ONLY rows are empty after failover by design. In an FCI, the new node must load durable memory-optimized data during recovery, so failover time can be longer. These differences belong in HA drills.

sql · inspect backup-relevant persistent footprint before a drill
SELECT  SUM(file_size_in_bytes)/1024.0/1024.0 AS xtp_checkpoint_allocated_mb,  SUM(file_size_used_in_bytes)/1024.0/1024.0 AS xtp_checkpoint_used_mbFROM sys.dm_db_xtp_checkpoint_files;GOSELECT  SUM(memory_used_by_table_kb + memory_used_by_indexes_kb)/1024.0 AS xtp_used_mbFROM sys.dm_db_xtp_table_memory_stats;GO

A large ratio between checkpoint storage and current in-memory size is not automatically corruption. It can be normal append/merge lifecycle, especially during heavy churn. But persistent growth warrants review of checkpoint/log-backup progress and merge state.

5. Deliberately wrong approach: migrate first, size later

Moving a hot 30-GB workload into SQL Server Standard because the current disk table is 28 GB ignores indexes, row versions, hash arrays and growth. The 32-GB per-database quota leaves almost no operating margin. Under load, inserts/updates can fail at the quota boundary. The repair is capacity modeling before migration: include expected peak rows, every index, churn/version headroom and business growth, then validate with a scaled workload and monitor actual XTP memory.

If the design cannot maintain safe headroom, choose a smaller candidate set, keep the table disk-based, partition the business workload deliberately, or use a deployment whose memory/edition boundaries fit. Do not treat an edition upgrade as the only architecture answer.

6. Production judgment and bridge

A credible adoption plan includes baseline waits, candidate workload, memory model, growth/headroom, checkpoint/log storage, backup size, restore/failover timings, quota alarms and rollback. Keep a disk-based representation/cutover plan until production behavior is understood. Chapter 20's final lesson combines these constraints into a decision: whether XTP is actually better than a competent rowstore implementation for the ServiceHub workload.

Check your understanding

  1. Why is current disk table size an insufficient memory estimate?
  2. What can keep obsolete row versions in memory?
  3. Are memory garbage collection and checkpoint-file merge the same process?
  4. Why can In-Memory OLTP affect RTO?
  5. What does error 41823 mean in this context?
Review the answers

1. Memory-optimized rows use a different representation and require indexes, hash arrays, row-version headroom and system/DDL overhead.

2. Long-running transactions whose visibility horizon still requires older versions can delay garbage collection.

3. No. Memory GC reclaims obsolete row versions; checkpoint-file persistence/merge/log-truncation governs on-disk storage lifecycle.

4. Durable memory-optimized data must be reconstructed into memory during recovery/restore, requiring enough memory and storage throughput.

5. A user-data quota for memory-optimized tables/table variables was reached on editions such as Standard or Express; repeated retries do not create capacity.

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.