Skip to content

Commit 8a917b8

Browse files
authored
Merge pull request #622 from imperugo/feature/hash-expiry-azure-compression
Add hash field expiry, Azure Managed Identity support, and compression decorator
2 parents a9f5ede + e6229b3 commit 8a917b8

File tree

23 files changed

+1247
-207
lines changed

23 files changed

+1247
-207
lines changed

NuGetPack.bat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ dotnet pack src\serializers\StackExchange.Redis.Extensions.Utf8Json\StackExchang
66
dotnet pack src\serializers\StackExchange.Redis.Extensions.System.Text.Json\StackExchange.Redis.Extensions.System.Text.Json.csproj -o .\packages\ -c Release
77
dotnet pack src\serializers\StackExchange.Redis.Extensions.MemoryPack\StackExchange.Redis.Extensions.MemoryPack.csproj -o .\packages\ -c Release
88
dotnet pack src\serializers\StackExchange.Redis.Extensions.ServiceStack\StackExchange.Redis.Extensions.ServiceStack.csproj -o .\packages\ -c Release
9+
dotnet pack src\compressors\StackExchange.Redis.Extensions.Compression.GZip\StackExchange.Redis.Extensions.Compression.GZip.csproj -o .\packages\ -c Release
10+
dotnet pack src\compressors\StackExchange.Redis.Extensions.Compression.Brotli\StackExchange.Redis.Extensions.Compression.Brotli.csproj -o .\packages\ -c Release
11+
dotnet pack src\compressors\StackExchange.Redis.Extensions.Compression.LZ4\StackExchange.Redis.Extensions.Compression.LZ4.csproj -o .\packages\ -c Release
12+
dotnet pack src\compressors\StackExchange.Redis.Extensions.Compression.Snappier\StackExchange.Redis.Extensions.Compression.Snappier.csproj -o .\packages\ -c Release
13+
dotnet pack src\compressors\StackExchange.Redis.Extensions.Compression.ZstdSharp\StackExchange.Redis.Extensions.Compression.ZstdSharp.csproj -o .\packages\ -c Release
914
dotnet pack src\aspnet\StackExchange.Redis.Extensions.AspNetCore\StackExchange.Redis.Extensions.AspNetCore.csproj -o .\packages\ -c Release

StackExchange.Redis.Extensions.sln

Lines changed: 291 additions & 198 deletions
Large diffs are not rendered by default.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
using StackExchange.Redis.Extensions.Core;
6+
7+
namespace Microsoft.Extensions.DependencyInjection;
8+
9+
/// <summary>
10+
/// Extension methods to add compression support to the Redis serialization pipeline.
11+
/// </summary>
12+
public static class IServiceCollectionCompressionExtensions
13+
{
14+
/// <summary>
15+
/// Adds compression to the Redis serialization pipeline using the specified <see cref="ICompressor"/> implementation.
16+
/// Wraps the registered <see cref="ISerializer"/> with a <see cref="CompressedSerializer"/>.
17+
/// Must be called after <c>AddStackExchangeRedisExtensions</c>.
18+
/// </summary>
19+
/// <typeparam name="TCompressor">The compressor implementation type.</typeparam>
20+
/// <param name="services">The service collection.</param>
21+
/// <returns>The service collection for chaining.</returns>
22+
/// <example>
23+
/// <code>
24+
/// services.AddStackExchangeRedisExtensions&lt;SystemTextJsonSerializer&gt;(config);
25+
/// services.AddRedisCompression&lt;GZipCompressor&gt;();
26+
/// </code>
27+
/// </example>
28+
public static IServiceCollection AddRedisCompression<TCompressor>(this IServiceCollection services)
29+
where TCompressor : class, ICompressor, new()
30+
{
31+
var compressor = new TCompressor();
32+
33+
return services.AddRedisCompression(compressor);
34+
}
35+
36+
/// <summary>
37+
/// Adds compression to the Redis serialization pipeline using the specified <see cref="ICompressor"/> instance.
38+
/// Wraps the registered <see cref="ISerializer"/> with a <see cref="CompressedSerializer"/>.
39+
/// Must be called after <c>AddStackExchangeRedisExtensions</c>.
40+
/// </summary>
41+
/// <param name="services">The service collection.</param>
42+
/// <param name="compressor">The compressor instance to use.</param>
43+
/// <returns>The service collection for chaining.</returns>
44+
public static IServiceCollection AddRedisCompression(this IServiceCollection services, ICompressor compressor)
45+
{
46+
ArgumentNullException.ThrowIfNull(compressor);
47+
48+
services.AddSingleton(compressor);
49+
50+
// Find the existing ISerializer registration and wrap it
51+
for (var i = services.Count - 1; i >= 0; i--)
52+
{
53+
var descriptor = services[i];
54+
55+
if (descriptor.ServiceType != typeof(ISerializer))
56+
continue;
57+
58+
var previousFactory = descriptor.ImplementationFactory;
59+
var previousInstance = descriptor.ImplementationInstance;
60+
var previousType = descriptor.ImplementationType;
61+
62+
services[i] = ServiceDescriptor.Singleton<ISerializer>(sp =>
63+
{
64+
ISerializer inner;
65+
66+
if (previousInstance != null)
67+
inner = (ISerializer)previousInstance;
68+
else if (previousFactory != null)
69+
inner = (ISerializer)previousFactory(sp);
70+
else
71+
inner = (ISerializer)ActivatorUtilities.CreateInstance(sp, previousType!);
72+
73+
return new CompressedSerializer(inner, compressor);
74+
});
75+
76+
return services;
77+
}
78+
79+
throw new InvalidOperationException(
80+
$"No registration for {nameof(ISerializer)} found. Call AddStackExchangeRedisExtensions before AddRedisCompression.");
81+
}
82+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.IO;
4+
using System.IO.Compression;
5+
6+
namespace StackExchange.Redis.Extensions.Core;
7+
8+
/// <summary>
9+
/// An <see cref="ICompressor"/> implementation using Brotli compression.
10+
/// Higher compression ratio than GZip, especially for text-like data. No external dependencies.
11+
/// </summary>
12+
public class BrotliCompressor : ICompressor
13+
{
14+
private readonly CompressionLevel compressionLevel;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="BrotliCompressor"/> class.
18+
/// </summary>
19+
/// <param name="compressionLevel">The compression level to use. Defaults to <see cref="CompressionLevel.Fastest"/>.</param>
20+
public BrotliCompressor(CompressionLevel compressionLevel = CompressionLevel.Fastest)
21+
{
22+
this.compressionLevel = compressionLevel;
23+
}
24+
25+
/// <inheritdoc/>
26+
public byte[] Compress(byte[] data)
27+
{
28+
using var output = new MemoryStream();
29+
30+
using (var brotli = new BrotliStream(output, compressionLevel))
31+
brotli.Write(data, 0, data.Length);
32+
33+
return output.ToArray();
34+
}
35+
36+
/// <inheritdoc/>
37+
public byte[] Decompress(byte[] compressedData)
38+
{
39+
using var input = new MemoryStream(compressedData);
40+
using var brotli = new BrotliStream(input, CompressionMode.Decompress);
41+
using var output = new MemoryStream();
42+
43+
brotli.CopyTo(output);
44+
45+
return output.ToArray();
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
4+
<Title>Brotli compression for StackExchange.Redis.Extensions.</Title>
5+
<Summary>Brotli compression for StackExchange.Redis.Extensions. No additional dependencies required.</Summary>
6+
<Description>Provides a Brotli ICompressor implementation for use with StackExchange.Redis.Extensions.Core's CompressedSerializer. Uses System.IO.Compression from the BCL — no external dependencies. Requires .NET 8+.</Description>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.201" PrivateAssets="All" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\core\StackExchange.Redis.Extensions.Core\StackExchange.Redis.Extensions.Core.csproj" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.IO;
4+
using System.IO.Compression;
5+
6+
namespace StackExchange.Redis.Extensions.Core;
7+
8+
/// <summary>
9+
/// An <see cref="ICompressor"/> implementation using GZip compression.
10+
/// Good compression ratio, widely supported. No external dependencies.
11+
/// </summary>
12+
public class GZipCompressor : ICompressor
13+
{
14+
private readonly CompressionLevel compressionLevel;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="GZipCompressor"/> class.
18+
/// </summary>
19+
/// <param name="compressionLevel">The compression level to use. Defaults to <see cref="CompressionLevel.Fastest"/>.</param>
20+
public GZipCompressor(CompressionLevel compressionLevel = CompressionLevel.Fastest)
21+
{
22+
this.compressionLevel = compressionLevel;
23+
}
24+
25+
/// <inheritdoc/>
26+
public byte[] Compress(byte[] data)
27+
{
28+
using var output = new MemoryStream();
29+
30+
using (var gzip = new GZipStream(output, compressionLevel))
31+
gzip.Write(data, 0, data.Length);
32+
33+
return output.ToArray();
34+
}
35+
36+
/// <inheritdoc/>
37+
public byte[] Decompress(byte[] compressedData)
38+
{
39+
using var input = new MemoryStream(compressedData);
40+
using var gzip = new GZipStream(input, CompressionMode.Decompress);
41+
using var output = new MemoryStream();
42+
43+
gzip.CopyTo(output);
44+
45+
return output.ToArray();
46+
}
47+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<Title>GZip compression for StackExchange.Redis.Extensions.</Title>
4+
<Summary>GZip compression for StackExchange.Redis.Extensions. No additional dependencies required.</Summary>
5+
<Description>Provides a GZip ICompressor implementation for use with StackExchange.Redis.Extensions.Core's CompressedSerializer. Uses System.IO.Compression from the BCL — no external dependencies.</Description>
6+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.201" PrivateAssets="All" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\core\StackExchange.Redis.Extensions.Core\StackExchange.Redis.Extensions.Core.csproj" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using K4os.Compression.LZ4;
4+
5+
namespace StackExchange.Redis.Extensions.Core;
6+
7+
/// <summary>
8+
/// An <see cref="ICompressor"/> implementation using LZ4 compression.
9+
/// Extremely fast compression and decompression — ideal for caching scenarios
10+
/// where latency matters more than compression ratio.
11+
/// </summary>
12+
public class LZ4Compressor : ICompressor
13+
{
14+
private readonly LZ4Level level;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="LZ4Compressor"/> class.
18+
/// </summary>
19+
/// <param name="level">The LZ4 compression level. Defaults to <see cref="LZ4Level.L00_FAST"/>.</param>
20+
public LZ4Compressor(LZ4Level level = LZ4Level.L00_FAST)
21+
{
22+
this.level = level;
23+
}
24+
25+
/// <inheritdoc/>
26+
public byte[] Compress(byte[] data) =>
27+
LZ4Pickler.Pickle(data, level);
28+
29+
/// <inheritdoc/>
30+
public byte[] Decompress(byte[] compressedData) =>
31+
LZ4Pickler.Unpickle(compressedData);
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<Title>LZ4 compression for StackExchange.Redis.Extensions.</Title>
4+
<Summary>LZ4 compression for StackExchange.Redis.Extensions. Extremely fast compression/decompression.</Summary>
5+
<Description>Provides an LZ4 ICompressor implementation for use with StackExchange.Redis.Extensions.Core's CompressedSerializer. LZ4 offers the best compression/decompression speed, ideal for caching scenarios where latency matters more than compression ratio.</Description>
6+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="K4os.Compression.LZ4" Version="[1.3.*,2.0)" />
12+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.201" PrivateAssets="All" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\core\StackExchange.Redis.Extensions.Core\StackExchange.Redis.Extensions.Core.csproj" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
using Snappier;
6+
7+
namespace StackExchange.Redis.Extensions.Core;
8+
9+
/// <summary>
10+
/// An <see cref="ICompressor"/> implementation using Snappy compression via Snappier.
11+
/// Snappy prioritizes speed over compression ratio, similar to LZ4.
12+
/// </summary>
13+
public class SnappierCompressor : ICompressor
14+
{
15+
/// <inheritdoc/>
16+
public byte[] Compress(byte[] data)
17+
{
18+
var maxLength = Snappy.GetMaxCompressedLength(data.Length);
19+
var buffer = new byte[maxLength];
20+
var compressedLength = Snappy.Compress(data, buffer);
21+
22+
return buffer.AsSpan(0, compressedLength).ToArray();
23+
}
24+
25+
/// <inheritdoc/>
26+
public byte[] Decompress(byte[] compressedData)
27+
{
28+
var decompressedLength = Snappy.GetUncompressedLength(compressedData);
29+
var buffer = new byte[decompressedLength];
30+
var actualLength = Snappy.Decompress(compressedData, buffer);
31+
32+
return buffer.AsSpan(0, actualLength).ToArray();
33+
}
34+
}

0 commit comments

Comments
 (0)