diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2afe71a --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) +[Bb]in/ +[Oo]bj/ + +# mstest test results +TestResults + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +src/*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +packages + +# Windows Azure Build Output +csx +*.build.csdef + +# Others +[Bb]in +[Oo]bj +sql +TestResults +[Tt]est[Rr]esult* +ClientBin +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config new file mode 100644 index 0000000..67f8ea0 --- /dev/null +++ b/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe new file mode 100644 index 0000000..4e719e4 Binary files /dev/null and b/.nuget/NuGet.exe differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets new file mode 100644 index 0000000..bda5bea --- /dev/null +++ b/.nuget/NuGet.targets @@ -0,0 +1,143 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + false + + + false + + + true + + + false + + + + + + + + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) + $([System.IO.Path]::Combine($(SolutionDir), "packages")) + + + + + $(SolutionDir).nuget + packages.config + $(SolutionDir)packages + + + + + $(NuGetToolsPath)\nuget.exe + @(PackageSource) + + "$(NuGetExePath)" + mono --runtime=v4.0.30319 $(NuGetExePath) + + $(TargetDir.Trim('\\')) + + -RequireConsent + + $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)" + $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NHibernate.Caches.Redis.sln b/NHibernate.Caches.Redis.sln new file mode 100644 index 0000000..1d0f43a --- /dev/null +++ b/NHibernate.Caches.Redis.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Redis", "src\NHibernate.Caches.Redis\NHibernate.Caches.Redis.csproj", "{1B50164C-DB00-4109-A0D4-E2868ABFEDAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Redis.Tests", "tests\NHibernate.Caches.Redis.Tests\NHibernate.Caches.Redis.Tests.csproj", "{A067C7D2-CF09-4F24-9848-5CE10DC48438}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{65D5F343-E913-4E44-AD77-C338437C9D68}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\NuGet.exe = .nuget\NuGet.exe + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F5128A75-1A1D-4585-BB4E-A9E55D12D03C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F0732F96-D774-44A7-B51A-54AC1A278F99}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF}.Release|Any CPU.Build.0 = Release|Any CPU + {A067C7D2-CF09-4F24-9848-5CE10DC48438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A067C7D2-CF09-4F24-9848-5CE10DC48438}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A067C7D2-CF09-4F24-9848-5CE10DC48438}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A067C7D2-CF09-4F24-9848-5CE10DC48438}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF} = {F5128A75-1A1D-4585-BB4E-A9E55D12D03C} + {A067C7D2-CF09-4F24-9848-5CE10DC48438} = {F0732F96-D774-44A7-B51A-54AC1A278F99} + EndGlobalSection +EndGlobal diff --git a/build/NHibernate.Caches.Redis.nuspec b/build/NHibernate.Caches.Redis.nuspec new file mode 100644 index 0000000..e69de29 diff --git a/build/build.proj b/build/build.proj new file mode 100644 index 0000000..e69de29 diff --git a/src/NHibernate.Caches.Redis/NHibernate.Caches.Redis.csproj b/src/NHibernate.Caches.Redis/NHibernate.Caches.Redis.csproj new file mode 100644 index 0000000..2c2ac4b --- /dev/null +++ b/src/NHibernate.Caches.Redis/NHibernate.Caches.Redis.csproj @@ -0,0 +1,80 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF} + Library + Properties + NHibernate.Caches.Redis + NHibernate.Caches.Redis + v4.0 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Iesi.Collections.3.2.0.4000\lib\Net35\Iesi.Collections.dll + + + ..\packages\NHibernate.3.3.1.4000\lib\Net35\NHibernate.dll + + + ..\packages\ServiceStack.Common.3.9.11\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.11\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Redis.3.9.12\lib\net35\ServiceStack.Redis.dll + + + ..\packages\ServiceStack.Text.3.9.11\lib\net35\ServiceStack.Text.dll + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Caches.Redis/ObjectExtensions.cs b/src/NHibernate.Caches.Redis/ObjectExtensions.cs new file mode 100644 index 0000000..894aab8 --- /dev/null +++ b/src/NHibernate.Caches.Redis/ObjectExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NHibernate.Caches.Redis +{ + static class ObjectExtensions + { + public static T ThrowIfNull(this T source) + where T : class + { + if (source == null) throw new ArgumentNullException(); + return source; + } + + public static T ThrowIfNull(this T source, string paramName) + { + if (source == null) throw new ArgumentNullException(paramName); + return source; + } + } +} diff --git a/src/NHibernate.Caches.Redis/Properties/AssemblyInfo.cs b/src/NHibernate.Caches.Redis/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..33e2f63 --- /dev/null +++ b/src/NHibernate.Caches.Redis/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NHibernate.Caches.Redis")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NHibernate.Caches.Redis")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c3db6972-73e3-4a08-8a79-99e6c29d1d25")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: InternalsVisibleTo("NHibernate.Caches.Redis.Tests")] \ No newline at end of file diff --git a/src/NHibernate.Caches.Redis/RedisCache.cs b/src/NHibernate.Caches.Redis/RedisCache.cs new file mode 100644 index 0000000..3f85d80 --- /dev/null +++ b/src/NHibernate.Caches.Redis/RedisCache.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cache; +using NHibernate.Util; +using ServiceStack.Common; +using ServiceStack.Redis; +using ServiceStack.Redis.Support; +using ServiceStack.Text; + +namespace NHibernate.Caches.Redis +{ + public class RedisCache : ICache + { + private const string CacheNamePrefix = "NHibernate-Cache:"; + + private static readonly IInternalLogger log; + private static Dictionary acquiredLocks = new Dictionary(); + + private readonly ISerializer serializer; + private readonly IRedisClientsManager clientManager; + private readonly int expirySeconds; + private readonly TimeSpan lockTimeout = TimeSpan.FromSeconds(30); + + public string RegionName { get; private set; } + public RedisNamespace CacheNamespace { get; private set; } + public int Timeout { get { return Timestamper.OneMs * 60000; } } + + static RedisCache() + { + log = LoggerProvider.LoggerFor(typeof(RedisCache)); + } + + public RedisCache(string regionName, IRedisClientsManager clientManager) + : this(regionName, new Dictionary(), clientManager) + { + + } + + public RedisCache(string regionName, IDictionary properties, IRedisClientsManager clientManager) + { + this.serializer = new ObjectSerializer(); + this.clientManager = clientManager.ThrowIfNull("clientManager"); + this.RegionName = regionName.ThrowIfNull("regionName"); + + this.expirySeconds = PropertiesHelper.GetInt32(Cfg.Environment.CacheDefaultExpiration, properties, 300); + log.DebugFormat("using expiration : {0} seconds", this.expirySeconds); + + var regionPrefix = PropertiesHelper.GetString(Cfg.Environment.CacheRegionPrefix, properties, null); + log.DebugFormat("using region prefix : {0}", regionPrefix); + + var namespacePrefix = CacheNamePrefix + this.RegionName; + if (!String.IsNullOrWhiteSpace(regionPrefix)) + { + namespacePrefix = regionPrefix + ":" + namespacePrefix; + } + + this.CacheNamespace = new RedisNamespace(namespacePrefix); + this.SyncGeneration(); + } + + public long NextTimestamp() + { + return Timestamper.Next(); + } + + private void SyncGeneration() + { + if (CacheNamespace.GetGeneration() == -1) + { + CacheNamespace.SetGeneration(FetchGeneration()); + } + } + + private long FetchGeneration() + { + using (var client = this.clientManager.GetClient()) + { + var generationKey = CacheNamespace.GetGenerationKey(); + var attemptedGeneration = client.GetValue(generationKey); + + if (attemptedGeneration == null) + { + var generation = client.Increment(generationKey, 1); + log.DebugFormat("creating new generation : {0}", generation); + return generation; + } + else + { + log.DebugFormat("using existing generation : {0}", attemptedGeneration); + return Convert.ToInt64(attemptedGeneration); + } + } + } + + public void Put(object key, object value) + { + key.ThrowIfNull("key"); + value.ThrowIfNull("value"); + + log.DebugFormat("put in cache : {0}", key); + + try + { + var data = serializer.Serialize(value); + + ExecuteEnsureGeneration(transaction => + { + transaction.QueueCommand(r => + { + var cacheKey = CacheNamespace.GlobalCacheKey(key); + ((IRedisNativeClient)r).SetEx(cacheKey, expirySeconds, data); + }); + + transaction.QueueCommand(r => + { + var globalKeysKey = CacheNamespace.GetGlobalKeysKey(); + var cacheKey = CacheNamespace.GlobalCacheKey(key); + r.AddItemToSet(globalKeysKey, cacheKey); + }); + }); + } + catch (Exception) + { + log.WarnFormat("could not put in cache : {0}", key); + throw; + } + } + + public object Get(object key) + { + key.ThrowIfNull(); + + log.DebugFormat("get from cache : {0}", key); + + try + { + byte[] data = null; + + ExecuteEnsureGeneration(transaction => + { + transaction.QueueCommand(r => + { + var cacheKey = CacheNamespace.GlobalCacheKey(key); + return ((IRedisNativeClient)r).Get(cacheKey); + }, x => data = x); + }); + + return serializer.Deserialize(data); + + } + catch (Exception) + { + log.WarnFormat("coult not get from cache : {0}", key); + throw; + } + } + + public void Remove(object key) + { + key.ThrowIfNull(); + + log.DebugFormat("remove from cache : {0}", key); + + try + { + ExecuteEnsureGeneration(transaction => + { + transaction.QueueCommand(r => + { + var cacheKey = CacheNamespace.GlobalCacheKey(key); + ((RedisNativeClient)r).Del(cacheKey); + }); + }); + } + catch (Exception) + { + log.WarnFormat("could not remove from cache : {0}", key); + throw; + } + } + + public void Clear() + { + var generationKey = CacheNamespace.GetGenerationKey(); + var globalKeysKey = CacheNamespace.GetGlobalKeysKey(); + + log.DebugFormat("clear cache : {0}", generationKey); + + try + { + List keysToDelete = null; + + using (var client = this.clientManager.GetClient()) + { + // Update to a new generation. + using (var transaction = client.CreateTransaction()) + { + transaction.QueueCommand(r => + r.Increment(generationKey, 1), + x => CacheNamespace.SetGeneration(x)); + + transaction.QueueCommand( + r => r.GetAllItemsFromSet(globalKeysKey).ToList(), + x => keysToDelete = x); + + transaction.Commit(); + } + + log.DebugFormat("running eager garbage collection : {0}", generationKey); + + // Remove the old generation's cached values. + using (var transaction = client.CreateTransaction()) + { + transaction.QueueCommand(r => r.Remove(globalKeysKey)); + transaction.QueueCommand(r => r.RemoveAll(keysToDelete)); + transaction.Commit(); + } + } + } + catch (Exception) + { + log.WarnFormat("could not clear cache : {0}", generationKey); + throw; + } + } + + public void Destroy() + { + // No-op since Redis is distributed. + log.DebugFormat("destroying cache : {0}", this.CacheNamespace.GetGenerationKey()); + } + + public void Lock(object key) + { + log.DebugFormat("acquiring cache lock : {0}", key); + + try + { + var globalKey = CacheNamespace.GlobalKey(key, RedisNamespace.NumTagsForLockKey); + + using (var client = this.clientManager.GetClient()) + { + // Derived from ServiceStack's RedisLock. + ExecExtensions.RetryUntilTrue(() => + { + var wasSet = client.SetEntryIfNotExists(globalKey, "lock " + DateTime.UtcNow.ToUnixTime()); + + if (wasSet) + { + acquiredLocks[key] = globalKey; + } + + return wasSet; + }, lockTimeout); + } + } + catch (Exception) + { + log.WarnFormat("could not acquire cache lock : ", key); + throw; + } + } + + public void Unlock(object key) + { + string globalKey; + if (!acquiredLocks.TryGetValue(key, out globalKey)) { return; } + + log.DebugFormat("releasing cache lock : {0}", key); + + try + { + using (var client = this.clientManager.GetClient()) + { + client.Remove(globalKey); + } + } + catch (Exception) + { + log.WarnFormat("could not release cache lock : {0}", key); + throw; + } + } + + private void ExecuteEnsureGeneration(Action action) + { + long serverGeneration = -1; + + using (var client = this.clientManager.GetClient()) + using (var transaction = client.CreateTransaction()) + { + action(transaction); + + transaction.QueueCommand(r => + r.GetValue(CacheNamespace.GetGenerationKey()), + x => serverGeneration = Convert.ToInt64(x)); + + transaction.Commit(); + + // Another client/cache has changed the generation. Therefore, + // this cache is out of date so we need to sync the generation + // with the server. + while (serverGeneration != CacheNamespace.GetGeneration()) + { + CacheNamespace.SetGeneration(serverGeneration); + transaction.Replay(); + } + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Caches.Redis/RedisCacheProvider.cs b/src/NHibernate.Caches.Redis/RedisCacheProvider.cs new file mode 100644 index 0000000..4e7f34e --- /dev/null +++ b/src/NHibernate.Caches.Redis/RedisCacheProvider.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NHibernate.Cache; +using ServiceStack.Redis; + +namespace NHibernate.Caches.Redis +{ + public class RedisCacheProvider : ICacheProvider + { + private static readonly IInternalLogger log; + private static IRedisClientsManager clientManagerStatic; + + static RedisCacheProvider() + { + log = LoggerProvider.LoggerFor(typeof(RedisCacheProvider)); + } + + public static void SetClientManager(IRedisClientsManager clientManager) + { + if (clientManagerStatic != null) + { + throw new InvalidOperationException("The client manager can only be configured once."); + } + + clientManagerStatic = clientManager.ThrowIfNull(); + } + + internal static void InternalSetClientManager(IRedisClientsManager clientManager) + { + clientManagerStatic = clientManager; + } + + public ICache BuildCache(string regionName, IDictionary properties) + { + if (clientManagerStatic == null) + { + throw new InvalidOperationException( + "An 'IRedisClientsManager' must be configured with SetClientManager(). " + + "For example, call 'RedisCacheProvider(new PooledRedisClientManager())' " + + "before creating the ISessionFactory."); + } + + if (log.IsDebugEnabled) + { + var sb = new StringBuilder(); + foreach (var pair in properties) + { + sb.Append("name="); + sb.Append(pair.Key); + sb.Append("&value="); + sb.Append(pair.Value); + sb.Append(";"); + } + log.Debug("building cache with region: " + regionName + ", properties: " + sb); + } + + return new RedisCache(regionName, properties, clientManagerStatic); + } + + public long NextTimestamp() + { + return Timestamper.Next(); + } + + public void Start(IDictionary properties) + { + // No-op. + log.Debug("starting cache provider"); + } + + public void Stop() + { + // No-op. + log.Debug("stopping cache provider"); + } + } +} diff --git a/src/NHibernate.Caches.Redis/packages.config b/src/NHibernate.Caches.Redis/packages.config new file mode 100644 index 0000000..cc987fc --- /dev/null +++ b/src/NHibernate.Caches.Redis/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/NHibernate.Caches.Redis.Tests/NHibernate.Caches.Redis.Tests.csproj b/tests/NHibernate.Caches.Redis.Tests/NHibernate.Caches.Redis.Tests.csproj new file mode 100644 index 0000000..a5744f2 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/NHibernate.Caches.Redis.Tests.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {A067C7D2-CF09-4F24-9848-5CE10DC48438} + Library + Properties + NHibernate.Caches.Redis.Tests + NHibernate.Caches.Redis.Tests + v4.0 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FluentNHibernate.1.3.0.733\lib\FluentNHibernate.dll + + + ..\packages\Iesi.Collections.3.2.0.4000\lib\Net35\Iesi.Collections.dll + + + ..\packages\NHibernate.3.3.1.4000\lib\Net35\NHibernate.dll + + + ..\packages\ServiceStack.Common.3.9.11\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.11\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Redis.3.9.12\lib\net35\ServiceStack.Redis.dll + + + ..\packages\ServiceStack.Text.3.9.11\lib\net35\ServiceStack.Text.dll + + + + + + + + + + ..\packages\xunit.1.9.1\lib\net20\xunit.dll + + + + + + + + + + + + + + + + {1B50164C-DB00-4109-A0D4-E2868ABFEDAF} + NHibernate.Caches.Redis + + + + + + \ No newline at end of file diff --git a/tests/NHibernate.Caches.Redis.Tests/Person.cs b/tests/NHibernate.Caches.Redis.Tests/Person.cs new file mode 100644 index 0000000..82a7040 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/Person.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NHibernate.Caches.Redis.Tests +{ + [Serializable] + public class Person + { + public virtual int Id { get; protected set; } + public virtual int Age { get; set; } + public virtual string Name { get; set; } + + protected Person() { } + + public Person(string name, int age) + { + this.Name = name; + this.Age = age; + } + } +} diff --git a/tests/NHibernate.Caches.Redis.Tests/PersonMapping.cs b/tests/NHibernate.Caches.Redis.Tests/PersonMapping.cs new file mode 100644 index 0000000..a6cc680 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/PersonMapping.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentNHibernate.Mapping; + +namespace NHibernate.Caches.Redis.Tests +{ + public class PersonMapping : ClassMap + { + public PersonMapping() + { + Table("Person"); + Id(x => x.Id); + Map(x => x.Age); + Map(x => x.Name); + + Cache.ReadWrite(); + } + } +} diff --git a/tests/NHibernate.Caches.Redis.Tests/Properties/AssemblyInfo.cs b/tests/NHibernate.Caches.Redis.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..92cfc20 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NHibernate.Caches.Redis.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NHibernate.Caches.Redis.Tests")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("211995f4-5c9c-4c5c-b0f8-a72bef23396b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/NHibernate.Caches.Redis.Tests/RedisCacheIntegrationTests.cs b/tests/NHibernate.Caches.Redis.Tests/RedisCacheIntegrationTests.cs new file mode 100644 index 0000000..2654469 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/RedisCacheIntegrationTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NHibernate.Cfg; +using FluentNHibernate.Cfg; +using FluentNHibernate.Cfg.Db; +using Xunit; +using NHibernate.Tool.hbm2ddl; + +namespace NHibernate.Caches.Redis.Tests +{ + public class RedisCacheIntegrationTests : RedisTest + { + private static Configuration configuration; + + public RedisCacheIntegrationTests() + { + RedisCacheProvider.InternalSetClientManager(this.ClientManager); + + if (configuration == null) + { + configuration = Fluently.Configure() + .Database( + MsSqlConfiguration.MsSql2008 + .ConnectionString("Data Source=(local)\\SQLExpress;Initial Catalog=People;Integrated Security=SSPI;") + ) + .Mappings(m => + { + m.FluentMappings.Add(typeof(PersonMapping)); + }) + .ExposeConfiguration(cfg => + { + cfg.SetProperty(NHibernate.Cfg.Environment.GenerateStatistics, "true"); + }) + .Cache(c => + { + c.UseQueryCache().UseSecondLevelCache().ProviderClass(); + }) + .BuildConfiguration(); + } + + new SchemaExport(configuration).Create(false, true); + } + + [Fact] + public void Entity_cache() + { + using (var sf = CreateSessionFactory()) + { + object personId = null; + + UsingSession(sf, session => + { + personId = session.Save(new Person("Foo", 1)); + }); + + sf.Statistics.Clear(); + + UsingSession(sf, session => + { + session.Get(personId); + Assert.Equal(1, sf.Statistics.SecondLevelCacheMissCount); + Assert.Equal(1, sf.Statistics.SecondLevelCachePutCount); + }); + + sf.Statistics.Clear(); + + UsingSession(sf, session => + { + session.Get(personId); + Assert.Equal(1, sf.Statistics.SecondLevelCacheHitCount); + Assert.Equal(0, sf.Statistics.SecondLevelCacheMissCount); + Assert.Equal(0, sf.Statistics.SecondLevelCachePutCount); + }); + } + } + + [Fact] + private void SessionFactory_Dispose_should_not_clear_cache() + { + using (var sf = CreateSessionFactory()) + { + UsingSession(sf, session => + { + session.Save(new Person("Foo", 10)); + }); + + UsingSession(sf, session => + { + session.QueryOver() + .Cacheable() + .List(); + + Assert.Equal(1, sf.Statistics.QueryCacheMissCount); + Assert.Equal(1, sf.Statistics.SecondLevelCachePutCount); + Assert.Equal(1, sf.Statistics.QueryCachePutCount); + }); + } + + using (var sf = CreateSessionFactory()) + { + UsingSession(sf, session => + { + session.QueryOver() + .Cacheable() + .List(); + + Assert.Equal(1, sf.Statistics.SecondLevelCacheHitCount); + Assert.Equal(1, sf.Statistics.QueryCacheHitCount); + Assert.Equal(0, sf.Statistics.SecondLevelCachePutCount); + Assert.Equal(0, sf.Statistics.QueryCachePutCount); + }); + } + } + + private ISessionFactory CreateSessionFactory() + { + return configuration.BuildSessionFactory(); + } + + private void UsingSession(ISessionFactory sessionFactory, Action action) + { + using (var session = sessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + action(session); + transaction.Commit(); + } + } + } +} diff --git a/tests/NHibernate.Caches.Redis.Tests/RedisCacheTests.cs b/tests/NHibernate.Caches.Redis.Tests/RedisCacheTests.cs new file mode 100644 index 0000000..6518d61 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/RedisCacheTests.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Redis.Support; +using Xunit; + +namespace NHibernate.Caches.Redis.Tests +{ + public class RedisCacheTests : RedisTest + { + private readonly ISerializer serializer = new ObjectSerializer(); + + [Fact] + public void Constructor_should_set_generation_if_it_does_not_exist() + { + var cache = new RedisCache("regionName", this.ClientManager); + + var genKey = cache.CacheNamespace.GetGenerationKey(); + Assert.Contains("NHibernate-Cache:regionName", genKey); + Assert.Equal(1, cache.CacheNamespace.GetGeneration()); + } + + [Fact] + public void Constructor_should_get_current_generation_if_it_already_exists() + { + // Distributed caches. + var cache1 = new RedisCache("regionName", this.ClientManager); + var cache2 = new RedisCache("regionName", this.ClientManager); + + Assert.Equal(1, cache1.CacheNamespace.GetGeneration()); + Assert.Equal(1, cache2.CacheNamespace.GetGeneration()); + } + + [Fact] + public void Put_should_serialize_item_and_set_with_expiry() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + + // Act + cache.Put(999, new Person("Foo", 10)); + + // Assert + var cacheKey = cache.CacheNamespace.GlobalCacheKey(999); + var data = RedisNative.Get(cacheKey); + var expiry = Redis.GetTimeToLive(cacheKey); + + Assert.True(expiry <= TimeSpan.FromMinutes(5)); + + var person = serializer.Deserialize(data) as Person; + Assert.NotNull(person); + Assert.Equal("Foo", person.Name); + Assert.Equal(10, person.Age); + } + + [Fact] + public void Put_should_retry_until_generation_matches_the_server() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + + // Another client incremented the generation. + Redis.Increment(cache.CacheNamespace.GetGenerationKey(), 100); + + // Act + cache.Put(999, new Person("Foo", 10)); + + // Assert + Assert.Equal(cache.CacheNamespace.GetGeneration(), 101); + var data = RedisNative.Get(cache.CacheNamespace.GlobalCacheKey(999)); + var person = (Person)serializer.Deserialize(data); + Assert.Equal("Foo", person.Name); + Assert.Equal(10, person.Age); + } + + [Fact] + public void Get_should_deserialize_data() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + cache.Put(999, new Person("Foo", 10)); + + // Act + var person = cache.Get(999) as Person; + + // Assert + Assert.NotNull(person); + Assert.Equal("Foo", person.Name); + Assert.Equal(10, person.Age); + } + + [Fact] + public void Get_should_return_null_if_not_exists() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + + // Act + var person = cache.Get(99999) as Person; + + // Assert + Assert.Null(person); + } + + [Fact] + public void Get_should_retry_until_generation_matches_the_server() + { + // Arrange + var cache1 = new RedisCache("region", this.ClientManager); + + // Another client incremented the generation. + Redis.Increment(cache1.CacheNamespace.GetGenerationKey(), 100); + var cache2 = new RedisCache("region", this.ClientManager); + cache2.Put(999, new Person("Foo", 10)); + + // Act + var person = cache1.Get(999) as Person; + + // Assert + Assert.Equal(101, cache1.CacheNamespace.GetGeneration()); + Assert.NotNull(person); + Assert.Equal("Foo", person.Name); + Assert.Equal(10, person.Age); + } + + [Fact] + public void Put_and_Get_into_different_cache_regions() + { + // Arrange + const int key = 1; + var cache1 = new RedisCache("region_A", this.ClientManager); + var cache2 = new RedisCache("region_B", this.ClientManager); + + // Act + cache1.Put(key, new Person("A", 1)); + cache2.Put(key, new Person("B", 1)); + + // Assert + Assert.Equal("A", ((Person)cache1.Get(1)).Name); + Assert.Equal("B", ((Person)cache2.Get(1)).Name); + } + + [Fact] + public void Remove_should_remove_from_cache() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + cache.Put(999, new Person("Foo", 10)); + + // Act + cache.Remove(999); + + // Assert + var result = Redis.GetValue(cache.CacheNamespace.GlobalCacheKey(999)); + Assert.Null(result); + } + + [Fact] + public void Remove_should_retry_until_generation_matches_the_server() + { + // Arrange + var cache1 = new RedisCache("region", this.ClientManager); + + // Another client incremented the generation. + Redis.Increment(cache1.CacheNamespace.GetGenerationKey(), 100); + var cache2 = new RedisCache("region", this.ClientManager); + cache2.Put(999, new Person("Foo", 10)); + + // Act + cache1.Remove(999); + + // Assert + Assert.Equal(101, cache1.CacheNamespace.GetGeneration()); + var result = Redis.GetValue(cache1.CacheNamespace.GlobalCacheKey(999)); + Assert.Null(result); + } + + [Fact] + public void Clear_should_remove_all_items_for_this_region() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + cache.Put(1, new Person("Foo", 1)); + cache.Put(2, new Person("Bar", 2)); + cache.Put(3, new Person("Baz", 3)); + var oldKey1 = cache.CacheNamespace.GlobalCacheKey(1); + var oldKey2 = cache.CacheNamespace.GlobalCacheKey(2); + var oldKey3 = cache.CacheNamespace.GlobalCacheKey(3); + + var oldGlobalKeysKey = cache.CacheNamespace.GetGlobalKeysKey(); + + // Act + cache.Clear(); + + // Assert + + // New generation. + Assert.Equal(2, cache.CacheNamespace.GetGeneration()); + Assert.Null(Redis.GetValue(cache.CacheNamespace.GlobalCacheKey(1))); + Assert.Null(Redis.GetValue(cache.CacheNamespace.GlobalCacheKey(2))); + Assert.Null(Redis.GetValue(cache.CacheNamespace.GlobalCacheKey(3))); + + // Old keys were removed. + Assert.Null(Redis.GetValue(oldKey1)); + Assert.Null(Redis.GetValue(oldKey2)); + Assert.Null(Redis.GetValue(oldKey3)); + } + + [Fact] + public void Clear_should_ensure_generation_if_another_cache_has_already_incremented_the_generation() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + + // Another cache updated its generation (by clearing). + Redis.Increment(cache.CacheNamespace.GetGenerationKey(), 100); + + // Act + cache.Clear(); + + // Assert + Assert.Equal(102, cache.CacheNamespace.GetGeneration()); + } + + [Fact] + public void Destroy_should_not_clear() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + + // Act + cache.Destroy(); + + // Assert + Assert.Equal(1, cache.CacheNamespace.GetGeneration()); + } + + [Fact] + public void Lock_and_Unlock_concurrently_with_same_cache_client() + { + // Arrange + var cache = new RedisCache("region", this.ClientManager); + cache.Put(1, new Person("Foo", 1)); + + var results = new ConcurrentQueue(); + const int numberOfClients = 5; + + // Act + var tasks = new List(); + for (var i = 1; i <= numberOfClients; i++) + { + int clientNumber = i; + var t = Task.Factory.StartNew(() => + { + cache.Lock(1); + results.Enqueue(clientNumber + " lock"); + + // Atrifical concurrency. + Thread.Sleep(100); + + results.Enqueue(clientNumber + " unlock"); + cache.Unlock(1); + }); + + tasks.Add(t); + } + + // Assert + Task.WaitAll(tasks.ToArray()); + + // Each Lock should be followed by its associated Unlock. + var listResults = results.ToList(); + for (var i = 1; i <= numberOfClients; i++) + { + var lockIndex = listResults.IndexOf(i + " lock"); + Assert.Equal(i + " lock", listResults[lockIndex]); + Assert.Equal(i + " unlock", listResults[lockIndex + 1]); + } + } + + [Fact] + public void Lock_and_Unlock_concurrently_with_different_cache_clients() + { + // Arrange + var mainCache = new RedisCache("region", this.ClientManager); + mainCache.Put(1, new Person("Foo", 1)); + + var results = new ConcurrentQueue(); + const int numberOfClients = 5; + + // Act + var tasks = new List(); + for (var i = 1; i <= numberOfClients; i++) + { + int clientNumber = i; + var t = Task.Factory.StartNew(() => + { + var cacheX = new RedisCache("region", this.ClientManager); + cacheX.Lock(1); + results.Enqueue(clientNumber + " lock"); + + // Atrifical concurrency. + Thread.Sleep(100); + + results.Enqueue(clientNumber + " unlock"); + cacheX.Unlock(1); + }); + + tasks.Add(t); + } + + // Assert + Task.WaitAll(tasks.ToArray()); + + // Each Lock should be followed by its associated Unlock. + var listResults = results.ToList(); + for (var i = 1; i <= numberOfClients; i++) + { + var lockIndex = listResults.IndexOf(i + " lock"); + Assert.Equal(i + " lock", listResults[lockIndex]); + Assert.Equal(i + " unlock", listResults[lockIndex + 1]); + } + } + } +} diff --git a/tests/NHibernate.Caches.Redis.Tests/RedisTest.cs b/tests/NHibernate.Caches.Redis.Tests/RedisTest.cs new file mode 100644 index 0000000..edcbefa --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/RedisTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ServiceStack.Redis; + +namespace NHibernate.Caches.Redis.Tests +{ + public class RedisTest : IDisposable + { + protected IRedisClientsManager ClientManager { get; private set; } + protected IRedisClient Redis { get; private set; } + protected IRedisNativeClient RedisNative { get { return (IRedisNativeClient)Redis; } } + + protected RedisTest() + { + this.ClientManager = new BasicRedisClientManager("localhost:6379"); + this.Redis = this.ClientManager.GetClient(); + this.Redis.FlushDb(); + } + + public void Dispose() + { + this.Redis.Dispose(); + this.ClientManager.Dispose(); + } + } +} diff --git a/tests/NHibernate.Caches.Redis.Tests/packages.config b/tests/NHibernate.Caches.Redis.Tests/packages.config new file mode 100644 index 0000000..09f3822 --- /dev/null +++ b/tests/NHibernate.Caches.Redis.Tests/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/start-redis.bat b/tests/start-redis.bat new file mode 100644 index 0000000..9e579cb --- /dev/null +++ b/tests/start-redis.bat @@ -0,0 +1,3 @@ +cd ..\tools\redis\ +redis-server.exe redis.conf +redis-cli.exe flushdb \ No newline at end of file diff --git a/tests/stop-redis.bat b/tests/stop-redis.bat new file mode 100644 index 0000000..95d13e1 --- /dev/null +++ b/tests/stop-redis.bat @@ -0,0 +1,2 @@ +cd ..\tools\redis +redis-cli.exe shutdown \ No newline at end of file diff --git a/tools/MSBuild.ExtensionPack/Ionic.Zip.dll b/tools/MSBuild.ExtensionPack/Ionic.Zip.dll new file mode 100644 index 0000000..95fa928 Binary files /dev/null and b/tools/MSBuild.ExtensionPack/Ionic.Zip.dll differ diff --git a/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll new file mode 100644 index 0000000..481f92b Binary files /dev/null and b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll differ diff --git a/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll.config b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll.config new file mode 100644 index 0000000..5ae72b8 --- /dev/null +++ b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.dll.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.pdb b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.pdb new file mode 100644 index 0000000..62a3534 Binary files /dev/null and b/tools/MSBuild.ExtensionPack/MSBuild.ExtensionPack.pdb differ diff --git a/tools/Redis/dump.rdb b/tools/Redis/dump.rdb new file mode 100644 index 0000000..cb8e46c Binary files /dev/null and b/tools/Redis/dump.rdb differ diff --git a/tools/Redis/libhiredis.dll b/tools/Redis/libhiredis.dll new file mode 100644 index 0000000..b7d375b Binary files /dev/null and b/tools/Redis/libhiredis.dll differ diff --git a/tools/Redis/redis-benchmark.exe b/tools/Redis/redis-benchmark.exe new file mode 100644 index 0000000..0ca9d35 Binary files /dev/null and b/tools/Redis/redis-benchmark.exe differ diff --git a/tools/Redis/redis-check-aof.exe b/tools/Redis/redis-check-aof.exe new file mode 100644 index 0000000..3349a80 Binary files /dev/null and b/tools/Redis/redis-check-aof.exe differ diff --git a/tools/Redis/redis-check-dump.exe b/tools/Redis/redis-check-dump.exe new file mode 100644 index 0000000..860b9fb Binary files /dev/null and b/tools/Redis/redis-check-dump.exe differ diff --git a/tools/Redis/redis-cli.exe b/tools/Redis/redis-cli.exe new file mode 100644 index 0000000..d9d4d2a Binary files /dev/null and b/tools/Redis/redis-cli.exe differ diff --git a/tools/Redis/redis-server.exe b/tools/Redis/redis-server.exe new file mode 100644 index 0000000..d8f0118 Binary files /dev/null and b/tools/Redis/redis-server.exe differ diff --git a/tools/Redis/redis.conf b/tools/Redis/redis.conf new file mode 100644 index 0000000..9def31e --- /dev/null +++ b/tools/Redis/redis.conf @@ -0,0 +1,489 @@ +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specifiy +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile /var/run/redis.pid + +# Accept connections on the specified port, default is 6379. +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# If you want you can bind a single interface, if the bind option is not +# specified all the interfaces will listen for incoming connections. +# +# bind 127.0.0.1 + +# Specify the path for the unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /tmp/redis.sock +# unixsocketperm 755 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# Set server verbosity to 'debug' +# it can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel verbose + +# Specify the log file name. Also 'stdout' can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile stdout + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +################################ SNAPSHOTTING ################################# +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. + +save 900 1 +save 300 10 +save 60 10000 + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# Also the Append Only File will be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth + +# When a slave lost the connection with the master, or when the replication +# is still in progress, the slave can act in two different ways: +# +# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will +# still reply to client requests, possibly with out of data data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) if slave-serve-stale data is set to 'no' the slave will reply with +# an error "SYNC with master in progress" to all the kind of commands +# but to INFO and SLAVEOF. +# +slave-serve-stale-data yes + +# Slaves send PINGs to server in a predefined interval. It's possible to change +# this interval with the repl_ping_slave_period option. The default value is 10 +# seconds. +# +# repl-ping-slave-period 10 + +# The following option sets a timeout for both Bulk transfer I/O timeout and +# master data or ping response timeout. The default value is 60 seconds. +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-slave-period otherwise a timeout will be detected +# every time there is low traffic between the master and the slave. +# +# repl-timeout 60 + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +# requirepass foobared + +# Command renaming. +# +# It is possilbe to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# of hard to guess so that it will be still available for internal-use +# tools but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possilbe to completely kill a command renaming it into +# an empty string: +# +# rename-command CONFIG "" + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default there +# is no limit, and it's up to the number of file descriptors the Redis process +# is able to open. The special value '0' means no limits. +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# maxclients 128 + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys with an +# EXPIRE set. It will try to start freeing keys that are going to expire +# in little time and preserve keys with a longer time to live. +# Redis will also try to remove objects from free lists if possible. +# +# If all this fails, Redis will start to reply with errors to commands +# that will use more memory, like SET, LPUSH, and so on, and will continue +# to reply to most read-only commands like GET. +# +# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a +# 'state' server or cache, not as a real DB. When Redis is used as a real +# database the memory usage will grow over the weeks, it will be obvious if +# it is going to use too much memory in the long run, and you'll have the time +# to upgrade. With maxmemory after the limit is reached you'll start to get +# errors for write operations, and this may even lead to DB inconsistency. +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached? You can select among five behavior: +# +# volatile-lru -> remove the key with an expire set using an LRU algorithm +# allkeys-lru -> remove any key accordingly to the LRU algorithm +# volatile-random -> remove a random key with an expire set +# allkeys->random -> remove a random key, any key +# volatile-ttl -> remove the key with the nearest expire time (minor TTL) +# noeviction -> don't expire at all, just return an error on write operations +# +# Note: with all the kind of policies, Redis will return an error on write +# operations, when there are not suitable keys for eviction. +# +# At the date of writing this commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy volatile-lru + +# LRU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can select as well the sample +# size to check. For instance for default Redis will check three keys and +# pick the one that was used less recently, you can change the sample size +# using the following configuration directive. +# +# maxmemory-samples 3 + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. If you can live +# with the idea that the latest records will be lost if something like a crash +# happens this is the preferred way to run Redis. If instead you care a lot +# about your data and don't want to that a single record can get lost you should +# enable the append only mode: when this mode is enabled Redis will append +# every write operation received in the file appendonly.aof. This file will +# be read on startup in order to rebuild the full dataset in memory. +# +# Note that you can have both the async dumps and the append only file if you +# like (you have to comment the "save" statements above to disable the dumps). +# Still if append only mode is enabled Redis will load the data from the +# log file at startup ignoring the dump.rdb file. +# +# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append +# log file in background when it gets too big. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") +# appendfilename appendonly.aof + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only if one second passed since the last fsync. Compromise. +# +# The default is "everysec" that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving the durability of Redis is +# the same as "appendfsync none", that in pratical terms means that it is +# possible to lost up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size will growth by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (or if no rewrite happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a precentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 1024 + +################################ VIRTUAL MEMORY ############################### + +### WARNING! Virtual Memory is deprecated in Redis 2.4 +### The use of Virtual Memory is strongly discouraged. + +### WARNING! Virtual Memory is deprecated in Redis 2.4 +### The use of Virtual Memory is strongly discouraged. + +# Virtual Memory allows Redis to work with datasets bigger than the actual +# amount of RAM needed to hold the whole dataset in memory. +# In order to do so very used keys are taken in memory while the other keys +# are swapped into a swap file, similarly to what operating systems do +# with memory pages. +# +# To enable VM just set 'vm-enabled' to yes, and set the following three +# VM parameters accordingly to your needs. + +vm-enabled no +# vm-enabled yes + +# This is the path of the Redis swap file. As you can guess, swap files +# can't be shared by different Redis instances, so make sure to use a swap +# file for every redis process you are running. Redis will complain if the +# swap file is already in use. +# +# The best kind of storage for the Redis swap file (that's accessed at random) +# is a Solid State Disk (SSD). +# +# *** WARNING *** if you are using a shared hosting the default of putting +# the swap file under /tmp is not secure. Create a dir with access granted +# only to Redis user and configure Redis to create the swap file there. +vm-swap-file /tmp/redis.swap + +# vm-max-memory configures the VM to use at max the specified amount of +# RAM. Everything that deos not fit will be swapped on disk *if* possible, that +# is, if there is still enough contiguous space in the swap file. +# +# With vm-max-memory 0 the system will swap everything it can. Not a good +# default, just specify the max amount of RAM you can in bytes, but it's +# better to leave some margin. For instance specify an amount of RAM +# that's more or less between 60 and 80% of your free RAM. +vm-max-memory 0 + +# Redis swap files is split into pages. An object can be saved using multiple +# contiguous pages, but pages can't be shared between different objects. +# So if your page is too big, small objects swapped out on disk will waste +# a lot of space. If you page is too small, there is less space in the swap +# file (assuming you configured the same number of total swap file pages). +# +# If you use a lot of small objects, use a page size of 64 or 32 bytes. +# If you use a lot of big objects, use a bigger page size. +# If unsure, use the default :) +vm-page-size 32 + +# Number of total memory pages in the swap file. +# Given that the page table (a bitmap of free/used pages) is taken in memory, +# every 8 pages on disk will consume 1 byte of RAM. +# +# The total swap size is vm-page-size * vm-pages +# +# With the default of 32-bytes memory pages and 134217728 pages Redis will +# use a 4 GB swap file, that will use 16 MB of RAM for the page table. +# +# It's better to use the smallest acceptable value for your application, +# but the default is large in order to work in most conditions. +vm-pages 134217728 + +# Max number of VM I/O threads running at the same time. +# This threads are used to read/write data from/to swap file, since they +# also encode and decode objects from disk to memory or the reverse, a bigger +# number of threads can help with big objects even if they can't help with +# I/O itself as the physical device may not be able to couple with many +# reads/writes operations at the same time. +# +# The special value of 0 turn off threaded I/O and enables the blocking +# Virtual Memory implementation. +vm-max-threads 4 + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded in a special way (much more memory efficient) when they +# have at max a given numer of elements, and the biggest element does not +# exceed a given threshold. You can configure this limits with the following +# configuration directives. +hash-max-zipmap-entries 512 +hash-max-zipmap-value 64 + +# Similarly to hashes, small lists are also encoded in a special way in order +# to save a lot of space. The special representation is only used when +# you are under the following limits: +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happens to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into an hash table +# that is rhashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# include /path/to/local.conf +# include /path/to/other.conf