Commit b6e4bd65 authored by Marc Gravell's avatar Marc Gravell

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget;...

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget; update tests; change namespace of keyspace isolation - that was an error
parent a541e22d
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NuGet.CommandLine" version="2.8.2" />
<package id="Redis-64" version="2.8.12" />
<package id="NuGet.CommandLine" version="2.8.3" />
<package id="Redis-64" version="2.8.17" />
</packages>
\ No newline at end of file
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7000
\ No newline at end of file
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7000
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7001
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7001
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7002
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7002
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7003
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7003
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7004
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7004
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7005
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7005
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6379
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6379
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6381
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6381
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6380
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6380
@start ..\packages\Redis-64.2.8.12\redis-server.exe master.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe slave.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe secure.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe master.conf
@..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@..\packages\Redis-64.2.8.12\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe slave.conf
@..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
......@@ -4,7 +4,7 @@
using System.Threading.Tasks;
using BookSleeve;
using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests
{
[TestFixture]
......@@ -664,6 +664,22 @@ private void Incr(IDatabase database, RedisKey key, int delta, ref int total)
database.StringIncrement(key, delta, CommandFlags.FireAndForget);
total += delta;
}
[Test]
public void WrappedDatabasePrefixIntegration()
{
using (var conn = Create())
{
var db = conn.GetDatabase().WithKeyPrefix("abc");
db.KeyDelete("count");
db.StringIncrement("count");
db.StringIncrement("count");
db.StringIncrement("count");
int count = (int)conn.GetDatabase().StringGet("abccount");
Assert.AreEqual(3, count);
}
}
}
}
using System;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -15,7 +16,7 @@ public sealed class BatchWrapperTests
public void Initialize()
{
mock = new Mock<IBatch>();
wrapper = new BatchWrapper(mock.Object, "prefix:");
wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
......@@ -4,7 +4,8 @@
using System.Net;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -18,7 +19,7 @@ public sealed class DatabaseWrapperTests
public void Initialize()
{
mock = new Mock<IDatabase>();
wrapper = new DatabaseWrapper(mock.Object, "prefix:");
wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......@@ -149,7 +150,7 @@ public void HashLength()
public void HashScan()
{
wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......@@ -604,7 +605,7 @@ public void SetRemove_2()
public void SetScan()
{
wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......@@ -774,7 +775,7 @@ public void SortedSetRemoveRangeByValue()
public void SortedSetScan()
{
wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......
......@@ -5,10 +5,6 @@ namespace StackExchange.Redis.Tests
[TestFixture]
public class Lex : TestBase
{
protected override string GetConfiguration()
{
return "ubuntu";
}
[Test]
public void QueryRangeAndLengthByLex()
......
......@@ -14,7 +14,7 @@ protected override string GetConfiguration()
return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword;
}
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "FLUSHDB cannot be issued to a slave")]
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")]
public void CannotFlushSlave()
{
ConfigurationOptions config = GetMasterSlaveConfig();
......@@ -73,7 +73,7 @@ public void DeslaveGoesToPrimary()
}
catch (RedisConnectionException ex)
{
Assert.AreEqual("No connection is available to service this operation: EXISTS", ex.Message);
Assert.AreEqual("No connection is available to service this operation: EXISTS DeslaveGoesToPrimary", ex.Message);
}
primary.MakeMaster(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates | ReplicationChangeOptions.SetTiebreaker);
......
......@@ -73,6 +73,8 @@ public void CheckDatabaseMethodsUseKeys(Type type)
{
case "KeyRandom":
case "KeyRandomAsync":
case "Publish":
case "PublishAsync":
continue; // they're fine, but don't want to widen check to return type
}
......
......@@ -33,9 +33,5 @@ public void SubscriberCount()
Assert.IsTrue(channels.Contains(channel));
}
}
protected override string GetConfiguration()
{
return "ubuntu";
}
}
}
using System;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -15,7 +16,7 @@ public sealed class TransactionWrapperTests
public void Initialize()
{
mock = new Mock<ITransaction>();
wrapper = new TransactionWrapper(mock.Object, "prefix:");
wrapper = new TransactionWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System;
namespace StackExchange.Redis.Tests
......
......@@ -4,7 +4,8 @@
using System.Net;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -18,7 +19,7 @@ public sealed class WrapperBaseTests
public void Initialize()
{
mock = new Mock<IDatabaseAsync>();
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, "prefix:");
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
......@@ -163,7 +163,7 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedResult ? " exists" : " does not exists");
}
......@@ -221,7 +221,7 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedEqual ? " == " : " != ")
+ expectedValue;
}
......
using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch
{
public BatchWrapper(IBatch inner, RedisKey prefix)
public BatchWrapper(IBatch inner, byte[] prefix)
: base(inner, prefix)
{
}
......
using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
/// <summary>
/// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>.
......@@ -49,7 +49,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
throw new ArgumentNullException("keyPrefix");
}
if (keyPrefix.Value.Length == 0)
if (keyPrefix.IsEmpty)
{
return database; // fine - you can keep using the original, then
}
......@@ -62,7 +62,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
database = wrapper.Inner;
}
return new DatabaseWrapper(database, keyPrefix);
return new DatabaseWrapper(database, keyPrefix.AsPrefix());
}
}
}
......@@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.Net;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase
{
public DatabaseWrapper(IDatabase inner, RedisKey prefix)
public DatabaseWrapper(IDatabase inner, byte[] prefix)
: base(inner, prefix)
{
}
......
using System;
using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction
{
public TransactionWrapper(ITransaction inner, RedisKey prefix)
public TransactionWrapper(ITransaction inner, byte[] prefix)
: base(inner, prefix)
{
}
......
......@@ -4,17 +4,17 @@
using System.Net;
using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync
{
private readonly TInner _inner;
private readonly RedisKey _prefix;
private readonly byte[] _keyPrefix;
internal WrapperBase(TInner inner, RedisKey prefix)
internal WrapperBase(TInner inner, byte[] keyPrefix)
{
_inner = inner;
_prefix = prefix;
_keyPrefix = keyPrefix;
}
public ConnectionMultiplexer Multiplexer
......@@ -27,9 +27,9 @@ internal TInner Inner
get { return _inner; }
}
internal RedisKey Prefix
internal byte[] Prefix
{
get { return _prefix; }
get { return _keyPrefix; }
}
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
......@@ -653,7 +653,7 @@ public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None)
protected internal RedisKey ToInner(RedisKey outer)
{
return this.Prefix + outer;
return RedisKey.WithPrefix(_keyPrefix, outer);
}
protected RedisKey ToInnerOrDefault(RedisKey outer)
......@@ -713,7 +713,7 @@ protected RedisKey[] ToInner(RedisKey[] outer)
protected RedisValue ToInner(RedisValue outer)
{
return RedisKey.Concatenate(this.Prefix, outer);
return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
}
protected RedisValue SortByToInner(RedisValue outer)
......@@ -761,7 +761,7 @@ protected RedisValue[] SortGetToInner(RedisValue[] outer)
protected RedisChannel ToInner(RedisChannel outer)
{
return RedisKey.Concatenate((byte[])Prefix, (byte[])outer);
return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
}
private Func<RedisKey, RedisKey> mapFunction;
......
......@@ -612,7 +612,7 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey
this.Key = key;
}
public override string CommandAndKey { get { return Command + " " + Key; } }
public override string CommandAndKey { get { return Command + " " + ((string)Key); } }
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(Key);
......
......@@ -354,7 +354,15 @@ internal void SetUnknownDatabase()
internal void Write(RedisKey key)
{
WriteUnified(outStream, key.Value);
var val = key.KeyValue;
if (val is string)
{
WriteUnified(outStream, key.KeyPrefix, (string)val);
}
else
{
WriteUnified(outStream, key.KeyPrefix, (byte[])val);
}
}
internal void Write(RedisChannel channel)
......@@ -494,6 +502,61 @@ internal static byte ToHexNibble(int value)
return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value);
}
void WriteUnified(Stream stream, byte[] prefix, string value)
{
stream.WriteByte((byte)'$');
if (value == null)
{
WriteRaw(stream, -1); // note that not many things like this...
}
else
{
int encodedLength = Encoding.UTF8.GetByteCount(value);
if (prefix == null)
{
WriteRaw(stream, encodedLength);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
else
{
WriteRaw(stream, prefix.Length + encodedLength);
stream.Write(prefix, 0, prefix.Length);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
}
}
unsafe void WriteRaw(Stream stream, string value, int encodedLength)
{
if (encodedLength <= ScratchSize)
{
int bytes = Encoding.UTF8.GetBytes(value, 0, value.Length, outScratch, 0);
stream.Write(outScratch, 0, bytes);
}
else
{
fixed (char* c = value)
fixed (byte* b = outScratch)
{
int charsRemaining = value.Length, charOffset = 0, bytesWritten;
while (charsRemaining > Scratch_CharsPerBlock)
{
bytesWritten = outEncoder.GetBytes(c + charOffset, Scratch_CharsPerBlock, b, ScratchSize, false);
stream.Write(outScratch, 0, bytesWritten);
charOffset += Scratch_CharsPerBlock;
charsRemaining -= Scratch_CharsPerBlock;
}
bytesWritten = outEncoder.GetBytes(c + charOffset, charsRemaining, b, ScratchSize, true);
if (bytesWritten != 0) stream.Write(outScratch, 0, bytesWritten);
}
}
}
const int ScratchSize = 512;
static readonly int Scratch_CharsPerBlock = ScratchSize / Encoding.UTF8.GetMaxByteCount(1);
private readonly byte[] outScratch = new byte[ScratchSize];
private readonly Encoder outEncoder = Encoding.UTF8.GetEncoder();
static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{
stream.WriteByte((byte)'$');
......
......@@ -2322,7 +2322,7 @@ public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCo
{
this.ttlCommand = ttlCommand;
}
public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + Key; } }
public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + (string)Key; } }
public IEnumerable<Message> GetMessages(PhysicalConnection connection)
{
......
......@@ -61,15 +61,16 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer)
/// <summary>
/// Computes the hash-slot that would be used by the given key
/// </summary>
public unsafe int HashSlot(byte[] key)
public unsafe int HashSlot(RedisKey key)
{
//HASH_SLOT = CRC16(key) mod 16384
if (key == null) return NoSlot;
if (key.IsNull) return NoSlot;
unchecked
{
fixed (byte* ptr = key)
var blob = (byte[])key;
fixed (byte* ptr = blob)
{
int offset = 0, count = key.Length, start, end;
int offset = 0, count = blob.Length, start, end;
if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0
&& (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0
&& --end != start)
......@@ -183,10 +184,9 @@ internal int CombineSlot(int oldSlot, int newSlot)
}
internal int CombineSlot(int oldSlot, RedisKey key)
{
byte[] blob = key.Value;
if (oldSlot == MultipleSlots || (blob = key.Value) == null) return oldSlot;
if (oldSlot == MultipleSlots || key.IsNull) return oldSlot;
int newSlot = HashSlot(blob);
int newSlot = HashSlot(key);
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
......
......@@ -327,6 +327,16 @@ slave-priority 100
# Note that you must specify a directory here, not a file name.
# heapdir <directory path(absolute or relative)>
# If Redis is to be used as an in-memory-only cache without any kind of
# persistence, then the fork() mechanism used by the background AOF/RDB
# persistence is unneccessary. As an optimization, all persistence can be
# turned off in the Windows version of Redis. This will disable the creation of
# the memory mapped heap file, redirect heap allocations to the system heap
# allocator, and disable commands that would otherwise cause fork() operations:
# BGSAVE and BGREWRITEAOF. This flag may not be combined with any of the other
# flags that configure AOF and RDB operations.
# persistence-available [(yes)|no]
# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# accordingly to the eviction policy selected (see maxmemmory-policy).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment