Skip to content

Stop wasting memory

Nikolay Mitikov edited this page Mar 8, 2019 · 4 revisions

Bait to make you read this one

When was the last time your machine ran out of RAM turning your work into slow motion nightmare?

Recall the dark day you shut down Spotify, cats memes with an illusive hope of having sufficient RAM for mainstream work tasks...

What if one-third of your app memory was wasted? Meaning you did not have to sacrifice that much and still could have worked with dignity.

memory_waste

No way.

Sounds too good to be the truth, right?

You should not trust everything that is written on the web without proof (clickable):

proof_less_memory_consumed_in_reality

How?

Memory is consumed by objects => objects carry data => the more data == the more memory?

If only we could locate repeatable data patterns to reuse immutable object instances instead of producing new ones...

.NET already provides a concept of reusing immutable data, aka interning.

Two immutable types are mentioned as RAM consumers on the picture - string and Sitecore.Data.ID.

The second picture with reduced memory pressure has only half of string type and does not mention IDs at all.

Welcome Sitecore.Interning.config

There is a possibility of interning inside platform since 8.1 Update 3 !

Sitecore.Data.Interning namespace defines foundation for re-using immutable reference types instead of keeping duplicated values inside process memory.

interning_is_here

The OOB implementation is plugged into PrefetchData reducing duplicated values for item fields that are known to have limited set of field values (like workflow, workflow state, created by).

Locating repeatable values in your solution

WITH DuplicatedFieldValues AS  (
SELECT 
	v.FieldId,	
	FieldDefinitionRow.[Name],
	CONVERT(NVARCHAR(250),v.[Value]) AS [Field Value],
	COUNT(1) AS [Hits]
FROM 
	[VersionedFields] v 
JOIN 
	[Items] FieldDefinitionRow ON FieldDefinitionRow.ID = v.fieldID
WHERE 
	v.FieldId NOT IN 
	(
		/* Fields already interned OOB by Sitecore.Interning.config */			
		'BADD9CF9-53E0-4D0C-BCC0-2D784C282F6A' /* updated by */,
		'5DD74568-4D4B-44C1-B513-0AF5F4CDA34F' /* created by */,
		'52807595-0F8F-4B20-8D2A-CB71D28C6103' /* owner */,
		'3E431DE1-525E-47A3-B6B0-1CCBEC3A8C98' /* workflow state */	
	)
GROUP BY 
	FieldId, [Name], CONVERT(NVARCHAR(250),[Value])
HAVING 
	COUNT(*) > 500 /* How many same field values must be met to be shown */)

SELECT * FROM DuplicatedFieldValues
ORDER BY [Hits] DESC

SELECT 
	REPLACE(CONCAT('<', [Name], '>'), ' ','_') AS [BeginTag],
	CONCAT('{',FieldId,'}') AS [FieldId], 
	REPLACE(CONCAT('</', [Name], '>'), ' ','_') AS [EndTag],
	SUM(Hits) AS [Duplicates],
	COUNT(1) AS [Unique Values] 
FROM 
	DuplicatedFieldValues
GROUP BY
	[FieldId], [Name]
ORDER BY 
	[Duplicates] DESC

This query will return duplicated values met in VersionedFields more than X times.

You can review & add bubbled field ids to the Sitecore.Interning.config, fieldIdsToIntern section.

Alternative approach

You could play with SQL Server table statistics ( db-> versionedFields -> statistics) to locate repeatable values and where are they coming from.

Verify it works phase

My test plan was to load all solution content via following snipped with disabled cache size limits:

var db = Sitecore.Configuration.Factory.GetDatabase("master");
using (new SecurityDisabler())
{
	var root = db.GetItem(new ID("{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}"));
	var descendants = root.Axes.GetDescendants();
	MainUtil.Nop(descendants);
}
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);

Once page done its job, memory snapshot was collected via Task Manager.

The steps were repeated with ON/OFF interning to get the difference. Resulting snapshots were dropped to dotMemory.

Disclamers

A) Interning functionality runs on object pool design pattern that will pre-allocate large arrays to avoid expensive/blocking in-memory growth.

B) .NET processes do not like giving back memory with no reason. The process size may look huge, while internal GC heaps will be almost empty. Process will count on conquered memory for further allocations requests.

Lesson learnt

Immutable data types allow re-using already obtained values => reducing count of objects in application.

The provided example highlights interning truncates ~3.25 million objects from memory (~30 % of total heap count).

Clone this wiki locally