This multi-part blog post will cover various resource conditions that can affect memory-optimized workloads. We’ll first lay the foundation for what types of resources are required for In-Memory OLTP, and why.
The following topics will be covered :
- causes of OOM (Out of Memory)
- how files that persist durable memory-optimized data affect backup size
- how memory is allocated, including resource pools, if running Enterprise Edition
- potential effect on disk-based workloads (buffer pool pressure)
- what happens when volumes that store durable memory-optimized data run out of free space
- what you can and cannot do when a memory-optimized database runs out of resources
- database restore/recovery
- garbage collection (GC) for row versions and files (file merge)
- BPE (buffer pool extension)
Like most everything in the database world, In-Memory OLTP requires the following resources:
Let’s take storage first – why would a memory-optimized database require storage, what is it used for, and how much storage is required?
Why and What?
You’ll need more storage than you might expect, to hold the files that persist your durable memory-optimized data, and backups.
How much storage?
No one can exactly answer that question, as we’ll explain over the next few blog posts. However, Microsoft’s recommendation is that you have 4x durable memory-optimized data size as a starting point for storage capacity planning.
A memory-optimized database must have a special filegroup designated for memory-optimized data, known as a memory-optimized filegroup. This special filegroup is logically associated with one or more “containers”. What the heck is a “container”? Well, it’s just a fancy word for “folder”, nothing more, nothing less. But what is actually stored in those fancy folders?
Containers hold files known as “checkpoint file pairs”, which are also known as “data and delta files”, and these files persist durable memory-optimized data (in this blog post series, I’ll use the terms CFP and data/delta files interchangeably). You’ll note on the following image that it clearly states in bold red letters, “NO MAXSIZE” and “STREAMING”. “NO MAXSIZE” means that you can’t specify how large these files will grow, nor can you specify how large the container that houses them can grow (unless you set a quota, but you should NOT do that). And there’s also no way at the database level to control the size of anything having to do with In-Memory OLTP storage – you simply must have enough available free space for the data and delta files to grow.
This is the first potential resource issue for In-Memory OLTP: certain types of data modifications are no longer allowed if the volume your container resides upon runs out of free space. I’ll cover workload recovery from resource depletion in a future blog post.
“STREAMING” means that the data stored within these files is different than what’s stored in MDF/LDF/NDF files. Data files for disk-based tables store data rows on 8K pages, a group of which is known as an extent. Data for durable memory-optimized tables is not stored on pages or extents. Instead, memory-optimized data is written in a sequential, streaming fashion, like the FILESTREAM feature (it should be noted that you do not have to enable the FILESTREAM feature in order to use In-Memory OLTP, and that statement has been true since In-Memory OLTP was first released in SQL 2014).
How do these data/delta files get populated? All that is durable in SQL Server is written to the transaction log, and memory-optimized tables are no exception. After first being written to the transaction log, a process known as “offline checkpoint” harvests changes related to memory-optimized tables, and persists those changes in the data/delta files. In SQL 2014, there was a single offline checkpoint thread, but as of SQL 2016, there are multiple offline checkpoint threads.
Let’s create a sample database:
CREATE DATABASE [OOM_DB]
NAME = N'Data'
,FILENAME = N'H:\InMemOOMTest\OOM_DB.mdf'
,SIZE = 100MB
,MAXSIZE = UNLIMITED
,FILEGROWTH = 100MB
,FILEGROUP [OOM_DB_inmem1] CONTAINS MEMORY_OPTIMIZED_DATA DEFAULT
NAME = N'InMemDB_inmem1'
,FILENAME = N'H:\InMemOOMTest\OOM_DB_inmem1'
,MAXSIZE = UNLIMITED
NAME = N'InMemDB_inmem2'
,FILENAME = N'H:\InMemOOMTest\OOM_DB_inmem2'
,MAXSIZE = UNLIMITED
NAME = N'Log'
,FILENAME = N'S:\SQLDATA\OOM_DB.ldf'
,SIZE = 100MB
,MAXSIZE = 2048GB
,FILEGROWTH = 100MB
After creating the database, the InMemOOMTest folder looks like this:
OOM_DB_inmem1 and OOM_DB_inmem2 are containers (folders), and they’ll be used to hold checkpoint file pairs. You’ll note in the DDL listed above, that under the memory-optimized filegroup, each container has both a name and filename entry. The name is the logical name of the container, while the filename is actually the container name, which represents the folder that gets created on disk. Initially there are no CPFs in the containers, but as soon as you create your first memory-optimized table, CFPs get created in both containers.
If we have a look in one of the containers, we can see files that have GUIDs as names, and are created with different sizes.
This is definitely not human-readable, but luckily, Microsoft has created a DMV to allow us to figure out what these files represent.
ORDER BY file_type_desc
Below we can clearly see that there are different types of files, and that files can have different “states”, which is central to the discussion of the storage footprint for memory-optimized databases, and backups of those databases. There are different values for container_id – remember we said that a memory-optimized database can have one or more containers. Next, we should pay attention to the fact that all entries for the “relative_file_path” column begin with “$HKv2\”. This means that in each container, we have a folder with the name “$HKv2”, and all data/delta files for that container are located there.
At this point, it’s time for a discussion of the various file states. I’ll stick to SQL 2016+ (because SQL 2014 had more file states).
The possible file states are:
- UNDER CONSTRUCTION
- MERGE TARGET
- WAITING FOR LOG TRUNCATION
We’ll discuss the first three now, and save MERGE TARGET and WAITING FOR LOG TRUNCATION for later.
PRECREATED: as a performance optimization technique, the In-Memory engine will “precreate” files. These precreated files have nothing in them – they are completely empty, from a durable data perspective. A file in this state cannot yet be populated.
UNDER CONSTRUCTION: when the engine starts adding data to a file, the state of the file changes from PRECREATED to UNDER CONSTRUCTION. Data and delta files are shared by all durable memory-optimized tables, so it’s entirely possible that the first entry is for TableA, the next entry for TableB, and so on. “UNDER CONSTRUCTION” could be interpreted as “able to be populated”.
ACTIVE: When a file that was previously UNDER CONSTRUCTION gets closed, the state transitions to ACTIVE. That means it has entries in it, but is no longer able to be be populated. What causes a file to be closed? The CHECKPOINT process will close the checkpoint, changing all UNDER CONSTRUCTION files to ACTIVE.
That’s the basic rundown of the file states we need to know about at this point.
In Part 2, we’ll dive deeper into the impact of data/delta file states and the storage footprint for memory-optimized databases.