Compressing output cache content in Redis

This commit is contained in:
Sebastien Ros
2015-08-04 17:28:49 -07:00
parent f25758cc05
commit 430c3de460
2 changed files with 69 additions and 7 deletions
@@ -3,6 +3,9 @@
namespace Orchard.OutputCache.Models {
[Serializable]
public class CacheItem {
// used for serialization compatibility
public static readonly string Version = "1";
public DateTime CachedOnUtc { get; set; }
public int Duration { get; set; }
public int GraceTime { get; set; }
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Logging;
@@ -10,11 +9,23 @@ using Orchard.OutputCache.Models;
using Orchard.OutputCache.Services;
using Orchard.Redis.Extensions;
using StackExchange.Redis;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.IO.Compression;
namespace Orchard.Redis.OutputCache {
[OrchardFeature("Orchard.Redis.OutputCache")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
/// <summary>
/// This implementation stores a <see cref="CacheItem"/> instance to a Redis server.
/// The item is serialized using a <see cref="BinaryFormatter"/> and GZipped. We rely
/// on compression at this level of the implementation as other output cache providers
/// might not want to rely on it, or transform the data to binary. The content is compressed
/// as HTML pages can be consequent, like several hundreds of KB, and the network be clogged.
/// To prevent versioning issues with serialized data, the Redis keys contain the
/// <see cref="CacheItem.Version"/> property.
/// </summary>
public class RedisOutputCacheStorageProvider : IOutputCacheStorageProvider {
private readonly ShellSettings _shellSettings;
@@ -43,12 +54,19 @@ namespace Orchard.Redis.OutputCache {
}
public void Set(string key, CacheItem cacheItem) {
if(cacheItem == null) {
throw new ArgumentNullException("cacheItem");
}
if (cacheItem.ValidFor <= 0) {
return;
}
var value = JsonConvert.SerializeObject(cacheItem);
Database.StringSet(GetLocalizedKey(key), value, TimeSpan.FromSeconds(cacheItem.ValidFor));
using (var decompressedStream = Serialize(cacheItem)) {
using (var compressedStream = Compress(decompressedStream)) {
Database.StringSet(GetLocalizedKey(key), compressedStream.ToArray(), TimeSpan.FromSeconds(cacheItem.ValidFor));
}
}
}
public void Remove(string key) {
@@ -60,12 +78,21 @@ namespace Orchard.Redis.OutputCache {
}
public CacheItem GetCacheItem(string key) {
string value = Database.StringGet(GetLocalizedKey(key));
if (String.IsNullOrEmpty(value)) {
var value = Database.StringGet(GetLocalizedKey(key));
if (value.IsNullOrEmpty) {
return null;
}
return JsonConvert.DeserializeObject<CacheItem>(value);
using (var compressedStream = new MemoryStream(value)) {
if(compressedStream.Length == 0) {
return null;
}
using(var decompressedStream = Decompress(compressedStream)) {
return Deserialize(decompressedStream);
}
}
}
public IEnumerable<CacheItem> GetCacheItems(int skip, int count) {
@@ -88,7 +115,7 @@ namespace Orchard.Redis.OutputCache {
/// <param name="key">The key to localized.</param>
/// <returns>A localized key based on the tenant name.</returns>
private string GetLocalizedKey(string key) {
return _shellSettings.Name + ":OutputCache:" + key;
return _shellSettings.Name + ":OC:" + CacheItem.Version + ":" + key;
}
/// <summary>
@@ -111,5 +138,37 @@ namespace Orchard.Redis.OutputCache {
return _keysCache;
}
private static MemoryStream Serialize(CacheItem item) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, item);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private static CacheItem Deserialize(Stream stream) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var result = (CacheItem)binaryFormatter.Deserialize(stream);
return result;
}
private static MemoryStream Compress(Stream stream) {
var compressedStream = new MemoryStream();
using (var compressionStream = new GZipStream(compressedStream, CompressionMode.Compress)) {
stream.CopyTo(compressionStream);
return compressedStream;
}
}
private static Stream Decompress(Stream stream) {
var decompressedStream = new MemoryStream();
using (GZipStream decompressionStream = new GZipStream(stream, CompressionMode.Decompress)) {
decompressionStream.CopyTo(decompressedStream);
decompressedStream.Seek(0, SeekOrigin.Begin);
return decompressedStream;
}
}
}
}