Skip to content

Commit

Permalink
Update documentation on long item usage;
Browse files Browse the repository at this point in the history
  • Loading branch information
TonySkorik committed Aug 1, 2024
1 parent 2aa5157 commit 83b98b0
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 39 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,18 @@ To enable this option set the following configuration key.

Do not forget to set this option back to `false` or to delte it after the transition period is over.

## Restrictions
## Restrictions && Limitations

Key must be less than 250 characters and value must be less than 1MB of data.
Key must be less than 250 characters and value must be less than the value `-I` configured during memcached instance start.

The key length restriction can be lifted, see [Long keys support](#long-keys-support) section for the details.

If the value is greater than the configured `-I` value the memcached client won't throw an exception - it simply won't store a key without any notification.
Therefore, when trying to read such key - nothing will be returned.

Also, please note that this library utilizes a memcached item `flags` to store item type information to simplify and make deserialization faster.
This implies that one is not advised to use this library to read the data from memcached that you didn't put there using this library - it might be deserialized incorrectly or not deserialized at all.

## Additional configuration

### SASL
Expand Down
15 changes: 9 additions & 6 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ services:
image: memcached:latest
restart: always
container_name: memcached-1
command: ["-m", "128"]
ports:
command: ["-m", "128", "-I", "1M"]
# command: ["-m", "128", "-I", "512k", "-o", "slab_chunk_max=524288"]
ports:
- 11211:11211
memcached-2:
image: memcached:latest
restart: always
container_name: memcached-2
command: ["-m", "128"]
ports:
command: ["-m", "128", "-I", "1M"]
# command: ["-m", "128", "-I", "512k", "-o", "slab_chunk_max=524288"]
ports:
- 11212:11211
memcached-3:
image: memcached:latest
restart: always
container_name: memcached-3
command: ["-m", "128"]
ports:
command: ["-m", "128", "-I", "1M"]
# command: ["-m", "128", "-I", "512k", "-o", "slab_chunk_max=524288"]
ports:
- 11213:11211
13 changes: 12 additions & 1 deletion src/Aer.Memcached.Client/Serializers/Binary/BinarySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class BinarySerializer
private const uint RawDataFlag = 0xfa52;
private const uint TypeCodeSerializationMask = 0x0100;
private const uint TypeCodeDeserializationMask = 0xff;

private readonly IObjectBinarySerializer _objectBinarySerializer;

public BinarySerializer(IObjectBinarySerializerFactory objectBinarySerializerFactory)
Expand Down Expand Up @@ -119,6 +119,17 @@ internal DeserializationResult<T> Deserialize<T>(
var typeCode = (TypeCode) (item.Flags & TypeCodeDeserializationMask);
if (typeCode == TypeCode.Empty)
{
// the flags value is set during item serialization
// it stores the cache item System.Runtime.TypeCode
// if it is not set -> Flags == 0 or Flags contain some unknown value
// then the item is considered empty
// The Data property for such empty items though
// will contain ASCII bytes for "Not found" string
// 0x4E, 0x6F, 0x74, 0x20, 0x66, 0x6F, 0x75, 0x6E, 0x64
// we don't check them since typeCode absence already tells us that the item is empty
// it does, though impede using this library for reading existing
// memcached keys that were written not using this library
// since they may set Flags to some arbitrary value which we interpret
return DeserializationResult<T>.Empty;
}

Expand Down
1 change: 1 addition & 0 deletions tests/Aer.Memcached.Tests/Aer.Memcached.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>

<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public async Task Get_CheckAllTypes_DefaultValue()
await StoreAndGet_CheckType<Dictionary<KeyObject, SimpleObject>>();
}
}

[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
Expand Down Expand Up @@ -205,14 +205,14 @@ await Client.MultiStoreAsync(
var getValue = await Client.MultiGetAsync<string>(new[] {key}, CancellationToken.None);

getValue.Count.Should().Be(1);

await Task.Delay(TimeSpan.FromSeconds(CacheItemExpirationSeconds * 2));

getValue = await Client.MultiGetAsync<string>(new[]{key}, CancellationToken.None);
getValue = await Client.MultiGetAsync<string>(new[] {key}, CancellationToken.None);

getValue.Count.Should().Be(0);
}

[TestMethod]
public async Task StoreAndGet_EmptyString()
{
Expand All @@ -227,41 +227,47 @@ public async Task StoreAndGet_EmptyString()
getValue.Success.Should().BeTrue();
getValue.IsEmptyResult.Should().BeFalse();
}

[TestMethod]
public async Task MultiStoreAndGet_NullAsString()
{
var keyValues = new Dictionary<string, string>();

foreach (var _ in Enumerable.Range(0, 5))
{
keyValues[Guid.NewGuid().ToString()] = null;
}

await Client.MultiStoreAsync(keyValues, TimeSpan.FromSeconds(CacheItemExpirationSeconds), CancellationToken.None);


await Client.MultiStoreAsync(
keyValues,
TimeSpan.FromSeconds(CacheItemExpirationSeconds),
CancellationToken.None);

var getValues = await Client.MultiGetAsync<string>(keyValues.Keys, CancellationToken.None);

foreach (var keyValue in keyValues)
{
getValues[keyValue.Key].Should().Be(keyValues[keyValue.Key]);
}
}

[TestMethod]
public async Task MultiStoreAndGet_EmptyString()
{
var keyValues = new Dictionary<string, string>();

foreach (var _ in Enumerable.Range(0, 5))
{
keyValues[Guid.NewGuid().ToString()] = string.Empty;
}

await Client.MultiStoreAsync(keyValues, TimeSpan.FromSeconds(CacheItemExpirationSeconds), CancellationToken.None);


await Client.MultiStoreAsync(
keyValues,
TimeSpan.FromSeconds(CacheItemExpirationSeconds),
CancellationToken.None);

var getValues = await Client.MultiGetAsync<string>(keyValues.Keys, CancellationToken.None);

foreach (var keyValue in keyValues)
{
getValues[keyValue.Key].Should().BeNull();
Expand All @@ -275,15 +281,15 @@ public async Task StoreAndGet_NullExpiration()
var value = Fixture.Create<SimpleObject>();

await Client.StoreAsync(
key,
value,
expirationTime: null,
key,
value,
expirationTime: null,
CancellationToken.None);

var getValue = await Client.GetAsync<SimpleObject>(key, CancellationToken.None);

getValue.Success.Should().BeTrue();

getValue.IsEmptyResult.Should().BeFalse();
getValue.Result.Should().BeEquivalentTo(value);
}
Expand All @@ -302,12 +308,12 @@ public async Task StoreAndGet_SimpleObject()
getValue.Success.Should().BeTrue();
getValue.IsEmptyResult.Should().BeFalse();
}

[TestMethod]
public async Task MultiStoreAndGet_SimpleObject()
{
var keyValues = new Dictionary<string, SimpleObject>();

foreach (var _ in Enumerable.Range(0, 5))
{
keyValues[Guid.NewGuid().ToString()] = Fixture.Create<SimpleObject>();
Expand All @@ -317,11 +323,14 @@ public async Task MultiStoreAndGet_SimpleObject()

var tooLongKey = GetTooLongKey();
keyValues[tooLongKey] = Fixture.Create<SimpleObject>();

await Client.MultiStoreAsync(keyValues, TimeSpan.FromSeconds(CacheItemExpirationSeconds), CancellationToken.None);


await Client.MultiStoreAsync(
keyValues,
TimeSpan.FromSeconds(CacheItemExpirationSeconds),
CancellationToken.None);

var getValues = await Client.MultiGetAsync<SimpleObject>(keyValues.Keys, CancellationToken.None);

foreach (var keyValue in keyValues)
{
getValues[keyValue.Key].Should().BeEquivalentTo(keyValues[keyValue.Key]);
Expand Down Expand Up @@ -395,19 +404,19 @@ public async Task MultiStoreAndGet_ExpirationNowAndInThePast_NoKeysStored()
CancellationToken.None);

storeResult.Success.Should().BeFalse();

storeResult = await Client.MultiStoreAsync(
keyValues,
expirationTime: DateTimeOffset.Now,
CancellationToken.None);

storeResult.Success.Should().BeFalse();

var getValues =
var getValues =
await Client.MultiGetAsync<SimpleObject>(keyValues.Keys, CancellationToken.None);

// no keys should be stored

getValues.Count.Should().Be(0);
}

Expand Down Expand Up @@ -468,11 +477,64 @@ public async Task StoreAndGet_ObjectWithCollections()

var getValue = await Client.GetAsync<ObjectWithCollections>(key, CancellationToken.None);

getValue.Result.Should().BeEquivalentTo(value);
getValue.Success.Should().BeTrue();
getValue.Result.Should().BeEquivalentTo(value);
getValue.IsEmptyResult.Should().BeFalse();
}

[TestMethod]
public async Task StoreAndGet_VeryLargeObject()
{
var key = Guid.NewGuid().ToString();

// very large array 1_024_000 * 4 = 4MB
var value = new int[1_024_000];

// memcached, while restricting item to configured "-I" parameter,
// returns success while trying to add a too large item to cache,
// it just does nothing - it does not throw anything but does not store the key either
var storeResult = await Client.StoreAsync(
key,
value,
TimeSpan.FromSeconds(CacheItemExpirationSeconds),
CancellationToken.None);

storeResult.Success.Should().BeTrue();

// when trying to read too large object
// memcached simply returns nothing since it was not set in the first place
var getValue = await Client.GetAsync<byte[]>(key, CancellationToken.None);

getValue.Success.Should().BeTrue();
getValue.IsEmptyResult.Should().BeTrue();
}

[TestMethod]
public async Task StoreAndMultiGet_VeryLargeObject()
{
var key = Guid.NewGuid().ToString();

// very large array 1_024_000 * 4 = 4MB
var value = new int[1_024_000];

// memcached, while restricting item to configured "-I" parameter,
// returns success while trying to add a too large item to cache,
// it just does nothing - it does not throw anything but does not store the key either
var storeResult = await Client.StoreAsync(
key,
value,
TimeSpan.FromSeconds(CacheItemExpirationSeconds),
CancellationToken.None);

storeResult.Success.Should().BeTrue();

// when trying to read too large object
// memcached simply returns nothing since it was not set in the first place
var getValue = await Client.MultiGetAsync<byte[]>([key], CancellationToken.None);

getValue.ContainsKey(key).Should().BeFalse();
}

[TestMethod]
public async Task MultiStoreAndGet_ObjectWithCollections()
{
Expand Down

0 comments on commit 83b98b0

Please sign in to comment.