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()
{