diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index c21d6b6d4..463da7c7f 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -26,6 +26,7 @@ - Adds: Support for `LMPOP` with `.ListLeftPop()`/`.ListLeftPopAsync()` and `.ListRightPop()`/`.ListRightPopAsync()` ([#2094 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2094)) - Adds: Support for `ZMPOP` with `.SortedSetPop()`/`.SortedSetPopAsync()` ([#2094 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2094)) - Adds: Support for `XAUTOCLAIM` with `.StreamAutoClaim()`/.`StreamAutoClaimAsync()` and `.StreamAutoClaimIdsOnly()`/.`StreamAutoClaimIdsOnlyAsync()` ([#2095 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/2095)) +- Adds: Support for `OBJECT FREQ` with `.KeyFrequency()`/`.KeyFrequencyAsync()` ([#2105 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2105)) ## 2.5.61 diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index 780ae0a47..5ca31542f 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -754,6 +754,17 @@ public interface IDatabase : IRedis, IDatabaseAsync /// DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None); + /// + /// Returns the logarithmic access frequency counter of the object stored at . + /// The command is only available when the maxmemory-policy configuration directive is set to + /// one of the LFU policies. + /// + /// The key to get a frequency count for. + /// The flags to use for this operation. + /// The number of logarithmic access frequency counter, ( if the key does not exist). + /// + long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None); + /// /// Returns the time since the object stored at the specified key is idle (not requested by read or write operations). /// diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index d9a53ee2d..724be9caa 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -730,6 +730,17 @@ public interface IDatabaseAsync : IRedisAsync /// Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + /// + /// Returns the logarithmic access frequency counter of the object stored at . + /// The command is only available when the maxmemory-policy configuration directive is set to + /// one of the LFU policies. + /// + /// The key to get a frequency count for. + /// The flags to use for this operation. + /// The number of logarithmic access frequency counter, ( if the key does not exist). + /// + Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + /// /// Returns the time since the object stored at the specified key is idle (not requested by read or write operations). /// diff --git a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs index ad58d37b1..62a9ae6fb 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -182,6 +182,9 @@ public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFl public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.KeyExpireTime(ToInner(key), flags); + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyFrequency(ToInner(key), flags); + public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.KeyIdleTime(ToInner(key), flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs index ec5b44539..200cd67b5 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs @@ -193,6 +193,9 @@ public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.KeyExpireTimeAsync(ToInner(key), flags); + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyFrequencyAsync(ToInner(key), flags); + public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.KeyIdleTimeAsync(ToInner(key), flags); diff --git a/src/StackExchange.Redis/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI.Shipped.txt index d98c12330..517c48d3a 100644 --- a/src/StackExchange.Redis/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI.Shipped.txt @@ -555,6 +555,7 @@ StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.ExpireWhen when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool StackExchange.Redis.IDatabase.KeyExpireTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.DateTime? +StackExchange.Redis.IDatabase.KeyFrequency(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? StackExchange.Redis.IDatabase.KeyIdleTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.TimeSpan? StackExchange.Redis.IDatabase.KeyMigrate(StackExchange.Redis.RedisKey key, System.Net.EndPoint! toServer, int toDatabase = 0, int timeoutMilliseconds = 0, StackExchange.Redis.MigrateOptions migrateOptions = StackExchange.Redis.MigrateOptions.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void StackExchange.Redis.IDatabase.KeyMove(StackExchange.Redis.RedisKey key, int database, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool @@ -773,6 +774,7 @@ StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey k StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.ExpireWhen when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.KeyExpireTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyFrequencyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.KeyIdleTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.KeyMigrateAsync(StackExchange.Redis.RedisKey key, System.Net.EndPoint! toServer, int toDatabase = 0, int timeoutMilliseconds = 0, StackExchange.Redis.MigrateOptions migrateOptions = StackExchange.Redis.MigrateOptions.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.KeyMoveAsync(StackExchange.Redis.RedisKey key, int database, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 4f302f223..47553653e 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -877,11 +877,24 @@ public Task KeyExpireAsync(RedisKey key, DateTime? expire, ExpireWhen when return ExecuteAsync(msg, ResultProcessor.NullableDateTimeFromMilliseconds); } + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + return ExecuteSync(msg, ResultProcessor.NullableInt64); + } + + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + return ExecuteAsync(msg, ResultProcessor.NullableInt64); + } + public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); } + public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs index 3b405ae8b..44d4c60ac 100644 --- a/src/StackExchange.Redis/RedisLiterals.cs +++ b/src/StackExchange.Redis/RedisLiterals.cs @@ -63,6 +63,7 @@ public static readonly RedisValue EXAT = "EXAT", EXISTS = "EXISTS", FLUSH = "FLUSH", + FREQ = "FREQ", GET = "GET", GETNAME = "GETNAME", GT = "GT", diff --git a/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index f73162634..1f47d5d9f 100644 --- a/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -319,6 +319,13 @@ public void KeyExpireTime() mock.Verify(_ => _.KeyExpireTime("prefix:key", CommandFlags.None)); } + [Fact] + public void KeyFrequency() + { + wrapper.KeyFrequency("key", CommandFlags.None); + mock.Verify(_ => _.KeyFrequency("prefix:key", CommandFlags.None)); + } + [Fact] public void KeyMigrate() { diff --git a/tests/StackExchange.Redis.Tests/Keys.cs b/tests/StackExchange.Redis.Tests/Keys.cs index 65dd48c51..cd09cf9de 100644 --- a/tests/StackExchange.Redis.Tests/Keys.cs +++ b/tests/StackExchange.Redis.Tests/Keys.cs @@ -296,4 +296,47 @@ public async Task KeyRefCount() Assert.Null(db.KeyRefCount(keyNotExists)); Assert.Null(await db.KeyRefCountAsync(keyNotExists)); } + + [Fact] + public async Task KeyFrequency() + { + using var conn = Create(allowAdmin: true, require: RedisFeatures.v4_0_0); + + var key = Me(); + var db = conn.GetDatabase(); + var server = GetServer(conn); + + var serverConfig = server.ConfigGet("maxmemory-policy"); + var maxMemoryPolicy = serverConfig.Length == 1 ? serverConfig[0].Value : ""; + Log($"maxmemory-policy detected as {maxMemoryPolicy}"); + var isLfu = maxMemoryPolicy.Contains("lfu"); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + db.StringGet(key); + + if (isLfu) + { + var count = db.KeyFrequency(key); + Assert.True(count > 0); + + count = await db.KeyFrequencyAsync(key); + Assert.True(count > 0); + + // Key not exists + db.KeyDelete(key, CommandFlags.FireAndForget); + var res = db.KeyFrequency(key); + Assert.Null(res); + + res = await db.KeyFrequencyAsync(key); + Assert.Null(res); + } + else + { + var ex = Assert.Throws(() => db.KeyFrequency(key)); + Assert.Contains("An LFU maxmemory policy is not selected", ex.Message); + ex = await Assert.ThrowsAsync(() => db.KeyFrequencyAsync(key)); + Assert.Contains("An LFU maxmemory policy is not selected", ex.Message); + } + } } diff --git a/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs b/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs index 1d1058008..6acbb41f9 100644 --- a/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -280,6 +280,13 @@ public void KeyExpireTimeAsync() mock.Verify(_ => _.KeyExpireTimeAsync("prefix:key", CommandFlags.None)); } + [Fact] + public void KeyFrequencyAsync() + { + wrapper.KeyFrequencyAsync("key", CommandFlags.None); + mock.Verify(_ => _.KeyFrequencyAsync("prefix:key", CommandFlags.None)); + } + [Fact] public void KeyMigrateAsync() {