Table of Contents

Improving Memory Usage Accuracy

When using the "LRU-MaxMemory" or "Random-MaxMemory" client caches, the Scaleout.Client library uses the size of an object when serialized to estimate memory usage of object instances. If this estimate proves to be inaccurate, return your own estimated object size from serialization callbacks to improve accuracy.

Background

Some serializers such as Protocol Buffers and MessagePack can efficiently pack objects down to a very small size. In this case, the client cache's memory usage estimates can be very inaccurate because instantiated objects will be much larger than their serialized form. To address this, the Scaleout.Client library allows custom serialization callbacks to return their own size estimates: the CacheBuilder provides a SetSerialization overload that allows you to register serialization callbacks that return your own memory usage estimates.

Note

Improving memory accuracy is only relevant when using the "LRU-MaxMemory" or "Random-MaxMemory" client caches. The "LRU" and "Random" caches perform eviction based on object count and do not track memory usage.

Procedure

  1. Analyze your class to determine an accurate memory footprint.

    • In the example class below, the ScoreHistory list may be packed very efficiently by the protobuf-net library, so Scaleout.Client's default memory usage estimate will be inaccurate:
    [ProtoContract]
    public class Player
    {
        [ProtoMember(1)]
        public string PlayerId { get; set; }
    
        [ProtoMember(2)]
        public List<int> ScoreHistory { get; set; }
    }
    
  2. Implement your size estimate logic.

    static int EstimatePlayerMemUsage(Player player)
    {
        int estimatedSize = 0;
        estimatedSize += player.PlayerId.Length * sizeof(char);
        estimatedSize += player.ScoreHistory.Capacity * sizeof(int);
        return estimatedSize;
    }
    
  3. Define custom serialization and deserialization callbacks that return your size estimate.

    • The signature of the serializer is a function that takes an object instance and a stream as parameters. It returns an integer representing the estimated size of the object being serialized.
    public static int SerializePlayer(Player player, Stream stream)
    {
        ProtoBuf.Serializer.Serialize(stream, player);
        return EstimatePlayerMemUsage(player);
    }
    
    • The signature of the deserializer is a function that takes a stream and returns a ValueTuple consisting of the deserialized object and its estimated size.
    public static (Player, int) DeserializePlayer(Stream stream)
    {
        Player player = ProtoBuf.Serializer.Deserialize<Player>(stream);
        int estimatedSize = EstimatePlayerMemUsage(player);
        return (player, estimatedSize);
    }
    
  4. When building your Cache instance, register your serialization callbacks with the CacheBuilder.

    var conn = GridConnection.Connect("bootstrapGateways=localhost:721");
    var clientCache = new LruMemoryEvictionCache<Player>(capacityInBytes: 100_000_000);
    
    var cache = new CacheBuilder<string, Player>("players", conn)
        .SetClientCache(clientCache)
        .SetSerialization(SerializePlayer, DeserializePlayer)
        .Build();