diff --git a/Makefile b/Makefile index ab8d908ca..19d3f5ff3 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PATH := ./work/redis-git/src:${PATH} ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) PROFILE ?= ci SUPPORTED_TEST_ENV_VERSIONS := 8.4 8.2 8.0 7.4 7.2 -DEFAULT_TEST_ENV_VERSION := 8.2 +DEFAULT_TEST_ENV_VERSION := 8.4 REDIS_ENV_WORK_DIR := $(or ${REDIS_ENV_WORK_DIR},$(ROOT_DIR)/work) start: diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3f8b7d07a..bdc59a15b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,62 +1,30 @@ -Lettuce 7.0.0.BETA2 RELEASE NOTES +Lettuce 7.2.0 RELEASE NOTES ============================== -The Lettuce team is pleased to announce the second beta release of Lettuce 7.0! +The Lettuce team is pleased to announce the Lettuce 7.2.0 minor release! -The release focuses on introducing **Maintenance events support** functionality, API improvements, and cleanup of deprecated features. +Lettuce 7.2.0 supports Redis 2.6+ up to Redis 8.x. In terms of Java runtime, Lettuce requires at least Java 8 and +works with Java 24. The driver is tested against Redis 8.4, Redis 8.2, Redis 8.0, Redis 7.4 and Redis 7.2. -### Key changes -- **Maintenance events support** for graceful maintenance handling -- **Enhanced JSON API** with `String`-based access to avoid unnecessary conversions -- **Removal of deprecated APIs** and options as part of the major version upgrade -As part of the 7.0 line, this beta also removes several deprecated APIs and options. +Thanks to all contributors who made Lettuce 7.2.0.RELEASE possible. -Lettuce 7.0.0.BETA2 supports Redis 2.6+ up to Redis 8.x and requires Java 8 or newer. The driver is tested against Redis 8.2, 8.0, 7.4, and 7.2. +📗 Links +Reference documentation: https://lettuce.io/core/7.2.0.RELEASE/reference/ +Javadoc: https://lettuce.io/core/7.2.0.RELEASE/api/ -Thanks to all contributors who made Lettuce 7.0.0.BETA2 possible! +⭐ New Features +* Add ftHybrid by @a-TODO-rov in https://github.com/redis/lettuce/pull/3540 +* Expose method to add upstream driver libraries to CLIENT SETINFO payload by @viktoriya-kutsarova in https://github.com/redis/lettuce/pull/3542 -If you need any support, meet Lettuce at +🐞 Bug Fixes +* SearchArgs.returnField with alias produces malformed redis command #3528 by @tishun in https://github.com/redis/lettuce/pull/3530 +* fix consistency with get(int) that returns wrapped DelegateJsonObject/DelegateJsonArray for nested structures by @NeatGuyCoding in https://github.com/redis/lettuce/pull/3464 -* GitHub Discussions: https://github.com/lettuce-io/lettuce-core/discussions -* Stack Overflow (Questions): https://stackoverflow.com/questions/tagged/lettuce -* Join the chat at https://discord.gg/redis and look for the "Help:Tools Lettuce" channel -* GitHub Issues (Bug reports, feature requests): https://github.com/lettuce-io/lettuce-core/issues -* Documentation: https://lettuce.io/core/7.0.0.BETA2/reference/ -* Javadoc: https://lettuce.io/core/7.0.0.BETA2/api/ +💡 Other +* Bumping Netty to 4.2.5.Final (main) by @tishun in https://github.com/redis/lettuce/pull/3536 -# Changes +❤️ New Contributors +* @NeatGuyCoding made their first contribution in https://github.com/redis/lettuce/pull/3464 +* @viktoriya-kutsarova made their first contribution in https://github.com/redis/lettuce/pull/3542 -## 🔥 Breaking Changes - -- chore: remove usage of deprecated connection methods in command APIs in integration tests (#3328) (#3343) -- Remove deprecated dnsResolver option (#3328) (#3333) -- Remove deprecated `reset()` method from Lettuce API and internals (#3395) -- Make Utility Class constructor private to enforce noninstantiability (#3266) -- Enable adaptive refresh by default #3249 (#3316) -- ISSUE#3328 - Remove deprecated code from ISSUE#1314 (#3351) -- chore: deprecated withPassword(String) method (#3328) (#3350) -- Remove deprecated Utf8StringCodec class (#3328) (#3389) -- chore: remove deprecated default timeout from AbstractRedisClient (#3328) (#3344) -- chore: remove deprecated ClientOptions#cancelCommandsOnReconnectFailure (#3328) (#3346) - -## 🚀 New Features - -- Add support for EPSILON and WITHATTRIBS arguments in VSIM command (#3449) -- Add String-based JSON API to avoid unnecessary conversions (#3369) (#3394) -- React to maintenance events #3345 (#3354) - -## 🐛 Bug Fixes -- Rename maintenance notification configuration properties (#3450) -- Timeouts seen during endpoint re-bind and migrate (#3426) -- Fix a NullPointerException in DelegateJsonObject #3417 (#3418) - -## 💡 Other - -- Timeouts seen during endpoint re-bind and migrate (#3426) -- Return name method to ProtocolKeyword public interface. (#3424) -- Refactor JsonValue to Object mapping #3412 (#3413) -- Using non-native transports with SocketOptions should cause an error (#3279) - ---- - -**Full Changelog**: [6.8.0.RELEASE...7.0.0.BETA2](https://github.com/redis/lettuce/compare/6.8.0.RELEASE...7.0.0.BETA2) +**Full Changelog**: https://github.com/redis/lettuce/compare/7.1.0.RELEASE...7.2.0.RELEASE diff --git a/docs/static/benchmarks/data.js b/docs/static/benchmarks/data.js index 91c23f203..8a1b261a3 100644 --- a/docs/static/benchmarks/data.js +++ b/docs/static/benchmarks/data.js @@ -1,5 +1,5 @@ window.BENCHMARK_DATA = { - "lastUpdate": 1763344796578, + "lastUpdate": 1765418416201, "repoUrl": "https://github.com/redis/lettuce", "entries": { "Benchmark": [ @@ -122000,6 +122000,10182 @@ window.BENCHMARK_DATA = { "extra": "iterations: 10\nforks: 1\nthreads: 1" } ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "1343845572ea5184d56e134f125e6b4e74536023", + "message": "add Benchmark (jmh) benchmark result for 153a221bcf963c0e59cff191c0b8adf068336b1c", + "timestamp": "2025-11-17T01:59:56Z", + "url": "https://github.com/redis/lettuce/commit/1343845572ea5184d56e134f125e6b4e74536023" + }, + "date": 1763431163657, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 145425.1752356835, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15776.300172578418, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9022.333403433931, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 148448.41512273304, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 15990.400248250182, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16280.42442693128, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 312007.19784989126, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 146977.2766205407, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.4968869341308, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 372.8353815673134, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.263308274218474, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.806256252627148, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 163846.57846538565, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18390.43849369376, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10686.72117011084, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 167334.8922676447, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18805.086614882148, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19100.00504628871, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164888.01367773535, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.776601320859136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.407701784927305, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.215071624286217, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.31539147640845, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66195.34165522738, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13430.724435022314, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0075820635161492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9920858664156033, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 196.27544534522283, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.90991671224319, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 208.9409983416627, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.61258429726405, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.427570068227541, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.992606988909463, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.7220099625224, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.65015894079306, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 36.41752366088124, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.79280228274546, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 856479.0312375303, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 850917.5262857403, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.799158619539043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 162.81423568037584, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16424.522887056854, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9735.466414136385, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.81556650918138, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 389.0596737196923, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 579.9221230653492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.19381521137397, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 124.07002036168983, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.9234001313848825, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6358573815182983, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.893967324354954, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.30452855714712, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.19179687430306, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.31756617313024, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.89687873763965, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 68.19903991067017, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 227.5250121406551, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 193.41635397135232, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1730.6940397451563, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15787.85218212314, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 154504.47089175542, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 39.903403004251736, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 174.9914883608712, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 363.51308262638486, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7426.430976875985, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 651.5630205080213, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13601.706335444353, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Aleksandar Todorov", + "username": "a-TODO-rov", + "email": "a_t_todorov@yahoo.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "e8d59fc318b71a3952137b23f2cf319f25375ff9", + "message": "Bump to 8.4-GA-pre.3 (#3516)", + "timestamp": "2025-11-18T10:09:26Z", + "url": "https://github.com/redis/lettuce/commit/e8d59fc318b71a3952137b23f2cf319f25375ff9" + }, + "date": 1763517590047, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 143471.1260319274, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15536.024349881636, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 8867.838433891477, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 144257.19704207138, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16354.414062999198, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16352.695631764605, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 307644.64372374595, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 145638.10345209876, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 93.34716517693336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 374.76287043442653, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.33139642886915, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.792132373082782, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 162232.26139962717, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18281.375532510967, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10459.027932398098, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 163914.10074368413, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18739.030463823095, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 18773.35978228847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 162023.38065313053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.12189055625072, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.338128821693424, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.83505262407444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.12600197751569, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66463.40445037998, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13479.700719773595, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0080383350989979, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9947525692294302, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 189.7884962413136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.70583874011739, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 202.36144833028365, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.35677374235983, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.429731698824107, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.028461623237996, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.070751832979408, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.57637324081027, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.23665925853248, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.62642003257247, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 856644.6716984942, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 883372.6707388327, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.659342512005207, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 169.91803736523616, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16415.012782482434, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9587.256233731683, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 56.93927290755505, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 389.09406840799704, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 575.4251218041776, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.10707570235057, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.63882969532861, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.88984465759621, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6368542232958343, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.842834184369906, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.186350287116774, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.195487119194052, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.217966542955086, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.67494252801492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.89522323835754, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 232.09904050387567, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 173.37892331079078, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1724.8232883793066, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14644.75390668383, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 143147.33930372793, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.8351158563208, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.09557992837355, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 360.2852415371021, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7962.452441138931, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 640.0974402679451, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 14228.55729311461, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Aleksandar Todorov", + "username": "a-TODO-rov", + "email": "a_t_todorov@yahoo.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "295546cd03ffb8834c676ccb8f5f1d4b7b80a869", + "message": "Add support for XREADGROUP CLAIM arg (#3486)\n\n* Add support for XREADGROUP CLAIM arg\n\n* Add NOACK scenario in ITs\n\n* Fix NOACK IT scenario. Add test.\n\n* Implement new fields as integers. Fix tests.\n\n* Rename values for consistency.\n\n* Address some comments from code review", + "timestamp": "2025-11-19T17:19:45Z", + "url": "https://github.com/redis/lettuce/commit/295546cd03ffb8834c676ccb8f5f1d4b7b80a869" + }, + "date": 1763603990881, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 143058.11324885784, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15089.67997116322, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9016.305714056156, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 144955.4051795243, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16067.926412442059, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16030.220780302723, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 307376.5734255596, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 144632.66296212492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.86528572257649, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 371.0634066593049, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 30.67931288859632, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 8.97298220532208, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 161528.7275168634, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18375.236473543286, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10564.401782198542, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 163949.95569146296, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18745.17689788032, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 18616.202882070767, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 162171.41428506264, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.91326157802646, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.4655242872088, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.095907971260875, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.17536846414278, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 60004.370329665035, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13326.026307653057, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0017749711969317, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9888798470690835, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 201.87279096852959, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.35304057140618, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 204.33390460524691, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.31390547586751, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.404562866862367, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.895009340607027, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.68347489503167, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.446445730815647, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 31.90155809827154, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.1306515263481, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 882544.8693064891, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 868820.1331902674, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.449415866525193, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 165.52790147186673, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16176.944448378705, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9569.042409421203, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 54.50391958393973, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.67977475905104, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.9096582120343, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.88994583006287, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.24900768185262, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.8941893876755245, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6296001066053907, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.797140248309347, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.347074336799462, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.057128829754266, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.138377857005215, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.43566115072768, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.35839908057497, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 223.935360454232, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 176.6873075799612, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1656.0103387179686, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14820.155307814537, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 138572.2052800503, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.781392635419195, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 174.30667264877795, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 361.6896445522414, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7676.137396141106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 692.0211342392788, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13720.836092571137, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Magnus Hyllander", + "username": "mhyllander", + "email": "magnus@hyllander.org" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "0796a4e62eda877162abf7ae4bd25660ee2d834f", + "message": "Preserve null values when parsing SearchReplies (#3518)\n\nEncodedComplexOutput was skipping null values instead of passing them on. Then SearchReplyParser needs to store null values as they are and not try to decode them.\nThis affected both RESP2 and RESP3 parsing.\n\nAdded two integration tests in RediSearchAggregateIntegrationTests to verify that nulls in JSON documents are parsed correctly.", + "timestamp": "2025-11-20T11:43:44Z", + "url": "https://github.com/redis/lettuce/commit/0796a4e62eda877162abf7ae4bd25660ee2d834f" + }, + "date": 1763690373636, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 146686.65266361908, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15525.220362605216, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9196.12024415634, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 147605.37335965908, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16032.849033975008, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16058.808099611135, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 310119.857831058, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 147648.67992237647, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.95445700893659, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 370.3915848462243, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.07842481303707, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.7545053908833665, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 164437.99262620453, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18494.151714907843, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10788.745537268076, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 167083.8428704679, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19053.889271847896, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19041.37622281422, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164770.16683723233, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.612587059579, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.334008983912426, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.770803564462312, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.25508694066146, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 72289.20363146882, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13430.109452152354, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.003476346953068, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9927683020002831, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 230.02104249229473, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.66100062857429, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 226.45102485720082, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.14791411660694, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.413590641123948, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.965202343616891, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.937770664218696, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.557467802999525, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.1428589192369, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.30036486424538, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 880622.3858063277, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 874462.8779701659, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.602482967131078, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 170.65281632951138, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16253.677694348813, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9775.245177515779, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 54.778344896178226, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.3910549314686, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 572.9673004056651, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.91132370700511, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 124.4072326386123, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 7.225509917947733, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6326813866114622, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.790962146394342, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.11111803828963, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.096750865373036, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.179461278972676, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.51150372864298, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.59017054762128, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 222.06175690295913, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 166.9961864721444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1681.2972957749607, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15879.950512647207, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 158245.33391103096, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 40.53470878050217, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 174.97231304998076, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 364.95754170342724, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7586.328052821196, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 678.4534521345365, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13273.503015534832, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Aleksandar Todorov", + "username": "a-TODO-rov", + "email": "a_t_todorov@yahoo.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "7fefd6a31d9611ce475e565707ab316d93b85d36", + "message": "Modify release notes and bum pom version. (#3525)", + "timestamp": "2025-11-21T09:31:35Z", + "url": "https://github.com/redis/lettuce/commit/7fefd6a31d9611ce475e565707ab316d93b85d36" + }, + "date": 1763776736238, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 114254.20929287173, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 9649.439042413567, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 7595.841513079747, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 115522.89719416466, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 10533.810572782366, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 10712.762330990026, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 247792.6429586946, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 115501.20119832519, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 86.12847370516567, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 280.31064957568975, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 30.06033279315043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 12.325383780492714, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 127848.43188897667, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 11793.581836647998, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 9127.47098367415, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 132615.92769538373, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 12329.796188666656, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 12720.257880073992, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 130389.29383362495, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 38.15707903137218, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 34.46951620651913, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 26.68075892428833, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 26.897588988547444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 46566.396785934536, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 21103.34709446979, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0230112625672538, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 1.0239192777582073, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 214.65302321569843, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 66.54059377756766, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 226.24193965090112, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 68.21317708997547, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 3.8511111265224747, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 13.807497499579526, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 23.416069058107613, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 19.54465935029142, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 35.62926621744318, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 63.188950144450565, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 939785.4760082482, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 919292.4376589411, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 39.38611323680848, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 174.18423431450756, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 10680.101570161616, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 8394.046736794267, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 54.871115952736, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 346.04142696075934, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 425.54512236753874, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 69.55717629170775, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 119.75102669714525, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 7.547694226105284, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.2936172679071856, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 33.78213660474529, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 28.42073812140548, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 20.65732979368503, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 36.235505481601116, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 63.077474711099896, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.19394424899626, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 247.46605807978762, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 205.9000692233502, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1861.1859231704716, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14935.820993602965, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 152358.43274477957, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 49.09948569050513, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 173.61877526965475, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 382.1665721956823, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7531.418864739668, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 730.5312093663031, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 14035.923337396238, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "838fe47bef2b7c178a3f2ffbc658622ab5ff5f62", + "message": "add Benchmark (jmh) benchmark result for 7fefd6a31d9611ce475e565707ab316d93b85d36", + "timestamp": "2025-11-22T01:59:00Z", + "url": "https://github.com/redis/lettuce/commit/838fe47bef2b7c178a3f2ffbc658622ab5ff5f62" + }, + "date": 1763863220691, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 156882.15341910857, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 16192.841655195909, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9814.020362154655, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 157909.27179967935, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16822.50698816081, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16755.253372504863, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 333694.75900221727, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 159048.5147042899, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.96751390541928, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 377.1241027223791, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.212066692792398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.832213460101684, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 178645.18956392366, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19612.214201618488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11611.07717915378, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 180855.46831332386, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19833.380991060687, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19885.17894461983, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 178728.0621774317, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.713294965846515, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.528802406865765, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.61649225843762, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.3486988366041, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66845.78862118596, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13480.392531122507, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0053084087569764, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9925468913919218, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 199.19778257462238, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.01312267243404, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 204.44106821182214, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.4053637166767, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.409893200907742, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.286792362122284, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.075145400597552, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 23.731750977589883, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.0797813673134, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 88.42829055901917, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 861700.0481019535, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 859821.6651654541, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.860046785014276, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 168.8341487925347, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16825.555230949336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10362.07198799134, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 56.634832839771775, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 389.87271519058754, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 577.8797253784409, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.08105731853424, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.75318871556833, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.892064623412873, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6322380203265155, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.96848558140058, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.230354329372094, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.16582045789384, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.350287740560253, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.84387580725016, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 70.14696539638757, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 238.02400389315198, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 176.77983945126053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1577.9142468282448, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15235.136148881302, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 138834.05548788962, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.572071170953244, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 177.7504024340077, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 372.4506841729345, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7407.651506129163, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 689.2904563032243, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13226.083011532857, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "73a7bab40cb8df4e8589eb51a5fc8f597249af6e", + "message": "add Benchmark (jmh) benchmark result for 838fe47bef2b7c178a3f2ffbc658622ab5ff5f62", + "timestamp": "2025-11-23T02:00:24Z", + "url": "https://github.com/redis/lettuce/commit/73a7bab40cb8df4e8589eb51a5fc8f597249af6e" + }, + "date": 1763949621387, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 144085.72194325336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15407.934236541483, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 8991.136981140527, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 146232.19413289614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16131.56147497544, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 15972.25257875329, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 310081.1993790237, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 145398.11497600444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.66999537280245, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 371.42505444113783, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.15857403710105, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.7468424949903625, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 163616.90775802493, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18517.106048363214, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10663.400484763202, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 165811.34438308945, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18894.85611650147, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19049.11351975162, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 163960.4580979288, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.92812727760011, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.162251167998704, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.09063364208624, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.152258251890338, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66212.19383514892, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 32984.70991616291, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.000558619537356, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9950260706451628, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 209.14030779181567, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 77.15867535561156, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 195.35667022249936, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.09659811407425, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.402341013909344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.974853933562098, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.80389409740157, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.49076064201787, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.01451089324013, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.18103529374847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 862051.7776395144, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 861104.358132845, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.601741838313806, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 160.39073218884852, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16402.529457764096, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9688.485760925112, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.29366054457434, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 396.916700797586, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 575.9005765403065, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.90175714824205, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 122.93295638982143, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.879254825234149, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.632176387964263, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.857871184487625, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.238664787778482, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.090092457621733, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.219566421803387, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.70780396156518, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.52587873719776, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 210.17241968810754, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 188.27253611514888, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1568.5166366259486, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15972.662983301565, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 138351.22366914852, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.44220959845084, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 177.44841177827828, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 361.50558759630877, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7539.336322046331, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 679.7519292766976, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13160.668319607965, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "0e49f7367870c6b499d01ffd7614727e1bc8fdd6", + "message": "add Benchmark (jmh) benchmark result for 73a7bab40cb8df4e8589eb51a5fc8f597249af6e", + "timestamp": "2025-11-24T02:00:24Z", + "url": "https://github.com/redis/lettuce/commit/0e49f7367870c6b499d01ffd7614727e1bc8fdd6" + }, + "date": 1764035993613, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 146055.62172401597, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15466.224663978215, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9149.591754138748, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 148472.4983932104, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16491.708342943828, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16227.742552058688, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 311814.3210452916, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 148607.66051168583, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 101.38164025529898, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 371.5059194884426, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.18399331133687, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.782656167269524, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 165814.44465110492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18414.240327650663, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10856.346647137723, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 167710.1798536145, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18844.713894374967, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19064.29740186766, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 166765.40008534005, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.57499599999946, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.30856953121804, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.683708202884524, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.14719112029147, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 26919.830012741622, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13504.372427820103, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0051530711629768, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9905060525230132, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 221.58319894572588, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.22163260062416, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 203.05370526788246, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 76.96356681095043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.413417094430008, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.943892598260053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.78946330249433, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.52228117191297, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.08651521994009, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 85.5485848843641, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 854485.0884122879, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 864535.9620366839, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 28.467452416056716, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 169.3537273686199, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16293.16790999602, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9741.114711780783, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.931007847907175, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.20368409473883, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 572.3049498804346, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.99169865120142, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.31747246865254, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.895513407792667, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6336431039451059, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.890866382628776, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.1888322634383, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 14.156817036138332, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.301010949909488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.80081620567493, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.76128647671888, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 232.02188324669973, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 173.91880250302466, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1692.7447719056277, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15209.623156041256, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 140824.57515635798, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.561055831917166, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 184.16199180795036, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 369.50282606777216, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7629.582958055381, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 656.2705621504549, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13705.27234633757, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Tihomir Krasimirov Mateev", + "username": "tishun", + "email": "tihomir.mateev@redis.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "a4eab3746e66e322c769a57e4b582974cede44a8", + "message": "SearchArgs.returnField with alias produces malformed redis command #3528 (#3530)", + "timestamp": "2025-11-25T22:12:36Z", + "url": "https://github.com/redis/lettuce/commit/a4eab3746e66e322c769a57e4b582974cede44a8" + }, + "date": 1764122380314, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 154804.14642465394, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 16002.22532751497, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9510.745907311672, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 155054.68093308696, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16660.098647035058, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16635.631254968008, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 327026.47768434015, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 154044.08372648875, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.78456390985043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 374.0408320125521, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.166689982099122, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.762051852192712, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 173965.21238766093, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19317.513296385365, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11524.355898354928, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 178057.9709021792, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 20038.562124686898, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 20000.094040353626, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 177446.67685991531, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.693105954901945, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.344276516856, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.290310315090903, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.289101073720804, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 60018.948937024295, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35955.25132750663, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.002756075598966, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9887353698707125, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 209.1305765496376, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.87561938309085, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 217.15013118353704, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.59154230953862, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.407417072698352, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 16.08759275681208, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.728589207189042, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.493572063350378, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.12877873101182, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 90.48744420534194, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 836001.7257094553, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 855921.509215412, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.698731912596152, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 170.9701316337417, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16859.790842220787, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10266.639656863865, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 56.018235794093115, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 388.3611426827602, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.9861915525829, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.30317909149194, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.85626140986649, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.915082644014669, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6338984019602275, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.89784635075846, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.22033633677346, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.192560550336855, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.303748417045778, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 68.15276733509505, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 68.43206955960419, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 224.44341803142393, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 171.80287830072086, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1714.0298851614134, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 16309.083439106844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 146065.58271312862, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.73726535773806, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 173.16742986831449, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 371.3315728483203, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7736.547263314572, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 666.4967983397204, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13592.198568351392, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Tihomir Krasimirov Mateev", + "username": "tishun", + "email": "tihomir.mateev@redis.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "274af385cb2f28d56cab4a116e320972a640587d", + "message": "Bumping Netty to 4.2.5.Final (#3536)", + "timestamp": "2025-11-26T08:13:45Z", + "url": "https://github.com/redis/lettuce/commit/274af385cb2f28d56cab4a116e320972a640587d" + }, + "date": 1764208761112, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 146943.80735985446, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15762.979860729934, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9169.161523186163, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 148613.13720619114, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16235.403051904344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16268.602588691636, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 311304.5694775152, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 146160.91338818774, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.97155993161901, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 369.09912383876843, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.123484087485387, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.782413966517457, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 165406.9638046796, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18965.904221508106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10878.817201006244, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 167448.33973936376, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19176.09280828254, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19285.39348088393, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 167088.30452760897, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.68405636359969, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.52275863194821, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.71196509211158, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.17543333872862, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 27176.520291163157, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 36131.895921257106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0048993574664171, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9929988416159012, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 191.47214260616812, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.35459354642398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 216.87432056460943, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.05330585035315, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.399652458531757, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.97390513849503, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.074454779458314, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.489169474324225, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.06816900395825, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.78481592906891, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 847354.0580230106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 858330.8251038548, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.658211216301048, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 159.8655280674704, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16388.501261471723, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9807.171437594134, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 57.33581546752712, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.8807933778799, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.5244869373242, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.86463549920715, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.42002926853884, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.8947786808649365, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6328889771716311, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.82678959448929, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.1647103794433, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.114340495317089, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.211773520633372, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.48344354428137, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.7576388203585, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 246.25938289501613, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 179.64815111805726, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1608.9723337350267, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 16625.40243283641, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 152941.24484257237, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 40.08435614731939, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 180.91840020335135, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 363.4312874119206, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7555.760071403296, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 643.7121226721518, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13300.273039653353, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "8f2080a0bcf55dc817a61967c150fd3b7973c3e3", + "message": "add Benchmark (jmh) benchmark result for 274af385cb2f28d56cab4a116e320972a640587d", + "timestamp": "2025-11-27T01:59:24Z", + "url": "https://github.com/redis/lettuce/commit/8f2080a0bcf55dc817a61967c150fd3b7973c3e3" + }, + "date": 1764295155272, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 109940.84455094705, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 9670.306459121119, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 7614.662494720828, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 109519.47479087689, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 10328.580638620479, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 10287.76772332122, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 233616.58267928482, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 110248.01264457006, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 86.84859821569891, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 284.6222393626632, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 28.819033699614096, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 13.58681212398478, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 123019.2082980928, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 11495.642869823108, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 8461.909722507578, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 124085.50186009887, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 12292.240697678402, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 12268.484589450389, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 123820.02654165456, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 37.87192646334053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 34.48211011049977, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 26.63986075891372, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 27.19419624463136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 46774.306022471545, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 21082.44999943331, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0240200109854152, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 1.0205245964430985, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 215.35729011999888, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 66.22562736889358, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 216.14457549603353, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 67.81480689198625, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 3.8430495139139302, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 13.794619082007321, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 23.307084175812786, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 19.486212171002457, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 35.41844868695523, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 63.086204631894496, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 928115.0552999566, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 957231.5466954043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 39.063760654765176, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 174.24132921047672, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 10521.384517402917, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 8077.807674700307, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 53.329470489863944, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 353.9036253922757, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 428.6964186934316, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 67.98306870987133, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 114.82096049593326, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 7.749353983971571, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.293398958291456, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 32.97470460581791, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 28.159858576038545, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 20.2407482417349, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 37.13401472805084, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 63.17360562104709, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 62.17978388526308, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 234.9676341512042, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 205.7785894918, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1600.265097917364, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15172.495170371365, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 143321.28146543243, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 49.28740954929334, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 174.86024000402762, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 377.80139527837787, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7584.154609141679, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 724.2489313366865, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13947.528678652616, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "fe791966b2f0aaa115d0b7a5cf664f0fa07072d5", + "message": "add Benchmark (jmh) benchmark result for 8f2080a0bcf55dc817a61967c150fd3b7973c3e3", + "timestamp": "2025-11-28T01:59:19Z", + "url": "https://github.com/redis/lettuce/commit/fe791966b2f0aaa115d0b7a5cf664f0fa07072d5" + }, + "date": 1764381558286, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 156302.44650441472, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 16328.149725937637, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9670.247118616699, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 157405.12793942343, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16646.4491979091, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16636.85506123052, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 333391.60264094436, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 156864.44121893292, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.24390726494315, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 375.25300728060586, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.394968632273685, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 8.99983579864929, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 175178.25338186772, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19204.318880600702, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11330.932342202948, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 178272.37732159693, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19857.56064213377, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19820.622155650217, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 176367.72469391173, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.07128063128734, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.21236947168792, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.334514766324258, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.13878454143632, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 60288.66108491998, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 33052.70979465344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 0.9911977134865259, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9952845862116557, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 199.38328217179475, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.8316118101445, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 211.47888073752637, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.33510281171789, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.421545236280603, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.999437590685309, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.979262643711632, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.544958724100777, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.02778112998355, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.90266810001505, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 870641.737264639, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 877028.3450039087, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.814695975105913, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 161.95618113043085, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 17005.457648128, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10284.106665147423, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.51351970523812, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 389.884787289363, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.7417135943864, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 50.13684211297719, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 124.05101471107255, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.894412306360995, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.633276074508433, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.923101230544738, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.209142304289223, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.313301309398971, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.425239597738486, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.38843017999986, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.24240990166493, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 226.89549656657874, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 178.0134665121752, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1799.3752816478486, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 18459.549678726897, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 139696.09855395363, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.77475752004282, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 176.7701340061484, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 368.38029197532694, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7804.883616277771, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 663.2207447924512, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 14061.464775975517, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "289398b02686700cbb3ac76996beab160e37f55f", + "message": "add Benchmark (jmh) benchmark result for fe791966b2f0aaa115d0b7a5cf664f0fa07072d5", + "timestamp": "2025-11-29T01:59:21Z", + "url": "https://github.com/redis/lettuce/commit/289398b02686700cbb3ac76996beab160e37f55f" + }, + "date": 1764467996604, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 145461.02368066984, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15659.251748277089, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9050.71230196401, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 146438.33681426008, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16258.962808811397, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16340.44696195687, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 309209.53346479323, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 145584.32406367818, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.73744681882832, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 370.19915915831086, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.22210325552488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.770880546270492, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 163789.4456672023, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18685.263778311775, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10771.522181656255, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 165270.49760693318, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18916.63529998702, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19291.376182724485, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164646.5634286545, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.6269319901408, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.25653509515003, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.152249536520593, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.835817134421557, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 72431.61130474247, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35998.44437068084, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0062398985006178, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9888507147910006, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 189.2858076040594, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.81369646019431, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 204.23903943369282, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.07164684303238, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.416057532968162, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.89381803745211, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.216868979726417, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.592358215937644, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 31.99225756322203, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.42230164575561, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 853063.1434618186, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 866950.2287065368, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.656972767211975, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 198.97208990516148, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16323.816630501096, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9719.727744797949, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.662304751250076, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.98155593783883, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 577.6735941928506, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 50.028526223779814, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.72824381011006, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.926154610959479, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6329135870731524, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.8774446975476, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.226452472647807, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.108436338188472, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.139143780474024, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 69.12153278461136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.82621770143844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 229.7601834759318, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 185.7250406537235, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1553.7521291318847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 16595.318456926798, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 141404.36667193106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.76865406235103, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 177.93906888713997, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 371.68500464833625, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7553.686502633376, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 669.4338080303647, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 14086.086391676727, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "2f226a60d90398c42b3efd2a46027ea0bc140b07", + "message": "add Benchmark (jmh) benchmark result for 289398b02686700cbb3ac76996beab160e37f55f", + "timestamp": "2025-11-30T02:00:00Z", + "url": "https://github.com/redis/lettuce/commit/2f226a60d90398c42b3efd2a46027ea0bc140b07" + }, + "date": 1764554495501, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 143952.57309792953, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15793.04692263259, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9117.018888249168, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 147036.42846104215, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16323.016183097123, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16191.114211906475, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 309076.0696446651, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 147151.26711671203, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.17045215370581, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 368.3393254700505, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.534922162080107, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 8.975599780665721, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 164425.06398352332, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18709.230918458772, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10888.549789259983, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 166621.87496461073, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19368.944115443992, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19261.8729392397, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164994.2059696334, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.666429745644734, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.21901334151549, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.679776754570707, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.1833068997248, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66280.21667951152, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 33003.37292686316, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0041388437738568, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9901283652371454, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 206.59532846412077, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.8039432899155, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 209.46987505420097, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.16644996190696, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.423979488733396, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.019849685820507, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 30.475046010613397, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.493515046061226, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.004292811651, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.81226637633401, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 856690.002589647, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 890574.428623699, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.815942011324683, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 160.38794093180917, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16557.704233272925, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9698.23405833721, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 56.1790496786478, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.9683219465214, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 572.9422690282004, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.94812953232954, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 125.6493601734799, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.896175715339014, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6331921990084, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.965245476096726, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.23452258526347, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.26396310604684, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.380161637650929, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.96140643068644, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 68.02070772755488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 226.53636060634267, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 173.78592394367476, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1597.7311965701297, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14574.835384522336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 153567.01021398272, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.574358992171376, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.69098535670153, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 364.90503853389026, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7663.812861574415, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 693.5476753136044, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13372.913153559686, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "a1bb28d8ed7f076fa4132af5dbebf0dc3cb98d12", + "message": "add Benchmark (jmh) benchmark result for 2f226a60d90398c42b3efd2a46027ea0bc140b07", + "timestamp": "2025-12-01T02:01:39Z", + "url": "https://github.com/redis/lettuce/commit/a1bb28d8ed7f076fa4132af5dbebf0dc3cb98d12" + }, + "date": 1764640791987, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 144786.635123406, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15399.517619496424, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9040.586899803937, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 147315.85585870786, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 15978.87531792487, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 15873.669454819645, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 311183.50627186825, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 147048.80940539174, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.06863810127524, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 373.7431218117025, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.16452465724783, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 8.985318306217447, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 164238.17475628605, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18284.726122837623, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10744.090838297145, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 165409.73687099142, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18894.625003712958, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19078.12332610696, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 165910.89064427902, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.921395473811984, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.45731009861981, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.11852509499321, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.16076347471858, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 27005.357401288027, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35791.782868601505, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0022789853506817, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9881785362636781, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 203.13967699588738, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.57214716347754, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 211.06871046680845, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.31891746468933, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.405450657210799, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.976268525136012, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.32115956485216, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.491205858037596, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 31.93018010350073, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.40509910357629, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 867868.2910296367, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 851939.6058662139, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.606096115000042, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 175.80179807380586, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16172.815365902692, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9581.94146106458, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 56.92153815698572, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.506809647089, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.2976220021475, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.137813080518995, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.37230402824355, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.887097229928939, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.632535845569999, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.904492315933936, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.173234488300295, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.10185606511745, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.187043755104446, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 69.70188619056414, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 70.03288310935083, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 226.46365393885017, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 181.44534007822455, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1724.2158101163957, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 17308.91933630527, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 154037.79127101082, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.70942214376642, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.02820718659012, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 374.00960158159126, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7355.895743922064, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 647.4342391159614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13744.267647072342, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "d7e6a0a832c742321735fa92ad22d32d54712b92", + "message": "add Benchmark (jmh) benchmark result for a1bb28d8ed7f076fa4132af5dbebf0dc3cb98d12", + "timestamp": "2025-12-02T01:59:55Z", + "url": "https://github.com/redis/lettuce/commit/d7e6a0a832c742321735fa92ad22d32d54712b92" + }, + "date": 1764727195653, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 154720.20487149846, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15830.669758633117, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9477.500772022246, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 155741.1168116195, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16415.836313507454, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16717.93377646202, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 329971.4219250147, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 157596.18909503616, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.8026275723296, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 370.3796563551046, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.252566345066498, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.784146718651724, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 175763.00335626418, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19241.952507321566, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11291.72017077167, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 178164.78229790786, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19802.055939069163, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19886.837072076327, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 174154.21338410344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.03077620023155, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.22783747068007, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.14627199010032, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.196456079010005, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 65432.80703741654, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35834.81723567401, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0075988192625627, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9883192357758844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 202.70611632962337, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.69347724057727, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 213.90240963782085, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.03153256527861, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.415190507861032, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 18.9878337567733, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.814637194709785, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.5264928150824, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.18901251164426, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.30650092862854, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 861517.021296222, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 874070.3256195122, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.774640913537848, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 159.74437949799707, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16771.319933488743, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10212.610697399303, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 57.242244343759545, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.10680646869184, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 574.5674496686195, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.016467232426116, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 124.9780283009189, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.9188537727592045, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.632996556673566, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.90928903534719, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.22728714195456, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.195702510861588, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.32388578493325, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.75025975804206, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.84764847474447, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 229.29286740798398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 181.35971889015158, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1630.5178764807024, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14538.676829449128, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 138518.94491318957, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.550150718166236, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 173.72241604375068, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 368.91376820185013, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7589.954687597428, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 690.2123938736037, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13635.765921271453, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "9230a1726dd2699115f7ff82a2d63aab707499d3", + "message": "add Benchmark (jmh) benchmark result for d7e6a0a832c742321735fa92ad22d32d54712b92", + "timestamp": "2025-12-03T01:59:59Z", + "url": "https://github.com/redis/lettuce/commit/9230a1726dd2699115f7ff82a2d63aab707499d3" + }, + "date": 1764813608243, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 146037.9778731059, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15614.030877285924, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9151.432319010903, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 148250.11131580477, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16249.276587477614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16233.80969656562, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 311946.6087399778, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 146345.17672121024, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.63559722755619, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 372.96133938706305, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.28969183779151, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.769692801502872, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 166782.1443746022, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18869.94279423569, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10874.85434076493, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 168568.46379482228, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19168.20596496722, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19520.366156463388, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164419.378039004, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.54704528237782, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.2582413102253, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.696928512817568, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.193435092020735, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 72238.99560498125, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 36063.31480650535, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0005178144558906, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9924648464709103, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 209.4675859257427, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.66457770540889, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 192.8174443381388, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.21061245350529, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.40622131445849, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.02436036009389, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.629039076387677, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.51357938094589, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.11690210435745, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.17124010342413, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 853045.223148708, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 881634.227343193, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.73376090076597, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 160.15506960033557, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16263.64538826049, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9677.817794687498, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 57.89732885979665, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.0670245548459, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.9884524282136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.28038941721246, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 125.11683683081085, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.918793145092815, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6338538327204208, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 28.035341364661758, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.134535348878348, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.176735180339637, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.226091791485853, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.53792387909617, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.73542392438398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 221.51945727533106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 177.09529745635504, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1574.7263778458655, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 16265.66738706542, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 160564.73791747837, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.678587247187565, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 176.65181958774406, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 365.19051724414584, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7784.261657726194, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 696.2296456091815, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13078.224525333517, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Viktoriya Kutsarova", + "username": "viktoriya-kutsarova", + "email": "viktoriya.kutsarova@gmail.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "be132f9ceeacd03a0d982c847e4221748b4aeea9", + "message": "Expose method to add upstream driver libraries to CLIENT SETINFO payload (#3542)\n\n* Expose method to add upstream driver libraries to CLIENT SETINFO payload\n\n* Create a separate class to hold driver name and upstream drivers information\n\n* Fix PR comments\n\n* Update since tag", + "timestamp": "2025-12-04T13:05:24Z", + "url": "https://github.com/redis/lettuce/commit/be132f9ceeacd03a0d982c847e4221748b4aeea9" + }, + "date": 1764900005228, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 151915.35672757705, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15747.645106900958, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9489.61517064948, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 151790.03553494392, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16342.86269469529, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16458.882537394227, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 325157.13812063844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 152971.92979059735, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.99359086034018, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 369.7848473193013, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.160495029950948, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.7756708109347015, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 171193.3414816467, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18981.172157132678, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11207.861950810367, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 176685.10062947666, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19671.996672653455, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19678.635294196214, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 173084.48588682507, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.6278228678499, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.28584021523787, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.63529850659329, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.97928303021423, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 26888.391576743714, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 36036.383855888285, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0050090993127667, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9908227464693231, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 206.05830353840156, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.839932195844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 212.44682721599202, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 78.27115807675803, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.423206369441969, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.07988446626005, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.01949054206104, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.516372337405876, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.34192013428408, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 85.44790906941094, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 855392.6171795558, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 879020.7381949766, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.821947971551896, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 169.87438701537044, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16604.337504747047, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10140.137243258148, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 59.4783568482993, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.23510440823344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 573.6734590554115, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.15642717040346, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 125.01328739397854, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.899401444361773, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6336546669882472, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.872226494600596, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.195392996969343, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.221282429266097, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.288824725475383, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 70.28994493040422, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.74386568818929, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 226.05821443828535, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 176.25236096047163, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1730.8580474554133, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14504.42649541508, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 140388.522678166, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.96225205669462, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.09930009064968, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 366.06495373927055, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7364.55018433715, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 685.8908077021313, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13797.780421047613, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "Aleksandar Todorov", + "username": "a-TODO-rov", + "email": "a_t_todorov@yahoo.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "fdcfb74e504eb1a189db288bfa8006bf6009210e", + "message": "Release 7.2.0 (#3559)", + "timestamp": "2025-12-05T13:45:45Z", + "url": "https://github.com/redis/lettuce/commit/fdcfb74e504eb1a189db288bfa8006bf6009210e" + }, + "date": 1764986358316, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 155477.65767263883, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15950.418260918474, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9522.921440371749, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 155252.25437406963, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16505.860698496766, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16584.99889177786, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 331310.58834987256, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 156839.04258294, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.78975333423668, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 373.8168898944494, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.138914302279936, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.755110351941012, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 172289.15029595263, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18829.064392089225, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11226.97663561248, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 176491.67950633474, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19617.516562035064, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19529.485663065876, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 175318.75622164557, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.54319567187403, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.281115352509474, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.680332833855488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.9829983443294, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 65577.27155363494, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13430.723997380817, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0034515175612708, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9891253520726062, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 218.76817075840398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.50941028227878, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 209.4908225868096, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 76.95221582374889, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.410902773236838, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.00778840262962, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 31.543551564015893, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.506054290851914, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.41880236095898, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 95.50066481238163, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 858074.2847774795, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 865691.1756402804, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.574101817172153, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 179.83427108409603, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16759.738394271077, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10013.757680568251, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.28974827100698, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.21515929544375, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 577.7248098474745, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 49.64807440665633, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.5835761590819, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.864956179452589, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6327927388601935, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.900219943634028, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.21621694055902, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.099470033368245, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.152489311420592, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 69.73137839059123, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.9133369173488, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 218.07100395027254, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 166.76195364309467, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1869.4954697609955, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15993.820470589224, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 146847.18757302305, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.43957572706708, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 179.15829285731112, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 364.46870266406444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7461.218431838129, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 730.9158329249094, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 12952.499276253671, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "yang", + "username": "yangy0000", + "email": "43356004+yangy0000@users.noreply.github.com" + }, + "committer": { + "name": "GitHub", + "username": "web-flow", + "email": "noreply@github.com" + }, + "id": "f65b8d18789aad6aee58346d991e744df00103c6", + "message": "Fix command queue corruption on encoding failures (#3443)\n\n* Correctly handling the encoding error for Lettuce [POC]\n\nSummary:\nAdd encoding error tracking to prevent command queue corruption\n\n - Add markEncodingError() and hasEncodingError() methods to RedisCommand interface\n - Implement encoding error flag in Command class with volatile boolean\n - Mark commands with encoding errors in CommandEncoder on encode failures\n - Add lazy cleanup of encoding failures in CommandHandler response processing\n - Update all RedisCommand implementations to support encoding error tracking\n - Add comprehensive unit tests and integration tests for encoding error handling\n\nFixes issue where encoding failures could corrupt the outstanding command queue by leaving failed commands in the stack without proper cleanup, causing responses to be matched to wrong commands.\n\nTest Plan: UTs, Integration testing\n\nReviewers: yayang, ureview\n\nReviewed By: yayang\n\nTags: #has_java\n\nJIRA Issues: REDIS-14050\n\nDifferential Revision: https://code.uberinternal.com/D19068147\n\n* Fix error command handling code logic and add integration test for encoding failure\n\nSummary: Fix error command handling code logic and add integration test for encoding failure\n\nTest Plan: unittest, integration test\n\nReviewers: #ldap_storage_sre_cache, ureview, jingzhao\n\nReviewed By: #ldap_storage_sre_cache, jingzhao\n\nTags: #has_java\n\nJIRA Issues: REDIS-14192\n\nDifferential Revision: https://code.uberinternal.com/D19271701\n\n* latest changes\n\n* Addressing the reactive streams issue\n\n* Addressing the encoding issues\nAddressing some general cases\n\n* Formatting issues\n\n* Test failures addressed\n\n* Polishing\n\n---------\n\nCo-authored-by: Jing Zhao \nCo-authored-by: Tihomir Mateev ", + "timestamp": "2025-12-06T22:27:13Z", + "url": "https://github.com/redis/lettuce/commit/f65b8d18789aad6aee58346d991e744df00103c6" + }, + "date": 1765072813033, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 156155.72655745858, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 16255.426444074395, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9713.925544009962, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 156097.42145826682, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16697.08959856466, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16798.874101864556, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 333838.4579379896, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 157405.25451515225, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.44537191093796, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 372.4878373194823, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.16361951174274, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.784187966051155, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 175633.99417242847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19316.740499276053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11503.385240169853, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 180059.59122081858, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19983.31092724068, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19902.05395728554, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 176833.0003186325, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.10240012119425, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.203393604933595, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.152069476585762, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.87802739209103, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 26906.46924029118, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35903.27548582481, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0059817399944602, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9955312653765139, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 216.62876673550562, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 74.08445801774965, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 204.55841166879628, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 81.02426476956865, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.444314687484146, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.930736080044852, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.819916205563594, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.46091814834891, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 31.91837868683327, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.47112061023098, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 866397.4497189436, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 869904.1610663064, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 28.52537775864792, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 169.7699060215278, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16980.980465519835, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10238.743473243836, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.91507056024368, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 387.54351928190897, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 577.6546579722086, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.98791958961634, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 125.51417458361318, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.9152636570211286, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6318546362679381, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 28.21640930518107, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.359695762042506, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 14.211110203591053, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 15.147647875331128, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 70.26484530280634, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 68.11103922296125, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 230.75428030607833, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 175.9837996551885, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1697.0709844011271, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14599.995489401135, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 151856.38313974722, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.44756171770938, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 178.51767303817715, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 365.8032670233902, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7703.931479462425, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 689.7463344422366, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13840.021888270343, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "c6b42f0df36ba77d928b96336207c5f423cbf0e2", + "message": "add Benchmark (jmh) benchmark result for f65b8d18789aad6aee58346d991e744df00103c6", + "timestamp": "2025-12-07T02:00:17Z", + "url": "https://github.com/redis/lettuce/commit/c6b42f0df36ba77d928b96336207c5f423cbf0e2" + }, + "date": 1765159220833, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 146684.7899330211, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15637.253283007341, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9230.517414344613, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 148646.69388087565, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16187.783616427949, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16259.53415705213, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 313133.91115632665, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 147334.85418952888, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 89.88459615252886, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 374.37740039418327, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.134203304234255, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 11.488195577479928, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 164457.5355196185, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18469.32511084252, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10692.30160663847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 165877.89090090344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 18846.867097530176, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 18850.08234887039, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 164749.13421647385, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 41.02335586203508, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.18178134733429, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.085525144379336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.12486781054955, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66084.12232690913, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35864.679028820814, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0031730550538462, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9880859864119286, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 214.3922565292741, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.38882564952664, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 187.14294745255765, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.20643891058349, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.4045219409988885, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.09757315463538, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.59910687065996, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.497868051625034, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.09840575781504, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 85.50844741518871, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 860810.4984531043, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 869100.168729187, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.690967401311774, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 175.70586863038616, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16095.407294582652, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9726.407344787094, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 54.24746033225482, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.65285299888563, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 578.3169699118332, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.933364530397725, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.47829454737477, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.86452162311191, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6333518073852813, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.851928627415514, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.16775192297238, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.084120007074285, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.230197184127581, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 69.37426096942161, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 67.1871358280446, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 216.37451449788153, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 172.94466494565918, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1637.4343920750355, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15913.217913076945, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 136155.06525912546, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 39.36543646917428, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 181.1935644576775, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 388.30175982532774, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7574.18470392571, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 688.0294271882011, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13863.513563304883, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "5c5f117550c4323b55337c865ea1dc7bfa616f99", + "message": "add Benchmark (jmh) benchmark result for c6b42f0df36ba77d928b96336207c5f423cbf0e2", + "timestamp": "2025-12-08T02:00:24Z", + "url": "https://github.com/redis/lettuce/commit/5c5f117550c4323b55337c865ea1dc7bfa616f99" + }, + "date": 1765245599123, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 153110.91367867854, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15840.815517750729, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 9442.7142904002, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 153290.99640741342, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16813.296827054022, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16688.092227579175, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 324972.7102685868, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 153700.97689307743, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.13187735025248, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 371.1844598142326, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 31.140121883885534, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.778390376944998, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 174471.7755473391, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 19262.652073554473, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 11182.913005089742, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 174144.8755983533, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19612.883608002023, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19563.947004107744, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 172351.82835738262, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.607996570073986, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.23383229602487, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.69182475455626, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 31.32295721174471, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 66489.2205665706, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 13462.470023980422, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0039466787516746, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9956873034043632, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 172.31090249862095, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.61784501532085, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 210.08862234009607, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.28225256605303, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.409345072748205, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 15.056917077137744, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 28.33670466678108, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.491708959780972, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.12936411140208, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.46837936689614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 862002.8965211704, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 865675.3394805398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.71818297061869, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 162.95521039127897, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16596.05417462387, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 10056.135112420512, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 57.31472643865344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 388.4540730222715, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 576.1231911878829, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.9842576004699, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 124.35160491526972, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.925717920849507, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6364856274631852, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 28.178270006854895, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.130152500584416, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.166663734401794, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.203003729290927, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.50679578385873, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 68.13274788384841, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 244.95595040929447, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 172.67607014280688, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1707.8752168627136, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14556.961203709207, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 136855.45913123986, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 38.53243514935498, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.17265383141455, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 360.1962777814737, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7325.030868374243, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 703.0796634829683, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13902.353852325206, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "329c39c83cdd7edafe47821bae175b1178c63e0a", + "message": "add Benchmark (jmh) benchmark result for 5c5f117550c4323b55337c865ea1dc7bfa616f99", + "timestamp": "2025-12-09T02:00:02Z", + "url": "https://github.com/redis/lettuce/commit/329c39c83cdd7edafe47821bae175b1178c63e0a" + }, + "date": 1765332042624, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 143572.07617241287, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 15519.907192971596, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 8928.584928169828, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 144707.5856844848, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 16123.100630927274, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 16165.235343250222, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 302878.34298425977, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 145204.08484701155, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 90.39293245990513, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 370.70084566906445, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 34.24581109253344, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 7.755523684513726, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 161342.27737774057, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 18483.28442849812, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 10744.35788774065, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 165467.5313496413, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 19054.53399495614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 19138.26895615335, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 163068.7257389621, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 40.528057386227474, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 37.16951221180586, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 28.621522014774364, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 32.11881075830788, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 60284.95570739722, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 35920.38608596848, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.002995776089839, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 0.9886758312319228, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 210.55285656438755, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 73.43744516800658, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 209.02327288951847, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 77.05058624362681, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 4.411152749731215, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 14.96864733426087, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 27.71851552232161, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 20.498835680552908, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 32.08053083864837, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 84.76714997643869, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 852815.300352487, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 880440.2746962719, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 27.62259105026657, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 168.82568136923604, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 16360.536804630672, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 9471.46708802407, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.59877429701636, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 386.8380849813256, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 575.9515860529339, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 48.986908529736525, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 123.41891349084815, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 6.890386810329266, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.6334886956764422, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 27.854313088332553, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 23.089509410853477, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 13.113530269461668, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 14.176357683724595, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 67.81266328457353, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 69.75435255113366, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 218.90400880173948, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 170.8997434053716, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1596.4337983374398, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 14321.147609554977, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 136766.38376462122, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 43.013001725884564, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 175.77773532075486, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 367.68817262661844, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7676.036420569753, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 684.0370311193341, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13471.791047833318, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] + }, + { + "commit": { + "author": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "committer": { + "name": "github-action-benchmark", + "username": "github", + "email": "github@users.noreply.github.com" + }, + "id": "fa7e5d0c59db41ca69d88889f3c723fe1ed3e6ae", + "message": "add Benchmark (jmh) benchmark result for 329c39c83cdd7edafe47821bae175b1178c63e0a", + "timestamp": "2025-12-10T02:00:46Z", + "url": "https://github.com/redis/lettuce/commit/fa7e5d0c59db41ca69d88889f3c723fe1ed3e6ae" + }, + "date": 1765418412253, + "tool": "jmh", + "benches": [ + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSet", + "value": 114690.87849006588, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatch", + "value": 9812.978491878459, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.asyncSetBatchFlush", + "value": 7670.467736192544, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSet", + "value": 112701.74144673976, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatch", + "value": 10357.345045361712, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.reactiveSetBatchFlush", + "value": 10385.705641396124, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncList", + "value": 245694.18364218617, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.RedisClientBenchmark.syncSet", + "value": 114590.90174659497, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommands", + "value": 87.27566360346137, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.write3KeyedCommandsAsBatch", + "value": 286.66165500166034, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writeKeyedCommand", + "value": 29.169332888302506, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.ClusterDistributionChannelWriterBenchmark.writePlainCommand", + "value": 12.367260006116817, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSet", + "value": 131310.6662969135, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatch", + "value": 12003.824647666584, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.asyncSetBatchFlush", + "value": 8736.933749531176, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSet", + "value": 130869.21056808086, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatch", + "value": 12656.375278489677, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.reactiveSetBatchFlush", + "value": 12718.477786871143, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.RedisClusterClientBenchmark.syncSet", + "value": 130312.07566322852, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashDirect", + "value": 38.089134789701575, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashHeap", + "value": 34.47316943948069, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedDirect", + "value": 26.677870308001083, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.SlotHashBenchmark.measureSlotHashTaggedHeap", + "value": 26.926915978688083, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeAllSlots", + "value": 46849.920348552965, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.createClusterNodeLowerSlots", + "value": 21148.770737936553, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusAbsent", + "value": 1.0226796685962403, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.cluster.models.partitions.RedisClusterNodeBenchmark.querySlotStatusPresent", + "value": 1.0224575005195282, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyEstimatedSize", + "value": 209.28180170693955, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeKeyExactSize", + "value": 66.99329542046841, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueEstimatedSize", + "value": 215.69395382141641, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.ExactVsEstimatedSizeCodecBenchmark.encodeValueExactSize", + "value": 67.91282689503821, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.decodeUtf8Unpooled", + "value": 3.849600726480463, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeAsciiToBuf", + "value": 13.861954982979977, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeIsoToBuf", + "value": 23.099667716619493, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8PlainStringToBuf", + "value": 19.70343783121922, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8ToBuf", + "value": 35.51856734431906, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.codec.StringCodecBenchmark.encodeUtf8Unpooled", + "value": 63.64863797956836, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createBatchCommands", + "value": 931436.2067880336, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.createRegularCommands", + "value": 932385.5086328769, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeAsyncCommand", + "value": 39.70933600355977, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandFactoryBenchmark.executeCommandInterfaceCommand", + "value": 173.5516922951089, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.asyncSet", + "value": 10802.68151388459, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.RedisCommandsBenchmark.batchSet", + "value": 8358.404173713934, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.dynamic.intercept.InvocationProxyFactoryBenchmark.run", + "value": 55.279341613619444, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100Elements", + "value": 360.44123840962504, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure100ElementsWithResizeElement", + "value": 435.7074650086553, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16Elements", + "value": 68.99198433025393, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measure16ElementsWithResizeElement", + "value": 118.11501816481685, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureSingleElement", + "value": 7.438389190615875, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.output.ValueListOutputBenchmark.measureZeroElement", + "value": 0.29305813126967106, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createAsyncCommandUsingByteArrayCodec", + "value": 33.99119183470509, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingByteArrayCodec", + "value": 28.62050095984347, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.createCommandUsingStringCodec", + "value": 20.726282906448837, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingByteArrayCodec", + "value": 36.45718885082401, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingNewStringCodec", + "value": 65.11419212267143, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandBenchmark.encodeCommandUsingOldStringCodec", + "value": 65.11292064974131, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndRead", + "value": 242.9169335697026, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1", + "value": 208.15411497571205, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch10", + "value": 1798.3630755212457, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch100", + "value": 15015.956605112724, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.CommandHandlerBenchmark.measureNettyWriteAndReadBatch1000", + "value": 140611.3168878993, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisEndpointBenchmark.measureUserWrite", + "value": 50.573423091774735, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.protocol.RedisStateMachineBenchmark.measureDecode", + "value": 167.51684633892614, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.singleConnection", + "value": 383.8580036364034, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.AsyncConnectionPoolBenchmark.twentyConnections", + "value": 7687.263311561718, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.singleConnection", + "value": 697.5947343788105, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + }, + { + "name": "io.lettuce.core.support.GenericConnectionPoolBenchmark.twentyConnections", + "value": 13930.668980370188, + "unit": "ns/op", + "extra": "iterations: 10\nforks: 1\nthreads: 1" + } + ] } ] } diff --git a/pom.xml b/pom.xml index a9ed5032b..25856d33f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.lettuce lettuce-core - 7.1.0-SNAPSHOT + 7.3.0-SNAPSHOT jar Lettuce @@ -68,7 +68,7 @@ 1.14.2 1.2.4 4.9.0 - 4.2.4.Final + 4.2.5.Final 2.0.27 3.6.6 1.3.8 diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index 68245ab52..1b48a32e3 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -51,6 +51,7 @@ import io.lettuce.core.protocol.RedisCommand; import io.lettuce.core.search.AggregationReply; import io.lettuce.core.search.AggregationReply.Cursor; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; @@ -59,6 +60,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -741,6 +743,11 @@ public RedisFuture del(Iterable keys) { return dispatch(commandBuilder.del(keys)); } + @Override + public RedisFuture delex(K key, CompareCondition condition) { + return dispatch(commandBuilder.delex(key, condition)); + } + @Override public String digest(String script) { return digest(encodeScript(script)); @@ -1265,6 +1272,11 @@ public RedisFuture get(K key) { return dispatch(commandBuilder.get(key)); } + @Override + public RedisFuture digestKey(K key) { + return dispatch(commandBuilder.digestKey(key)); + } + public StatefulConnection getConnection() { return connection; } @@ -1696,6 +1708,11 @@ public RedisFuture> ftSearch(String index, V query) { return dispatch(searchCommandBuilder.ftSearch(index, query, SearchArgs. builder().build())); } + @Override + public RedisFuture> ftHybrid(String index, HybridArgs args) { + return dispatch(searchCommandBuilder.ftHybrid(index, args)); + } + @Override public RedisFuture> ftAggregate(String index, V query, AggregateArgs args) { return dispatch(searchCommandBuilder.ftAggregate(index, query, args)); @@ -2318,6 +2335,11 @@ public RedisFuture msetnx(Map map) { return dispatch(commandBuilder.msetnx(map)); } + @Override + public RedisFuture msetex(Map map, MSetExArgs args) { + return dispatch(commandBuilder.msetex(map, args)); + } + @Override public RedisFuture multi() { return dispatch(commandBuilder.multi()); diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index b0374474a..3a66d5c1a 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -53,6 +53,7 @@ import io.lettuce.core.search.AggregationReply; import io.lettuce.core.search.AggregationReply.Cursor; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -60,6 +61,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -811,6 +813,11 @@ public Mono del(Iterable keys) { return createMono(() -> commandBuilder.del(keys)); } + @Override + public Mono delex(K key, CompareCondition condition) { + return createMono(() -> commandBuilder.delex(key, condition)); + } + @Override public String digest(String script) { return digest(encodeScript(script)); @@ -1326,6 +1333,11 @@ public Mono get(K key) { return createMono(() -> commandBuilder.get(key)); } + @Override + public Mono digestKey(K key) { + return createMono(() -> commandBuilder.digestKey(key)); + } + public StatefulConnection getConnection() { return connection; } @@ -1768,6 +1780,11 @@ public Mono> ftSearch(String index, V query) { return createMono(() -> searchCommandBuilder.ftSearch(index, query, SearchArgs. builder().build())); } + @Override + public Mono> ftHybrid(String index, HybridArgs args) { + return createMono(() -> searchCommandBuilder.ftHybrid(index, args)); + } + @Override public Mono> ftAggregate(String index, V query, AggregateArgs args) { return createMono(() -> searchCommandBuilder.ftAggregate(index, query, args)); @@ -2389,6 +2406,11 @@ public Mono msetnx(Map map) { return createMono(() -> commandBuilder.msetnx(map)); } + @Override + public Mono msetex(Map map, MSetExArgs args) { + return createMono(() -> commandBuilder.msetex(map, args)); + } + @Override public Mono multi() { return createMono(commandBuilder::multi); diff --git a/src/main/java/io/lettuce/core/CompareCondition.java b/src/main/java/io/lettuce/core/CompareCondition.java new file mode 100644 index 000000000..95b7bac76 --- /dev/null +++ b/src/main/java/io/lettuce/core/CompareCondition.java @@ -0,0 +1,142 @@ +/* + * Copyright 2017-Present, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; + +/** + * A compare condition to be used with commands that support conditional value checks (e.g. SET with IFEQ/IFNE/IFDEQ/IFDNE and + * DELEX). This abstraction lets callers express value-based or digest-based comparisons. + * + *

+ * Digest-based comparisons use a 64-bit XXH3 digest represented as a 16-character lower-case hexadecimal string. + *

+ * + * @param value type used for value-based comparisons + * + * @author Aleksandar Todorov + * + * @since 7.1 + */ +@Experimental +public final class CompareCondition { + + /** + * The kind of condition represented by this instance. + */ + public enum Condition { + + /** current value must equal provided value */ + VALUE_EQUAL(CommandKeyword.IFEQ), + /** current value must not equal provided value */ + VALUE_NOT_EQUAL(CommandKeyword.IFNE), + /** current value's digest must equal provided digest */ + DIGEST_EQUAL(CommandKeyword.IFDEQ), + /** current value's digest must not equal provided digest */ + DIGEST_NOT_EQUAL(CommandKeyword.IFDNE); + + private final CommandKeyword keyword; + + Condition(CommandKeyword keyword) { + this.keyword = keyword; + } + + /** The protocol keyword to emit for this condition. */ + public CommandKeyword getKeyword() { + return keyword; + } + + } + + private final Condition condition; + + private final V value; // used for EQUAL/NOT_EQUAL + + private final String digest; // used for DIGEST_EQUAL/DIGEST_NOT_EQUAL + + private CompareCondition(Condition condition, V value, String digestHex) { + this.condition = condition; + this.value = value; + this.digest = digestHex; + } + + /** + * Append this condition's protocol arguments to the given args. + */ + public void build(CommandArgs args) { + args.add(condition.getKeyword()); + + if (condition.equals(Condition.DIGEST_EQUAL) || condition.equals(Condition.DIGEST_NOT_EQUAL)) { + args.add(digest); + } else if (condition.equals(Condition.VALUE_EQUAL) || condition.equals(Condition.VALUE_NOT_EQUAL)) { + args.addValue(value); + } + } + + // Factory methods for creating value- and digest-based conditions + /** Create a value-based equality condition; succeeds only if the current value equals the given value. */ + public static CompareCondition valueEq(V value) { + if (value == null) { + throw new IllegalArgumentException("value must not be null"); + } + return new CompareCondition<>(Condition.VALUE_EQUAL, value, null); + } + + /** Create a value-based inequality condition; succeeds only if the current value does not equal the given value. */ + public static CompareCondition valueNe(V value) { + if (value == null) { + throw new IllegalArgumentException("value must not be null"); + } + return new CompareCondition<>(Condition.VALUE_NOT_EQUAL, value, null); + } + + /** + * Create a digest-based equality condition; succeeds only if the current value's digest matches the given 16-character + * lower-case hex digest. + */ + public static CompareCondition digestEq(String hex16Digest) { + if (hex16Digest == null) { + throw new IllegalArgumentException("digest must not be null"); + } + return new CompareCondition<>(Condition.DIGEST_EQUAL, null, hex16Digest); + } + + /** + * Create a digest-based inequality condition; succeeds only if the current value's digest does not match the given + * 16-character lower-case hex digest. + */ + public static CompareCondition digestNe(String hex16Digest) { + if (hex16Digest == null) { + throw new IllegalArgumentException("digest must not be null"); + } + return new CompareCondition<>(Condition.DIGEST_NOT_EQUAL, null, hex16Digest); + } + + /** The kind of this condition. */ + public Condition getCondition() { + return condition; + } + + /** The value for value-based comparisons, or {@code null}. */ + public V getValue() { + return value; + } + + /** The 16-character lower-case hex digest for digest-based comparisons, or {@code null}. */ + public String getDigest() { + return digest; + } + + @Override + public String toString() { + return "ValueCondition{" + "kind=" + condition + (value != null ? ", value=" + value : "") + + (digest != null ? ", digestHex='" + digest + '\'' : "") + '}'; + } + +} diff --git a/src/main/java/io/lettuce/core/DriverInfo.java b/src/main/java/io/lettuce/core/DriverInfo.java new file mode 100644 index 000000000..87ef85a58 --- /dev/null +++ b/src/main/java/io/lettuce/core/DriverInfo.java @@ -0,0 +1,248 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ + +package io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.internal.LettuceSets; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Immutable class representing driver information for Redis client identification. + *

+ * This class is used to identify the client library and any upstream drivers (such as Spring Data Redis or Spring Session) when + * connecting to Redis. The information is sent via the {@code CLIENT SETINFO} command. + *

+ * The formatted name follows the pattern: {@code name(driver1_vVersion1;driver2_vVersion2)} + * + * @author Viktoriya Kutsarova + * @since 7.2 + * @see RedisURI#setDriverInfo(DriverInfo) + * @see RedisURI#getDriverInfo() + * @see CLIENT SETINFO + */ +public final class DriverInfo implements Serializable { + + /** + * Regex pattern for driver name validation. The name must start with a lowercase letter and contain only lowercase letters, + * digits, hyphens, and underscores. Mostly follows Maven artifactId naming conventions but also allows underscores. + * + * @see Maven Naming Conventions + */ + private static final String DRIVER_NAME_PATTERN = "^[a-z][a-z0-9_-]*$"; + + /** + * Set of brace characters that are not allowed in driver names or versions. These characters are used to delimit the driver + * information in the formatted output and would break parsing. + */ + private static final Set BRACES = LettuceSets.unmodifiableSet('(', ')', '[', ']', '{', '}'); + + private final String name; + + private final List upstreamDrivers; + + private DriverInfo(String name, List upstreamDrivers) { + this.name = name; + this.upstreamDrivers = Collections.unmodifiableList(upstreamDrivers); + } + + /** + * Creates a new {@link Builder} with default values. + *

+ * The default name is "Lettuce" (from {@link LettuceVersion#getName()}). + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new {@link Builder} initialized with values from an existing {@link DriverInfo}. + * + * @param driverInfo the existing driver info to copy from, must not be {@code null} + * @return a new builder instance initialized with the existing values + * @throws IllegalArgumentException if driverInfo is {@code null} + */ + public static Builder builder(DriverInfo driverInfo) { + LettuceAssert.notNull(driverInfo, "DriverInfo must not be null"); + return new Builder(driverInfo); + } + + /** + * Returns the formatted name including upstream drivers. + *

+ * If no upstream drivers are present, returns just the name. Otherwise, returns the name followed by upstream drivers in + * parentheses, separated by semicolons. + *

+ * Examples: + *

    + *
  • {@code "Lettuce"} - no upstream drivers
  • + *
  • {@code "Lettuce(spring-data-redis_v3.2.0)"} - one upstream driver
  • + *
  • {@code "Lettuce(spring-session_v3.3.0;spring-data-redis_v3.2.0)"} - multiple upstream drivers
  • + *
+ * + * @return the formatted name for use in CLIENT SETINFO + */ + public String getFormattedName() { + if (upstreamDrivers.isEmpty()) { + return name; + } + return String.format("%s(%s)", name, String.join(";", upstreamDrivers)); + } + + /** + * Returns the base library name without upstream driver information. + * + * @return the library name + */ + public String getName() { + return name; + } + + @Override + public String toString() { + return getFormattedName(); + } + + /** + * Builder for creating {@link DriverInfo} instances. + */ + public static class Builder { + + private String name; + + private final List upstreamDrivers; + + private Builder() { + this.name = LettuceVersion.getName(); + this.upstreamDrivers = new ArrayList<>(); + } + + private Builder(DriverInfo driverInfo) { + this.name = driverInfo.name; + this.upstreamDrivers = new ArrayList<>(driverInfo.upstreamDrivers); + } + + /** + * Sets the base library name. + *

+ * This overrides the default name ("Lettuce"). Use this when you want to completely customise the library + * identification. + * + * @param name the library name, must not be {@code null} + * @return this builder + * @throws IllegalArgumentException if name is {@code null} + */ + public Builder name(String name) { + LettuceAssert.notNull(name, "Name must not be null"); + this.name = name; + return this; + } + + /** + * Adds an upstream driver to the driver information. + *

+ * Upstream drivers are prepended to the list, so the most recently added driver appears first in the formatted output. + *

+ * The driver name must follow Maven artifactId naming conventions: lowercase letters, digits, hyphens, and underscores + * only, starting with a lowercase letter. + *

+ * Both values must not contain spaces, newlines, non-printable characters, or brace characters as these would violate + * the format of the Redis CLIENT LIST reply. + * + * @param driverName the name of the upstream driver (e.g., "spring-data-redis"), must not be {@code null} + * @param driverVersion the version of the upstream driver (e.g., "3.2.0"), must not be {@code null} + * @return this builder + * @throws IllegalArgumentException if the driver name or version is {@code null} or has invalid format + * @see Maven Naming Conventions + * @see CLIENT SETINFO + */ + public Builder addUpstreamDriver(String driverName, String driverVersion) { + LettuceAssert.notNull(driverName, "Driver name must not be null"); + LettuceAssert.notNull(driverVersion, "Driver version must not be null"); + validateDriverName(driverName); + validateDriverVersion(driverVersion); + String formattedDriverInfo = formatDriverInfo(driverName, driverVersion); + this.upstreamDrivers.add(0, formattedDriverInfo); + return this; + } + + /** + * Builds and returns a new immutable {@link DriverInfo} instance. + * + * @return a new DriverInfo instance + */ + public DriverInfo build() { + return new DriverInfo(name, upstreamDrivers); + } + + } + + /** + * Validates that the driver name follows Maven artifactId naming conventions: lowercase letters, digits, hyphens, and + * underscores only, starting with a lowercase letter (e.g., {@code spring-data-redis}, {@code lettuce-core}, + * {@code akka-redis_2.13}). + *

+ * Additionally validates Redis CLIENT LIST constraints: no spaces, newlines, non-printable characters, or braces. + * + * @param driverName the driver name to validate + * @throws IllegalArgumentException if the driver name does not follow the expected naming conventions + * @see Maven Naming Conventions + * @see CLIENT SETINFO + */ + private static void validateDriverName(String driverName) { + validateNoInvalidCharacters(driverName, "Driver name"); + if (!driverName.matches(DRIVER_NAME_PATTERN)) { + throw new IllegalArgumentException( + "Upstream driver name must follow Maven artifactId naming conventions: lowercase letters, digits, hyphens, and underscores only (e.g., 'spring-data-redis', 'lettuce-core')"); + } + } + + /** + * Validates that the driver version does not contain characters that would violate the format of the Redis CLIENT LIST + * reply: spaces, newlines, non-printable characters, or brace characters. + * + * @param driverVersion the driver version to validate + * @throws IllegalArgumentException if the driver version contains invalid characters + * @see CLIENT SETINFO + */ + private static void validateDriverVersion(String driverVersion) { + validateNoInvalidCharacters(driverVersion, "Driver version"); + } + + /** + * Validates that the value does not contain characters that would violate the format of the Redis CLIENT LIST reply: + * non-printable characters, spaces, or brace characters. + *

+ * Only printable ASCII characters (0x21-0x7E, i.e., '!' to '~') are allowed, excluding braces. + * + * @param value the value to validate + * @param fieldName the name of the field for error messages + * @throws IllegalArgumentException if the value contains invalid characters + * @see CLIENT SETINFO + */ + private static void validateNoInvalidCharacters(String value, String fieldName) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < '!' || c > '~' || BRACES.contains(c)) { + throw new IllegalArgumentException( + fieldName + " must not contain spaces, newlines, non-printable characters, or braces"); + } + } + } + + private static String formatDriverInfo(String driverName, String driverVersion) { + return driverName + "_v" + driverVersion; + } + +} diff --git a/src/main/java/io/lettuce/core/MSetExArgs.java b/src/main/java/io/lettuce/core/MSetExArgs.java new file mode 100644 index 000000000..436981551 --- /dev/null +++ b/src/main/java/io/lettuce/core/MSetExArgs.java @@ -0,0 +1,265 @@ +/* + * Copyright 2011-Present, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; + +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +import static io.lettuce.core.protocol.CommandKeyword.NX; +import static io.lettuce.core.protocol.CommandKeyword.XX; + +/** + * Argument list builder for the Redis MSETEX command starting from Redis 8.4 + * Static import the methods from {@link Builder} and chain the method calls: {@code ex(10).nx()}. + *

+ * {@link MSetExArgs} is a mutable object and instances should be used only once to avoid shared mutable state. + * + * @author Aleksandar Todorov + * @since 7.1 + */ +public class MSetExArgs implements CompositeArgument { + + private Long ex; + + private Long exAt; + + private Long px; + + private Long pxAt; + + private boolean nx = false; + + private boolean xx = false; + + private boolean keepttl = false; + + /** + * Builder entry points for {@link MSetExArgs}. + */ + public static class Builder { + + /** + * Utility constructor. + */ + private Builder() { + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal EX}. + * + * @param timeout expire time as duration. + * @return new {@link MSetExArgs} with {@literal EX} enabled. + * @see MSetExArgs#ex(long) + * @since 7.1 + */ + public static MSetExArgs ex(Duration timeout) { + return new MSetExArgs().ex(timeout); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link MSetExArgs} with {@literal EXAT} enabled. + * @see MSetExArgs#exAt(Instant) + * @since 7.1 + */ + public static MSetExArgs exAt(Instant timestamp) { + return new MSetExArgs().exAt(timestamp); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal PX}. + * + * @param timeout expire time in milliseconds. + * @return new {@link MSetExArgs} with {@literal PX} enabled. + * @see MSetExArgs#px(long) + * @since 7.1 + */ + public static MSetExArgs px(Duration timeout) { + return new MSetExArgs().px(timeout); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link MSetExArgs} with {@literal PXAT} enabled. + * @see MSetExArgs#pxAt(Instant) + * @since 7.1 + */ + public static MSetExArgs pxAt(Instant timestamp) { + return new MSetExArgs().pxAt(timestamp); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal NX}. + * + * @return new {@link MSetExArgs} with {@literal NX} enabled. + * @see MSetExArgs#nx() + * @since 7.1 + */ + public static MSetExArgs nx() { + return new MSetExArgs().nx(); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal XX}. + * + * @return new {@link MSetExArgs} with {@literal XX} enabled. + * @see MSetExArgs#xx() + */ + public static MSetExArgs xx() { + return new MSetExArgs().xx(); + } + + /** + * Creates new {@link MSetExArgs} and enable {@literal KEEPTTL}. + * + * @return new {@link MSetExArgs} with {@literal KEEPTTL} enabled. + * @see MSetExArgs#keepttl() + * @since 7.1 + */ + public static MSetExArgs keepttl() { + return new MSetExArgs().keepttl(); + } + + } + + /** + * Set the specified expire time, in seconds. + * + * @param timeout expire time in seconds. + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs ex(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.ex = timeout.toMillis() / 1000; + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs exAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + this.exAt = timestamp.toEpochMilli() / 1000; + return this; + } + + /** + * Set the specified expire time, in milliseconds. + * + * @param timeout expire time in milliseconds. + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs px(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.px = timeout.toMillis(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs pxAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + this.pxAt = timestamp.toEpochMilli(); + return this; + } + + /** + * Only set the key if it does not already exist. + * + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs nx() { + + this.nx = true; + return this; + } + + /** + * Set the value and retain the existing TTL. + * + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs keepttl() { + + this.keepttl = true; + return this; + } + + /** + * Only set the key if it already exists. + * + * @return {@code this} {@link MSetExArgs}. + * @since 7.1 + */ + public MSetExArgs xx() { + + this.xx = true; + return this; + } + + @Override + public void build(CommandArgs args) { + + if (ex != null) { + args.add("EX").add(ex); + } + + if (exAt != null) { + args.add("EXAT").add(exAt); + } + + if (px != null) { + args.add("PX").add(px); + } + + if (pxAt != null) { + args.add("PXAT").add(pxAt); + } + + if (nx) { + args.add(NX); + } + + if (xx) { + args.add(XX); + } + + if (keepttl) { + args.add("KEEPTTL"); + } + } + +} diff --git a/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java b/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java index effe88183..91b1a914a 100644 --- a/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java @@ -24,6 +24,8 @@ import io.lettuce.core.protocol.CommandKeyword; import io.lettuce.core.search.AggregateReplyParser; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; +import io.lettuce.core.search.HybridReplyParser; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SearchReplyParser; @@ -37,6 +39,7 @@ import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -108,6 +111,29 @@ public Command> ftSearch(String index, V query, SearchAr return createCommand(FT_SEARCH, new EncodedComplexOutput<>(codec, new SearchReplyParser<>(codec, searchArgs)), args); } + /** + * Execute a hybrid query that combines textual search and vector similarity on the given index. + * + * Build FT.HYBRID command for hybrid search combining text and vector similarity. + *

+ * FT.HYBRID supports flexible combinations of text search (SEARCH clause) and vector similarity (VSIM clause). At least one + * of SEARCH or VSIM must be configured in the HybridArgs. + *

+ * + * @param index the index name + * @param hybridArgs the hybrid query arguments containing SEARCH and/or VSIM clauses + * @return the command + */ + public Command> ftHybrid(String index, HybridArgs hybridArgs) { + LettuceAssert.notNull(index, "Index must not be null"); + LettuceAssert.notNull(hybridArgs, "HybridArgs must not be null"); + + CommandArgs args = new CommandArgs<>(codec).add(index); + hybridArgs.build(args); + + return createCommand(FT_HYBRID, new EncodedComplexOutput<>(codec, new HybridReplyParser<>(codec)), args); + } + /** * Run a search query on an index and perform aggregate transformations on the results. * diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java index 7a4d44ec3..fecc909fe 100644 --- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java @@ -940,6 +940,15 @@ Command del(Iterable keys) { return createCommand(DEL, new IntegerOutput<>(codec), args); } + Command delex(K key, CompareCondition condition) { + notNullKey(key); + LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + condition.build(args); + return createCommand(DELEX, new IntegerOutput<>(codec), args); + } + Command discard() { return createCommand(DISCARD, new StatusOutput<>(codec)); } @@ -2240,6 +2249,17 @@ Command msetnx(Map map) { return createCommand(MSETNX, new BooleanOutput<>(codec), args); } + Command msetex(Map map, MSetExArgs setArgs) { + LettuceAssert.notNull(map, "Map " + MUST_NOT_BE_NULL); + LettuceAssert.isTrue(!map.isEmpty(), "Map " + MUST_NOT_BE_EMPTY); + + CommandArgs args = new CommandArgs<>(codec).add(map.size()).add(map); + if (setArgs != null) { + setArgs.build(args); + } + return createCommand(MSETEX, new BooleanOutput<>(codec), args); + } + Command multi() { return createCommand(MULTI, new StatusOutput<>(codec)); } @@ -2702,6 +2722,13 @@ Command set(K key, V value, SetArgs setArgs) { return createCommand(SET, new StatusOutput<>(codec), args); } + Command digestKey(K key) { + notNullKey(key); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + return createCommand(DIGEST, new StatusOutput<>(codec), args); + } + Command setGet(K key, V value) { return setGet(key, value, new SetArgs()); } diff --git a/src/main/java/io/lettuce/core/RedisPublisher.java b/src/main/java/io/lettuce/core/RedisPublisher.java index 10f87be28..13797dc7b 100644 --- a/src/main/java/io/lettuce/core/RedisPublisher.java +++ b/src/main/java/io/lettuce/core/RedisPublisher.java @@ -59,6 +59,7 @@ * are emitted as they are decoded. Otherwise, results are processed at command completion. * * @author Mark Paluch + * @author Tihomir Mateev * @since 5.0 */ class RedisPublisher implements Publisher { @@ -293,7 +294,7 @@ public void onNext(T t) { try { DEMAND.decrementAndGet(this); this.subscriber.onNext(t); - } catch (Exception e) { + } catch (Throwable e) { onError(e); } return; @@ -544,7 +545,7 @@ void request(RedisSubscription subscription, long n) { try { subscription.checkCommandDispatch(); - } catch (Exception ex) { + } catch (Throwable ex) { subscription.onError(ex); } subscription.checkOnDataAvailable(); @@ -575,7 +576,7 @@ void onDataAvailable(RedisSubscription subscription) { return; } } while (subscription.hasDemand()); - } catch (Exception e) { + } catch (Throwable e) { subscription.onError(e); } } diff --git a/src/main/java/io/lettuce/core/RedisURI.java b/src/main/java/io/lettuce/core/RedisURI.java index 0ad65d99f..d5c6f09e3 100644 --- a/src/main/java/io/lettuce/core/RedisURI.java +++ b/src/main/java/io/lettuce/core/RedisURI.java @@ -237,11 +237,11 @@ public class RedisURI implements Serializable, ConnectionPoint { private String clientName; - private String libraryName = LettuceVersion.getName(); + private DriverInfo driverInfo = DriverInfo.builder().build(); private String libraryVersion = LettuceVersion.getVersion(); - private RedisCredentialsProvider credentialsProvider = new StaticCredentialsProvider(null, null);; + private RedisCredentialsProvider credentialsProvider = new StaticCredentialsProvider(null, null); private boolean ssl = false; @@ -606,26 +606,35 @@ public void setClientName(String clientName) { } /** - * Returns the library name. + * Returns the library name to be sent via {@code CLIENT SETINFO}. + *

+ * If upstream drivers have been added via {@link DriverInfo}, the returned value will include them in the format: + * {@code libraryName(driver1_v1.0.0;driver2_v2.0.0)}. Otherwise, returns just the library name. * - * @return the library name. + * @return the library name, potentially including upstream driver information. * @since 6.3 + * @see #getDriverInfo() + * @see DriverInfo#getFormattedName() */ public String getLibraryName() { - return libraryName; + return driverInfo.getFormattedName(); } /** * Sets the library name to be applied on Redis connections. + *

+ * This method creates a new {@link DriverInfo} with only the specified name, clearing any previously set upstream drivers. + * If you want to preserve upstream drivers, use {@link #setDriverInfo(DriverInfo)} instead. * - * @param libraryName the library name. + * @param libraryName the library name, must not contain spaces. * @since 6.3 + * @see #setDriverInfo(DriverInfo) */ public void setLibraryName(String libraryName) { if (libraryName != null && libraryName.indexOf(' ') != -1) { throw new IllegalArgumentException("Library name must not contain spaces"); } - this.libraryName = libraryName; + this.driverInfo = DriverInfo.builder().name(libraryName).build(); } /** @@ -638,6 +647,32 @@ public String getLibraryVersion() { return libraryVersion; } + /** + * Returns the driver information containing the library name and upstream drivers. + * + * @return the driver information, never {@code null} + * @since 6.5 + * @see DriverInfo + */ + public DriverInfo getDriverInfo() { + return driverInfo; + } + + /** + * Sets the driver information containing the library name and upstream drivers. + *

+ * This method replaces any previously set driver information or library name. Use this method when you want structured + * control over the library identification sent via {@code CLIENT SETINFO}. + * + * @param driverInfo the driver information to set + * @since 6.5 + * @see DriverInfo + * @see #setLibraryName(String) + */ + public void setDriverInfo(DriverInfo driverInfo) { + this.driverInfo = driverInfo; + } + /** * Sets the library version to be applied on Redis connections. * @@ -947,8 +982,8 @@ private String getQueryString() { queryPairs.add(PARAMETER_NAME_CLIENT_NAME + "=" + urlEncode(clientName)); } - if (libraryName != null && !libraryName.equals(LettuceVersion.getName())) { - queryPairs.add(PARAMETER_NAME_LIBRARY_NAME + "=" + urlEncode(libraryName)); + if (driverInfo.getName() != null && !driverInfo.getName().equals(LettuceVersion.getName())) { + queryPairs.add(PARAMETER_NAME_LIBRARY_NAME + "=" + urlEncode(driverInfo.getName())); } if (libraryVersion != null && !libraryVersion.equals(LettuceVersion.getVersion())) { @@ -1291,10 +1326,10 @@ public static class Builder { private String clientName; - private String libraryName = LettuceVersion.getName(); - private String libraryVersion = LettuceVersion.getVersion(); + private DriverInfo driverInfo = DriverInfo.builder().build(); + private RedisCredentialsProvider credentialsProvider; private boolean ssl = false; @@ -1625,11 +1660,38 @@ public Builder withLibraryName(String libraryName) { throw new IllegalArgumentException("Library name must not contain spaces"); } - this.libraryName = libraryName; + this.driverInfo = DriverInfo.builder().name(libraryName).build(); this.sentinels.forEach(it -> it.setLibraryName(libraryName)); return this; } + /** + * Configures driver information containing the library name and upstream drivers. + *

+ * This method allows upstream libraries (e.g., Spring Data Redis) that use Lettuce as their Redis driver to identify + * themselves. The driver information is sent via the {@code CLIENT SETINFO} command. + *

+ * If both {@link #withLibraryName(String)} and this method are called, the last call wins. When {@code driverInfo} is + * set, it takes precedence over {@code libraryName} in the built {@link RedisURI}. + *

+ * Also sets driver info for already configured Redis Sentinel nodes. + * + * @param driverInfo the driver information, must not be {@code null} + * @return the builder + * @throws IllegalArgumentException if driverInfo is {@code null} + * @see DriverInfo + * @see CLIENT SETINFO + * @since 6.5 + */ + public Builder withDriverInfo(DriverInfo driverInfo) { + + LettuceAssert.notNull(driverInfo, "DriverInfo must not be null"); + + this.driverInfo = driverInfo; + this.sentinels.forEach(it -> it.setDriverInfo(driverInfo)); + return this; + } + /** * Configures a library version. Sets library version also for already configured Redis Sentinel nodes. * @@ -1788,7 +1850,7 @@ public RedisURI build() { redisURI.setDatabase(database); redisURI.setClientName(clientName); - redisURI.setLibraryName(libraryName); + redisURI.setDriverInfo(driverInfo); redisURI.setLibraryVersion(libraryVersion); redisURI.setSentinelMasterId(sentinelMasterId); diff --git a/src/main/java/io/lettuce/core/SetArgs.java b/src/main/java/io/lettuce/core/SetArgs.java index 3f6102f08..12fff75d2 100644 --- a/src/main/java/io/lettuce/core/SetArgs.java +++ b/src/main/java/io/lettuce/core/SetArgs.java @@ -25,6 +25,7 @@ import java.time.Instant; import java.util.Date; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.internal.LettuceAssert; import io.lettuce.core.protocol.CommandArgs; @@ -54,6 +55,9 @@ public class SetArgs implements CompositeArgument { private boolean keepttl = false; + @Experimental + private CompareCondition compareCondition; + /** * Builder entry points for {@link SetArgs}. */ @@ -212,6 +216,19 @@ public static SetArgs keepttl() { return new SetArgs().keepttl(); } + /** + * Creates new {@link SetArgs} with compare condition {@link CompareCondition} + * + * @param condition the compare condition. + * @return new {@link SetArgs} with compare condition {@link CompareCondition}. + * @see SetArgs#compareCondition(CompareCondition) + * @since 7.1 + */ + @Experimental + public static SetArgs compareCondition(CompareCondition condition) { + return new SetArgs().compareCondition(condition); + } + } /** @@ -383,6 +400,20 @@ public SetArgs xx() { return this; } + /** + * Set a compare condition for the SET operation. + * + * @param compareCondition the value condition to apply. + * @return {@code this} {@link SetArgs}. + * @since 7.1 + */ + @Experimental + public SetArgs compareCondition(CompareCondition compareCondition) { + + this.compareCondition = compareCondition; + return this; + } + @Override public void build(CommandArgs args) { @@ -413,6 +444,10 @@ public void build(CommandArgs args) { if (keepttl) { args.add("KEEPTTL"); } + + if (compareCondition != null) { + ((CompareCondition) compareCondition).build(args); + } } } diff --git a/src/main/java/io/lettuce/core/StreamMessage.java b/src/main/java/io/lettuce/core/StreamMessage.java index a38dd07a3..4b3128120 100644 --- a/src/main/java/io/lettuce/core/StreamMessage.java +++ b/src/main/java/io/lettuce/core/StreamMessage.java @@ -17,6 +17,10 @@ public class StreamMessage { private final Map body; + private final Long millisElapsedFromDelivery; + + private final Long deliveredCount; + /** * Create a new {@link StreamMessage}. * @@ -29,6 +33,27 @@ public StreamMessage(K stream, String id, Map body) { this.stream = stream; this.id = id; this.body = body; + this.millisElapsedFromDelivery = null; + this.deliveredCount = null; + } + + /** + * Create a new {@link StreamMessage}. + * + * @param stream the stream. + * @param id the message id. + * @param millisElapsedFromDelivery the milliseconds since last delivery when CLAIM was used. + * @param deliveredCount the number of prior deliveries when CLAIM was used. + * @param body map containing the message body. + * @since 7.1 + */ + public StreamMessage(K stream, String id, Map body, long millisElapsedFromDelivery, long deliveredCount) { + + this.stream = stream; + this.id = id; + this.body = body; + this.millisElapsedFromDelivery = millisElapsedFromDelivery; + this.deliveredCount = deliveredCount; } public K getStream() { @@ -46,6 +71,36 @@ public Map getBody() { return body; } + /** + * @return the milliseconds since the last delivery of this message when CLAIM was used. + *

    + *
  • {@code null} when not applicable
  • + *
  • {@code 0} means not claimed from the pending entries list (PEL)
  • + *
  • {@code > 0} means claimed from the PEL
  • + *
+ * @since 7.1 + */ + public Long getMillisElapsedFromDelivery() { + return millisElapsedFromDelivery; + } + + /** + * @return the number of prior deliveries of this message when CLAIM was used: + *
    + *
  • {@code null} when not applicable
  • + *
  • {@code 0} means not claimed from the pending entries list (PEL)
  • + *
  • {@code > 0} means claimed from the PEL
  • + *
+ * @since 7.1 + */ + public Long getDeliveredCount() { + return deliveredCount; + } + + public boolean isClaimed() { + return deliveredCount != null && deliveredCount > 0; + } + @Override public boolean equals(Object o) { if (this == o) diff --git a/src/main/java/io/lettuce/core/XReadArgs.java b/src/main/java/io/lettuce/core/XReadArgs.java index b6fa3ead9..4ed1b0cfc 100644 --- a/src/main/java/io/lettuce/core/XReadArgs.java +++ b/src/main/java/io/lettuce/core/XReadArgs.java @@ -23,6 +23,8 @@ public class XReadArgs implements CompositeArgument { private boolean noack; + private Long claimMinIdleTime; + /** * Builder entry points for {@link XReadArgs}. */ @@ -90,6 +92,31 @@ public static XReadArgs noack(boolean noack) { return new XReadArgs().noack(noack); } + /** + * Create a new {@link XReadArgs} and set CLAIM min-idle-time. + * + * @implNote Only valid for XREADGROUP. + * @param milliseconds minimum idle time. + * @return new {@link XReadArgs} with CLAIM set + * @since 7.1 + */ + public static XReadArgs claim(long milliseconds) { + return new XReadArgs().claim(milliseconds); + } + + /** + * Create a new {@link XReadArgs} and set CLAIM min-idle-time. + * + * @implNote Only valid for XREADGROUP. + * @param timeout minimum idle time. + * @return new {@link XReadArgs} with CLAIM set + * @since 7.1 + */ + public static XReadArgs claim(Duration timeout) { + LettuceAssert.notNull(timeout, "Claim timeout must not be null"); + return claim(timeout.toMillis()); + } + } /** @@ -141,6 +168,35 @@ public XReadArgs noack(boolean noack) { return this; } + /** + * Claim idle pending messages first with a minimum idle time (milliseconds). + * + * @implNote Only valid for XREADGROUP. + * @param milliseconds minimum idle time. + * @return {@code this}. + * @since 7.1 + */ + public XReadArgs claim(long milliseconds) { + + this.claimMinIdleTime = milliseconds; + return this; + } + + /** + * Claim idle pending messages first with a minimum idle time (milliseconds). + * + * @implNote Only valid for XREADGROUP. + * @param timeout minimum idle time. + * @return {@code this}. + * @since 7.1 + */ + public XReadArgs claim(Duration timeout) { + + LettuceAssert.notNull(timeout, "Claim timeout must not be null"); + + return claim(timeout.toMillis()); + } + public void build(CommandArgs args) { if (block != null) { @@ -154,6 +210,10 @@ public void build(CommandArgs args) { if (noack) { args.add(CommandKeyword.NOACK); } + + if (claimMinIdleTime != null) { + args.add(CommandKeyword.CLAIM).add(claimMinIdleTime); + } } /** diff --git a/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java index a0322ce44..40e7a354d 100644 --- a/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java @@ -11,6 +11,7 @@ import io.lettuce.core.RedisFuture; import io.lettuce.core.annotations.Experimental; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -19,6 +20,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -1230,4 +1232,18 @@ public interface RediSearchAsyncCommands { @Experimental RedisFuture ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + RedisFuture> ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java index e5eab6dfb..33266c1b2 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java @@ -25,6 +25,7 @@ import java.util.List; import io.lettuce.core.*; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -68,6 +69,29 @@ public interface RedisKeyAsyncCommands { */ RedisFuture del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + RedisFuture delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + RedisFuture digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java index bcacfa731..b6b61f16c 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java @@ -25,11 +25,13 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.GetExArgs; import io.lettuce.core.KeyValue; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.RedisFuture; import io.lettuce.core.SetArgs; import io.lettuce.core.LcsArgs; import io.lettuce.core.StrAlgoArgs; import io.lettuce.core.StringMatchResult; + import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -364,6 +366,17 @@ public interface RedisStringAsyncCommands { */ RedisFuture msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + RedisFuture msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java index 0b715a3b0..24bf60ee8 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java @@ -11,6 +11,7 @@ import io.lettuce.core.annotations.Experimental; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -19,6 +20,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -1181,7 +1183,7 @@ public interface RediSearchReactiveCommands { * * @param index the index name * @param cursor the cursor obtained from a previous {@code FT.AGGREGATE} or {@code FT.CURSOR READ} command - * @return a {@link Mono} emitting the next batch of results; see {@link AggregationReply} + * @return the next batch of results; see {@link AggregationReply} * @since 6.8 * @see FT.CURSOR READ * @see { @Experimental Mono ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + Mono> ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java index 8fad8dde4..42af56029 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java @@ -23,6 +23,7 @@ import java.time.Instant; import java.util.Date; +import io.lettuce.core.CompareCondition; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import io.lettuce.core.CopyArgs; @@ -35,6 +36,7 @@ import io.lettuce.core.ScanCursor; import io.lettuce.core.SortArgs; import io.lettuce.core.StreamScanCursor; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -78,6 +80,29 @@ public interface RedisKeyReactiveCommands { */ Mono del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + Mono delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + Mono digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java index 60e2d5294..f85eb7ed5 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java @@ -21,6 +21,7 @@ import java.util.Map; +import io.lettuce.core.MSetExArgs; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import io.lettuce.core.BitFieldArgs; @@ -31,6 +32,7 @@ import io.lettuce.core.LcsArgs; import io.lettuce.core.StringMatchResult; import io.lettuce.core.Value; + import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -368,6 +370,17 @@ public interface RedisStringReactiveCommands { */ Mono msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Mono msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java b/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java index c716b6fe6..a1ee184d7 100644 --- a/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java @@ -11,6 +11,7 @@ import io.lettuce.core.annotations.Experimental; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -19,6 +20,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -1230,4 +1232,18 @@ public interface RediSearchCommands { @Experimental String ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + HybridReply ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java index 777dc966f..f8fd7b577 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java @@ -34,6 +34,8 @@ import io.lettuce.core.ScanCursor; import io.lettuce.core.SortArgs; import io.lettuce.core.StreamScanCursor; +import io.lettuce.core.CompareCondition; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -77,6 +79,29 @@ public interface RedisKeyCommands { */ Long del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + Long delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + String digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java index bae38567b..fe6a8e969 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java @@ -25,10 +25,12 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.GetExArgs; import io.lettuce.core.KeyValue; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.SetArgs; import io.lettuce.core.StrAlgoArgs; import io.lettuce.core.LcsArgs; import io.lettuce.core.StringMatchResult; + import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -363,6 +365,17 @@ public interface RedisStringCommands { */ Boolean msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Boolean msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java index 7136542fe..78f39f6a2 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java +++ b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java @@ -39,6 +39,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import io.lettuce.core.search.HybridReply; +import io.lettuce.core.search.arguments.HybridArgs; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -513,11 +515,20 @@ public RedisFuture mset(Map map) { @Override public RedisFuture msetnx(Map map) { + return executePartitionedBoolean(map, super::msetnx); + } + + @Override + public RedisFuture msetex(Map map, MSetExArgs args) { + return executePartitionedBoolean(map, op -> super.msetex(op, args)); + } + + private RedisFuture executePartitionedBoolean(Map map, Function, RedisFuture> operation) { Map> partitioned = SlotHash.partition(codec, map.keySet()); if (partitioned.size() < 2) { - return super.msetnx(map); + return operation.apply(map); } Map> executions = new HashMap<>(); @@ -527,14 +538,14 @@ public RedisFuture msetnx(Map map) { Map op = new HashMap<>(); entry.getValue().forEach(k -> op.put(k, map.get(k))); - RedisFuture msetnx = super.msetnx(op); - executions.put(entry.getKey(), msetnx); + RedisFuture future = operation.apply(op); + executions.put(entry.getKey(), future); } return new PipelinedRedisFuture<>(executions, objectPipelinedRedisFuture -> { - for (RedisFuture listRedisFuture : executions.values()) { - Boolean b = MultiNodeExecution.execute(() -> listRedisFuture.get()); + for (RedisFuture f : executions.values()) { + Boolean b = MultiNodeExecution.execute(f::get); if (b == null || !b) { return false; } @@ -790,6 +801,11 @@ public RedisFuture> ftSearch(String index, V query) { return ftSearch(index, query, SearchArgs. builder().build()); } + @Override + public RedisFuture> ftHybrid(String index, HybridArgs args) { + return routeKeyless(() -> super.ftHybrid(index, args), (conn) -> conn.ftHybrid(index, args), CommandType.FT_HYBRID); + } + @Override public RedisFuture ftExplain(String index, V query) { return routeKeyless(() -> super.ftExplain(index, query), (conn) -> conn.ftExplain(index, query), diff --git a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java index 16f19f495..1ba5db381 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java +++ b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java @@ -36,6 +36,8 @@ import java.util.stream.Collectors; import io.lettuce.core.api.reactive.RediSearchReactiveCommands; +import io.lettuce.core.search.HybridReply; +import io.lettuce.core.search.arguments.HybridArgs; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -410,6 +412,12 @@ public Mono mset(Map map) { booleanFlux -> booleanFlux).last(); } + @Override + public Mono msetex(Map map, MSetExArgs args) { + return pipeliningWithMap(map, kvMap -> RedisAdvancedClusterReactiveCommandsImpl.super.msetex(kvMap, args).flux(), + booleanFlux -> booleanFlux).reduce((accu, next) -> accu && next); + } + @Override public Mono randomkey() { @@ -609,6 +617,11 @@ public Mono> ftSearch(String index, V query) { return ftSearch(index, query, SearchArgs. builder().build()); } + @Override + public Mono> ftHybrid(String index, HybridArgs args) { + return routeKeyless(() -> super.ftHybrid(index, args), conn -> conn.ftHybrid(index, args), CommandType.FT_HYBRID); + } + @Override public Mono ftExplain(String index, V query) { return routeKeyless(() -> super.ftExplain(index, query), conn -> conn.ftExplain(index, query), CommandType.FT_EXPLAIN); diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java index 7233e5ae9..63ff07ee2 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java @@ -34,6 +34,8 @@ import io.lettuce.core.ScanCursor; import io.lettuce.core.SortArgs; import io.lettuce.core.StreamScanCursor; +import io.lettuce.core.CompareCondition; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -77,6 +79,29 @@ public interface NodeSelectionKeyAsyncCommands { */ AsyncExecutions del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + AsyncExecutions delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + AsyncExecutions digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSearchAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSearchAsyncCommands.java index c1cf0c032..c5e92369c 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSearchAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSearchAsyncCommands.java @@ -11,6 +11,7 @@ import io.lettuce.core.annotations.Experimental; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -18,6 +19,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -1230,4 +1232,18 @@ public interface NodeSelectionSearchAsyncCommands { @Experimental AsyncExecutions ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + AsyncExecutions> ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java index 44672f381..ece9239d0 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java @@ -25,6 +25,7 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.GetExArgs; import io.lettuce.core.KeyValue; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.SetArgs; import io.lettuce.core.StrAlgoArgs; import io.lettuce.core.StringMatchResult; @@ -363,6 +364,17 @@ public interface NodeSelectionStringAsyncCommands { */ AsyncExecutions msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + AsyncExecutions msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/main/java/io/lettuce/core/cluster/api/async/RedisAdvancedClusterAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/RedisAdvancedClusterAsyncCommands.java index fb122684b..f89ba6b70 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/RedisAdvancedClusterAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/RedisAdvancedClusterAsyncCommands.java @@ -25,9 +25,11 @@ import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.RedisFuture; import io.lettuce.core.ScanArgs; import io.lettuce.core.ScanCursor; +import io.lettuce.core.SetArgs; import io.lettuce.core.StreamScanCursor; import io.lettuce.core.api.async.RedisKeyAsyncCommands; import io.lettuce.core.api.async.RedisScriptingAsyncCommands; @@ -240,6 +242,17 @@ default AsyncNodeSelection all() { */ RedisFuture msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. Cross-slot keys will result in multiple calls to the particular cluster nodes. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + RedisFuture msetex(Map map, MSetExArgs args); + /** * Set the current connection name on all cluster nodes with pipelining. * diff --git a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java index 3e46f10ad..466f4063d 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java @@ -23,8 +23,10 @@ import java.util.List; import java.util.Map; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.Range; import io.lettuce.core.RedisFuture; +import io.lettuce.core.SetArgs; import io.lettuce.core.api.async.*; import io.lettuce.core.json.JsonParser; @@ -367,6 +369,17 @@ public interface RedisClusterAsyncCommands extends BaseRedisAsyncCommands< */ RedisFuture msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. Cross-slot keys will result in multiple calls to the particular cluster nodes. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + RedisFuture msetex(Map map, MSetExArgs args); + /** * Tells a Redis cluster replica node that the client is ok reading possibly stale data and is not interested in running * write queries. diff --git a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisAdvancedClusterReactiveCommands.java b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisAdvancedClusterReactiveCommands.java index aa82b166d..4f3d75252 100644 --- a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisAdvancedClusterReactiveCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisAdvancedClusterReactiveCommands.java @@ -21,6 +21,8 @@ import java.util.Map; +import io.lettuce.core.MSetExArgs; +import io.lettuce.core.SetArgs; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import io.lettuce.core.KeyScanCursor; @@ -134,6 +136,17 @@ public interface RedisAdvancedClusterReactiveCommands extends RedisCluster */ Mono msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. Cross-slot keys will result in multiple calls to the particular cluster nodes. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Mono msetex(Map map, MSetExArgs args); + /** * Set the current connection name on all cluster nodes with pipelining. * diff --git a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java index 028fa639f..0664366c5 100644 --- a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java @@ -23,7 +23,9 @@ import java.util.List; import java.util.Map; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.Range; +import io.lettuce.core.SetArgs; import io.lettuce.core.api.reactive.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -377,4 +379,15 @@ public interface RedisClusterReactiveCommands extends BaseRedisReactiveCom */ Mono>> clusterLinks(); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. Cross-slot keys will result in multiple calls to the particular cluster nodes. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Mono msetex(Map map, MSetExArgs args); + } diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java index 663a8aead..21dede698 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.List; +import io.lettuce.core.CompareCondition; import io.lettuce.core.CopyArgs; import io.lettuce.core.ExpireArgs; import io.lettuce.core.KeyScanArgs; @@ -34,6 +35,7 @@ import io.lettuce.core.ScanCursor; import io.lettuce.core.SortArgs; import io.lettuce.core.StreamScanCursor; +import io.lettuce.core.annotations.Experimental; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -77,6 +79,29 @@ public interface NodeSelectionKeyCommands { */ Executions del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + Executions delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + Executions digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSearchCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSearchCommands.java index 9b1b54f46..db724a301 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSearchCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSearchCommands.java @@ -11,6 +11,7 @@ import io.lettuce.core.annotations.Experimental; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; import io.lettuce.core.search.SpellCheckResult; import io.lettuce.core.search.Suggestion; @@ -18,6 +19,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -1230,4 +1232,18 @@ public interface NodeSelectionSearchCommands { @Experimental Executions ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + Executions> ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java index d52a332d5..d416dd612 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java @@ -25,6 +25,7 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.GetExArgs; import io.lettuce.core.KeyValue; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.SetArgs; import io.lettuce.core.StrAlgoArgs; import io.lettuce.core.StringMatchResult; @@ -363,6 +364,17 @@ public interface NodeSelectionStringCommands { */ Executions msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Executions msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java index de0bfb2c2..8807cdb9a 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java @@ -23,7 +23,9 @@ import java.util.List; import java.util.Map; +import io.lettuce.core.MSetExArgs; import io.lettuce.core.Range; +import io.lettuce.core.SetArgs; import io.lettuce.core.api.sync.*; import io.lettuce.core.json.JsonParser; @@ -385,4 +387,15 @@ public interface RedisClusterCommands extends BaseRedisCommands, Red */ JsonParser getJsonParser(); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. Cross-slot keys will result in multiple calls to the particular cluster nodes. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Boolean msetex(Map map, MSetExArgs args); + } diff --git a/src/main/java/io/lettuce/core/json/DelegateJsonArray.java b/src/main/java/io/lettuce/core/json/DelegateJsonArray.java index e660b90fd..d0fedf427 100644 --- a/src/main/java/io/lettuce/core/json/DelegateJsonArray.java +++ b/src/main/java/io/lettuce/core/json/DelegateJsonArray.java @@ -60,7 +60,7 @@ public List asList() { List result = new ArrayList<>(); for (JsonNode jsonNode : node) { - result.add(new DelegateJsonValue(jsonNode, objectMapper)); + result.add(wrap(jsonNode, objectMapper)); } return result; diff --git a/src/main/java/io/lettuce/core/output/EncodedComplexOutput.java b/src/main/java/io/lettuce/core/output/EncodedComplexOutput.java index f8f60a45f..360cf64ef 100644 --- a/src/main/java/io/lettuce/core/output/EncodedComplexOutput.java +++ b/src/main/java/io/lettuce/core/output/EncodedComplexOutput.java @@ -25,16 +25,12 @@ public EncodedComplexOutput(RedisCodec codec, ComplexDataParser parser) @Override public void set(ByteBuffer bytes) { - if (bytes != null) { - data.storeObject(bytes.asReadOnlyBuffer()); - } + data.storeObject(bytes == null ? null : bytes.asReadOnlyBuffer()); } @Override public void setSingle(ByteBuffer bytes) { - if (bytes != null) { - data.storeObject(bytes.asReadOnlyBuffer()); - } + data.storeObject(bytes == null ? null : bytes.asReadOnlyBuffer()); } } diff --git a/src/main/java/io/lettuce/core/output/StreamReadOutput.java b/src/main/java/io/lettuce/core/output/StreamReadOutput.java index 977b7bf86..919a81f2d 100644 --- a/src/main/java/io/lettuce/core/output/StreamReadOutput.java +++ b/src/main/java/io/lettuce/core/output/StreamReadOutput.java @@ -8,6 +8,7 @@ import io.lettuce.core.StreamMessage; import io.lettuce.core.codec.RedisCodec; + import io.lettuce.core.internal.LettuceAssert; /** @@ -31,6 +32,10 @@ public class StreamReadOutput extends CommandOutput body; + private Long msSinceLastDelivery; + + private Long redeliveryCount; + private boolean bodyReceived = false; public StreamReadOutput(RedisCodec codec) { @@ -75,6 +80,23 @@ public void set(ByteBuffer bytes) { key = null; } + @Override + public void set(long integer) { + + // Extra integers appear only for claimed entries (XREADGROUP with CLAIM) + if (id != null && bodyReceived) { + if (msSinceLastDelivery == null) { + msSinceLastDelivery = integer; + return; + } + if (redeliveryCount == null) { + redeliveryCount = integer; + return; + } + } + super.set(integer); + } + @Override public void multi(int count) { @@ -91,12 +113,20 @@ public void multi(int count) { @Override public void complete(int depth) { - if (depth == 3 && bodyReceived) { - subscriber.onNext(output, new StreamMessage<>(stream, id, body == null ? Collections.emptyMap() : body)); + // Emit the message when the entry array (id/body[/extras]) completes. + if (depth == 2 && bodyReceived) { + Map map = body == null ? Collections.emptyMap() : body; + if (msSinceLastDelivery != null && redeliveryCount != null) { + subscriber.onNext(output, new StreamMessage<>(stream, id, map, msSinceLastDelivery, redeliveryCount)); + } else { + subscriber.onNext(output, new StreamMessage<>(stream, id, map)); + } bodyReceived = false; key = null; body = null; id = null; + msSinceLastDelivery = null; + redeliveryCount = null; } // RESP2/RESP3 compat diff --git a/src/main/java/io/lettuce/core/protocol/CommandEncoder.java b/src/main/java/io/lettuce/core/protocol/CommandEncoder.java index 1bcb58b9e..c78778c62 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandEncoder.java +++ b/src/main/java/io/lettuce/core/protocol/CommandEncoder.java @@ -76,10 +76,12 @@ private void encode(ChannelHandlerContext ctx, ByteBuf out, RedisCommand output, - Consumer errorHandler); + Consumer errorHandler); } @@ -206,7 +207,7 @@ enum Type implements StateHandler { @Override public Result handle(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { return behavior.handle(rsm, state, buffer, output, errorHandler); } @@ -309,7 +310,7 @@ public boolean decode(ByteBuf buffer, CommandOutput output) { * @param errorHandler the error handler * @return true if a complete response was read. */ - public boolean decode(ByteBuf buffer, CommandOutput output, Consumer errorHandler) { + public boolean decode(ByteBuf buffer, CommandOutput output, Consumer errorHandler) { buffer.touch("RedisStateMachine.decode(…)"); @@ -338,7 +339,7 @@ public boolean decode(ByteBuf buffer, CommandOutput output, Consumer output, Consumer errorHandler) { + private boolean doDecode(ByteBuf buffer, CommandOutput output, Consumer errorHandler) { boolean resp3Indicator = false; @@ -376,7 +377,7 @@ private boolean doDecode(ByteBuf buffer, CommandOutput output, Consumer } static State.Result handleSingle(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { ByteBuffer bytes; if ((bytes = rsm.readLine(buffer)) == null) { @@ -390,7 +391,7 @@ static State.Result handleSingle(RedisStateMachine rsm, State state, ByteBuf buf } static State.Result handleBigNumber(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { ByteBuffer bytes; if ((bytes = rsm.readLine(buffer)) == null) { @@ -402,7 +403,7 @@ static State.Result handleBigNumber(RedisStateMachine rsm, State state, ByteBuf } static State.Result handleError(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { ByteBuffer bytes; if ((bytes = rsm.readLine(buffer)) == null) { @@ -414,7 +415,7 @@ static State.Result handleError(RedisStateMachine rsm, State state, ByteBuf buff } static State.Result handleNull(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { if (rsm.readLine(buffer) == null) { return State.Result.BREAK_LOOP; } @@ -423,7 +424,7 @@ static State.Result handleNull(RedisStateMachine rsm, State state, ByteBuf buffe } static State.Result handleInteger(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int end; if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) { @@ -435,7 +436,7 @@ static State.Result handleInteger(RedisStateMachine rsm, State state, ByteBuf bu } static State.Result handleBoolean(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { if (rsm.findLineEnd(buffer) == NOT_FOUND) { return State.Result.BREAK_LOOP; } @@ -445,7 +446,7 @@ static State.Result handleBoolean(RedisStateMachine rsm, State state, ByteBuf bu } static State.Result handleFloat(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int end; if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) { @@ -457,7 +458,7 @@ static State.Result handleFloat(RedisStateMachine rsm, State state, ByteBuf buff } static State.Result handleBulkAndVerbatim(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int length; int end; @@ -477,7 +478,7 @@ static State.Result handleBulkAndVerbatim(RedisStateMachine rsm, State state, By } static State.Result handleBulkError(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int length; int end; @@ -497,7 +498,7 @@ static State.Result handleBulkError(RedisStateMachine rsm, State state, ByteBuf } static State.Result handleHelloV3(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int end; if (state.count == NOT_FOUND) { @@ -511,7 +512,7 @@ static State.Result handleHelloV3(RedisStateMachine rsm, State state, ByteBuf bu } static State.Result handlePushAndMulti(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int end; if (state.count == NOT_FOUND) { @@ -527,7 +528,7 @@ static State.Result handlePushAndMulti(RedisStateMachine rsm, State state, ByteB } static State.Result handleMap(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int length; int end; @@ -545,7 +546,7 @@ static State.Result handleMap(RedisStateMachine rsm, State state, ByteBuf buffer } static State.Result handleSet(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { int end; if (state.count == NOT_FOUND) { @@ -579,7 +580,7 @@ static State.Result returnDependStateCount(RedisStateMachine rsm, State state) { } static State.Result handleVerbatim(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { ByteBuffer bytes; if ((bytes = rsm.readBytes(buffer, state.count)) == null) { @@ -592,7 +593,7 @@ static State.Result handleVerbatim(RedisStateMachine rsm, State state, ByteBuf b } static State.Result handleBytes(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput output, - Consumer errorHandler) { + Consumer errorHandler) { ByteBuffer bytes; if ((bytes = rsm.readBytes(buffer, state.count)) == null) { @@ -603,7 +604,7 @@ static State.Result handleBytes(RedisStateMachine rsm, State state, ByteBuf buff } private static State.Result handleAttribute(RedisStateMachine rsm, State state, ByteBuf buffer, - CommandOutput output, Consumer errorHandler) { + CommandOutput output, Consumer errorHandler) { throw new RedisProtocolException("Not implemented"); } @@ -760,11 +761,11 @@ private boolean isEmpty(State[] stack) { * @param value * @param errorHandler */ - protected void safeSet(CommandOutput output, boolean value, Consumer errorHandler) { + protected void safeSet(CommandOutput output, boolean value, Consumer errorHandler) { try { output.set(value); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -776,11 +777,11 @@ protected void safeSet(CommandOutput output, boolean value, Consumer output, long number, Consumer errorHandler) { + protected void safeSet(CommandOutput output, long number, Consumer errorHandler) { try { output.set(number); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -792,11 +793,11 @@ protected void safeSet(CommandOutput output, long number, Consumer output, double number, Consumer errorHandler) { + protected void safeSet(CommandOutput output, double number, Consumer errorHandler) { try { output.set(number); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -808,11 +809,11 @@ protected void safeSet(CommandOutput output, double number, Consumer output, ByteBuffer bytes, Consumer errorHandler) { + protected void safeSet(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { try { output.set(bytes); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -824,11 +825,11 @@ protected void safeSet(CommandOutput output, ByteBuffer bytes, Consumer * @param bytes * @param errorHandler */ - protected void safeSetSingle(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { + protected void safeSetSingle(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { try { output.set(bytes); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -840,11 +841,11 @@ protected void safeSetSingle(CommandOutput output, ByteBuffer bytes, Co * @param bytes * @param errorHandler */ - protected void safeSetBigNumber(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { + protected void safeSetBigNumber(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { try { output.setBigNumber(bytes); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -856,11 +857,11 @@ protected void safeSetBigNumber(CommandOutput output, ByteBuffer bytes, * @param count * @param errorHandler */ - protected void safeMultiArray(CommandOutput output, int count, Consumer errorHandler) { + protected void safeMultiArray(CommandOutput output, int count, Consumer errorHandler) { try { output.multiArray(count); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -872,11 +873,11 @@ protected void safeMultiArray(CommandOutput output, int count, Consumer * @param count * @param errorHandler */ - protected void safeMultiPush(CommandOutput output, int count, Consumer errorHandler) { + protected void safeMultiPush(CommandOutput output, int count, Consumer errorHandler) { try { output.multiPush(count); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -888,11 +889,11 @@ protected void safeMultiPush(CommandOutput output, int count, Consumer< * @param count * @param errorHandler */ - protected void safeMultiSet(CommandOutput output, int count, Consumer errorHandler) { + protected void safeMultiSet(CommandOutput output, int count, Consumer errorHandler) { try { output.multiSet(count); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -904,11 +905,11 @@ protected void safeMultiSet(CommandOutput output, int count, Consumer output, int count, Consumer errorHandler) { + protected void safeMultiMap(CommandOutput output, int count, Consumer errorHandler) { try { output.multiMap(count); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } @@ -920,11 +921,11 @@ protected void safeMultiMap(CommandOutput output, int count, Consumer output, ByteBuffer bytes, Consumer errorHandler) { + protected void safeSetError(CommandOutput output, ByteBuffer bytes, Consumer errorHandler) { try { output.setError(bytes); - } catch (Exception e) { + } catch (Throwable e) { errorHandler.accept(e); } } diff --git a/src/main/java/io/lettuce/core/search/HybridReply.java b/src/main/java/io/lettuce/core/search/HybridReply.java new file mode 100644 index 000000000..0eef0f3e1 --- /dev/null +++ b/src/main/java/io/lettuce/core/search/HybridReply.java @@ -0,0 +1,158 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.search; + +import io.lettuce.core.annotations.Experimental; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents the results of an {@code FT.HYBRID} command. Contains total result count, execution time, warnings, and a list of + * per-document results with document key and field values. + * + * @param Key type. + * @param Value type. + * @author Aleksandar Todorov + * @since 7.2 + */ +@Experimental +public class HybridReply { + + private long totalResults; + + private double executionTime; + + private final List> results; + + private final List warnings = new ArrayList<>(); + + /** + * Creates a new empty HybridReply instance. + */ + public HybridReply() { + this.totalResults = 0; + this.executionTime = 0; + this.results = new ArrayList<>(); + } + + /** + * @return the total number of matching documents reported by the server + */ + public long getTotalResults() { + return totalResults; + } + + /** + * Set the total number of matching documents. + * + * @param totalResults the total number of results + */ + public void setTotalResults(long totalResults) { + this.totalResults = totalResults; + } + + /** + * @return the execution time reported by the server in seconds (or {@code 0.0} if not available) + */ + public double getExecutionTime() { + return executionTime; + } + + /** + * Set the execution time reported by the server. + * + * @param executionTime execution time in seconds + */ + public void setExecutionTime(double executionTime) { + this.executionTime = executionTime; + } + + /** + * @return an unmodifiable view of all results returned by the command + */ + public List> getResults() { + return Collections.unmodifiableList(results); + } + + /** + * Add a new result entry. + * + * @param result the result to add + */ + public void addResult(Result result) { + this.results.add(result); + } + + /** + * @return a read-only view of all warnings reported by the server + */ + public List getWarnings() { + return Collections.unmodifiableList(warnings); + } + + /** + * Add a warning message. + * + * @param warning the warning to add + */ + public void addWarning(V warning) { + this.warnings.add(warning); + } + + /** + * @return the number of result entries + */ + public int size() { + return results.size(); + } + + /** + * @return {@code true} if no results were returned + */ + public boolean isEmpty() { + return results.isEmpty(); + } + + /** + * Represents a single result entry in an {@code FT.HYBRID} response. + *

+ * Each result contains field values returned by the query. The document key is available in the fields map under the + * reserved field name {@code __key} when returning individual documents. Score information (text score, vector distance, + * combined score) is included in the fields map when using {@code YIELD_SCORE_AS} in the query. + *

+ */ + public static class Result { + + private final Map fields = new HashMap<>(); + + public Result() { + } + + /** + * @return a mutable map of all fields associated with this result + */ + public Map getFields() { + return fields; + } + + /** + * Add a single field to this result. + * + * @param key field name + * @param value field value + */ + public void addField(K key, V value) { + fields.put(key, value); + } + + } + +} diff --git a/src/main/java/io/lettuce/core/search/HybridReplyParser.java b/src/main/java/io/lettuce/core/search/HybridReplyParser.java new file mode 100644 index 000000000..0165bc16e --- /dev/null +++ b/src/main/java/io/lettuce/core/search/HybridReplyParser.java @@ -0,0 +1,254 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.search; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.codec.RedisCodec; +import io.lettuce.core.codec.StringCodec; +import io.lettuce.core.output.ComplexData; +import io.lettuce.core.output.ComplexDataParser; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +/** + * Parser for {@code FT.HYBRID} responses. Handles both RESP2 and RESP3 protocol formats. + * + * @param Key type. + * @param Value type. + * @author Aleksandar Todorov + * @since 7.2 + */ +@Experimental +public class HybridReplyParser implements ComplexDataParser> { + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HybridReplyParser.class); + + private final RedisCodec codec; + + private final ByteBuffer TOTAL_RESULTS_KEY = StringCodec.UTF8.encodeKey("total_results"); + + private final ByteBuffer EXECUTION_TIME_KEY = StringCodec.UTF8.encodeKey("execution_time"); + + private final ByteBuffer WARNINGS_KEY = StringCodec.UTF8.encodeKey("warnings"); + + private final ByteBuffer RESULTS_KEY = StringCodec.UTF8.encodeKey("results"); + + public HybridReplyParser(RedisCodec codec) { + this.codec = codec; + } + + @Override + public HybridReply parse(ComplexData data) { + try { + HybridReply hybridReply = new HybridReply<>(); + + if (data.isList()) { + parseResp2(data, hybridReply); + } else { + parseResp3(data, hybridReply); + } + + return hybridReply; + } catch (Exception e) { + LOG.warn("Unable to parse FT.HYBRID result from Redis", e); + return new HybridReply<>(); + } + } + + private void parseResp2(ComplexData data, HybridReply reply) { + List list = data.getDynamicList(); + if (list == null || list.isEmpty()) { + return; + } + parseResp2(list, reply); + } + + private void parseResp2(List list, HybridReply reply) { + // RESP2 format: ["key1", value1, "key2", value2, ...] + // Parse as key-value pairs + for (int i = 0; i + 1 < list.size(); i += 2) { + Object keyObj = list.get(i); + Object valueObj = list.get(i + 1); + + if (!(keyObj instanceof ByteBuffer)) { + continue; + } + + ByteBuffer keyBuffer = (ByteBuffer) keyObj; + + if (keyBuffer.equals(TOTAL_RESULTS_KEY)) { + if (valueObj instanceof Long) { + reply.setTotalResults((Long) valueObj); + } + } else if (keyBuffer.equals(EXECUTION_TIME_KEY)) { + if (valueObj instanceof ByteBuffer) { + try { + String asString = StringCodec.UTF8.decodeKey((ByteBuffer) valueObj); + reply.setExecutionTime(Double.parseDouble(asString)); + } catch (NumberFormatException ignore) { + // leave default + } + } else if (valueObj instanceof Double) { + reply.setExecutionTime((Double) valueObj); + } + } else if (keyBuffer.equals(WARNINGS_KEY)) { + if (valueObj instanceof ComplexData) { + ComplexData warningData = (ComplexData) valueObj; + List warnList = warningData.getDynamicList(); + if (warnList != null) { + for (Object o : warnList) { + if (o instanceof ByteBuffer) { + reply.addWarning(codec.decodeValue((ByteBuffer) o)); + } + } + } + } + } else if (keyBuffer.equals(RESULTS_KEY)) { + if (valueObj instanceof ComplexData) { + ComplexData resultsData = (ComplexData) valueObj; + List resultsList = resultsData.getDynamicList(); + if (resultsList != null) { + for (Object resultObj : resultsList) { + if (resultObj instanceof ComplexData) { + HybridReply.Result result = new HybridReply.Result<>(); + addFieldsFromComplexData((ComplexData) resultObj, result); + reply.addResult(result); + } + } + } + } + } + } + } + + private void parseResp3(ComplexData data, HybridReply reply) { + Map resultsMap = data.getDynamicMap(); + if (resultsMap == null || resultsMap.isEmpty()) { + return; + } + + Object total = resultsMap.get(TOTAL_RESULTS_KEY); + if (total instanceof Long) { + reply.setTotalResults((Long) total); + } + + Object executionTime = resultsMap.get(EXECUTION_TIME_KEY); + if (executionTime instanceof Double) { + reply.setExecutionTime((Double) executionTime); + } else if (executionTime instanceof ByteBuffer) { + try { + String asString = StringCodec.UTF8.decodeKey((ByteBuffer) executionTime); + reply.setExecutionTime(Double.parseDouble(asString)); + } catch (NumberFormatException ignore) { + // leave default + } + } + + Object warnings = resultsMap.get(WARNINGS_KEY); + if (warnings instanceof ComplexData) { + ComplexData warningData = (ComplexData) warnings; + List warnList = warningData.getDynamicList(); + if (warnList != null) { + for (Object o : warnList) { + if (o instanceof ByteBuffer) { + reply.addWarning(codec.decodeValue((ByteBuffer) o)); + } + } + } + } + + Object resultsObj = resultsMap.get(RESULTS_KEY); + if (!(resultsObj instanceof ComplexData)) { + return; + } + + ComplexData results = (ComplexData) resultsObj; + List rawResults = results.getDynamicList(); + if (rawResults == null || rawResults.isEmpty()) { + return; + } + + for (Object raw : rawResults) { + if (!(raw instanceof ComplexData)) { + continue; + } + + ComplexData resultData = (ComplexData) raw; + HybridReply.Result result = parseResultEntry(resultData); + reply.addResult(result); + } + } + + private HybridReply.Result parseResultEntry(ComplexData resultData) { + Map entryMap; + try { + entryMap = resultData.getDynamicMap(); + } catch (UnsupportedOperationException e) { + entryMap = null; + } + + HybridReply.Result result = new HybridReply.Result<>(); + + if (entryMap != null && !entryMap.isEmpty()) { + entryMap.forEach((key, value) -> { + if (!(key instanceof ByteBuffer) || !(value instanceof ByteBuffer)) { + return; + } + + K fieldKey = codec.decodeKey((ByteBuffer) key); + V fieldValue = codec.decodeValue((ByteBuffer) value); + result.addField(fieldKey, fieldValue); + }); + } else { + addFieldsFromComplexData(resultData, result); + } + + return result; + } + + private void addFieldsFromComplexData(ComplexData data, HybridReply.Result result) { + Map map; + try { + map = data.getDynamicMap(); + } catch (UnsupportedOperationException e) { + map = null; + } + + if (map != null && !map.isEmpty()) { + map.forEach((k, v) -> { + if (!(k instanceof ByteBuffer) || !(v instanceof ByteBuffer)) { + return; + } + K decodedKey = codec.decodeKey((ByteBuffer) k); + V decodedValue = codec.decodeValue((ByteBuffer) v); + result.addField(decodedKey, decodedValue); + }); + return; + } + + List list = data.getDynamicList(); + if (list == null || list.isEmpty()) { + return; + } + + for (int i = 0; i + 1 < list.size(); i += 2) { + Object k = list.get(i); + Object v = list.get(i + 1); + if (!(k instanceof ByteBuffer) || !(v instanceof ByteBuffer)) { + continue; + } + K decodedKey = codec.decodeKey((ByteBuffer) k); + V decodedValue = codec.decodeValue((ByteBuffer) v); + result.addField(decodedKey, decodedValue); + } + } + +} diff --git a/src/main/java/io/lettuce/core/search/SearchReplyParser.java b/src/main/java/io/lettuce/core/search/SearchReplyParser.java index 976b60714..f67a3f63e 100644 --- a/src/main/java/io/lettuce/core/search/SearchReplyParser.java +++ b/src/main/java/io/lettuce/core/search/SearchReplyParser.java @@ -186,11 +186,13 @@ private void parseResults(SearchReply searchReply, List resultsLis ComplexData resultData = (ComplexData) resultsList.get(i); List resultEntries = resultData.getDynamicList(); - Map resultEntriesProcessed = IntStream.range(0, resultEntries.size() / 2).boxed() - .collect(Collectors.toMap(idx -> codec.decodeKey((ByteBuffer) resultEntries.get(idx * 2)), - idx -> codec.decodeValue((ByteBuffer) resultEntries.get(idx * 2 + 1)))); + for (int idx = 0; idx < resultEntries.size(); idx += 2) { + K decodedKey = codec.decodeKey((ByteBuffer) resultEntries.get(idx)); + Object value = resultEntries.get(idx + 1); + V decodedValue = value == null ? null : codec.decodeValue((ByteBuffer) value); + searchResult.addFields(decodedKey, decodedValue); + } - searchResult.addFields(resultEntriesProcessed); i++; } @@ -265,7 +267,7 @@ public SearchReply parse(ComplexData data) { ComplexData extraAttributes = (ComplexData) resultEntry.get(EXTRA_ATTRIBUTES_KEY); extraAttributes.getDynamicMap().forEach((key, value) -> { K decodedKey = codec.decodeKey((ByteBuffer) key); - V decodedValue = codec.decodeValue((ByteBuffer) value); + V decodedValue = value == null ? null : codec.decodeValue((ByteBuffer) value); searchResult.addFields(decodedKey, decodedValue); }); } diff --git a/src/main/java/io/lettuce/core/search/arguments/CombineArgs.java b/src/main/java/io/lettuce/core/search/arguments/CombineArgs.java new file mode 100644 index 000000000..338d7dbce --- /dev/null +++ b/src/main/java/io/lettuce/core/search/arguments/CombineArgs.java @@ -0,0 +1,220 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.search.arguments; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; + +/** + * Arguments for the COMBINE clause in FT.HYBRID command. Defines how text search scores and vector similarity scores are + * combined into a final ranking score using RRF (Reciprocal Rank Fusion) or LINEAR strategies. + * + * @param Key type + * @author Aleksandar Todorov + * @since 7.2 + * @see FT.HYBRID + * @see RRF + * @see Linear + */ +@Experimental +public class CombineArgs { + + private final CombineMethod method; + + private K scoreAlias; + + private CombineArgs(CombineMethod method) { + this.method = method; + } + + /** + * Create CombineArgs with the specified combine method. + * + * @param method the combine method (RRF or Linear) + * @param Key type + * @return CombineArgs instance + */ + public static CombineArgs of(CombineMethod method) { + return new CombineArgs<>(method); + } + + /** + * Set an alias for the combined score field using YIELD_SCORE_AS. + * + * @param alias the field name to use for the combined score + * @return this instance + */ + public CombineArgs as(K alias) { + LettuceAssert.notNull(alias, "Alias must not be null"); + this.scoreAlias = alias; + return this; + } + + /** + * Interface for combine methods (RRF or LINEAR). + * + * @param Key type + */ + public interface CombineMethod { + + /** + * Build the combine method arguments. + * + * @param args the {@link CommandArgs} to append to + * @param value type + */ + void build(CommandArgs args); + + } + + /** + * Reciprocal Rank Fusion (RRF) combine method. Combines rankings from text and vector search using reciprocal rank fusion. + *

+ * RRF formula: score = 1 / (CONSTANT + rank_in_window) + *

+ * + * @param Key type + */ + public static class RRF implements CombineMethod { + + private Integer window; + + private Double constant; + + /** + * Set the WINDOW parameter - number of top results to consider from each ranking. + * + * @param window number of top results + * @return this instance + */ + public RRF window(int window) { + LettuceAssert.isTrue(window > 0, "Window must be positive"); + this.window = window; + return this; + } + + /** + * Set the CONSTANT parameter - constant added to rank to prevent division by zero. + * + * @param constant constant value + * @return this instance + */ + public RRF constant(double constant) { + LettuceAssert.isTrue(constant > 0, "Constant must be positive"); + this.constant = constant; + return this; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.RRF); + // Count of total items (not pairs): WINDOW, value, CONSTANT, value = 4 items + int itemCount = 0; + if (window != null) { + itemCount += 2; // WINDOW + value + } + if (constant != null) { + itemCount += 2; // CONSTANT + value + } + + // Always add count, even if 0 + args.add(itemCount); + if (window != null) { + args.add(CommandKeyword.WINDOW); + args.add(window); + } + if (constant != null) { + args.add(CommandKeyword.CONSTANT); + args.add(constant); + } + } + + } + + /** + * Linear combine method. Combines text and vector scores using weighted average. + *

+ * Formula: combined_score = ALPHA * text_score + BETA * vector_score + *

+ * + * @param Key type + */ + public static class Linear implements CombineMethod { + + private Double alpha; + + private Double beta; + + /** + * Set the ALPHA parameter - weight for text search score. + * + * @param alpha weight for text score + * @return this instance + */ + public Linear alpha(double alpha) { + LettuceAssert.isTrue(alpha >= 0, "Alpha must be non-negative"); + this.alpha = alpha; + return this; + } + + /** + * Set the BETA parameter - weight for vector search score. + * + * @param beta weight for vector score + * @return this instance + */ + public Linear beta(double beta) { + LettuceAssert.isTrue(beta >= 0, "Beta must be non-negative"); + this.beta = beta; + return this; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.LINEAR); + // Count of total items (not pairs): ALPHA, value, BETA, value = 4 items + int itemCount = 0; + if (alpha != null) { + itemCount += 2; // ALPHA + value + } + if (beta != null) { + itemCount += 2; // BETA + value + } + + // Always add count, even if 0 + args.add(itemCount); + if (alpha != null) { + args.add(CommandKeyword.ALPHA); + args.add(alpha); + } + if (beta != null) { + args.add(CommandKeyword.BETA); + args.add(beta); + } + } + + } + + /** + * Build the COMBINE clause arguments. + * + * @param args the {@link CommandArgs} to append to + * @param value type + */ + public void build(CommandArgs args) { + method.build(args); + + // YIELD_SCORE_AS for COMBINE + if (scoreAlias != null) { + args.add(CommandKeyword.YIELD_SCORE_AS); + args.addKey(scoreAlias); + } + } + +} diff --git a/src/main/java/io/lettuce/core/search/arguments/HybridArgs.java b/src/main/java/io/lettuce/core/search/arguments/HybridArgs.java new file mode 100644 index 000000000..1a21fc442 --- /dev/null +++ b/src/main/java/io/lettuce/core/search/arguments/HybridArgs.java @@ -0,0 +1,213 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ + +package io.lettuce.core.search.arguments; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Argument list builder for the Redis {@code FT.HYBRID} command. Combines text search and vector similarity search with + * configurable combination strategies and post-processing operations. + * + *

Basic Usage:

+ * + *
+ *
+ * {
+ *     @code
+ *     HybridArgs args = HybridArgs. builder()
+ *             .search(HybridSearchArgs. builder().query("comfortable shoes").build())
+ *             .vectorSearch(HybridVectorArgs. builder().field("@embedding").vector(vectorBlob)
+ *                     .method(HybridVectorArgs.Knn.of(10)).build())
+ *             .combine(CombineArgs.of(new CombineArgs.RRF<>())).build();
+ * }
+ * 
+ * + * @param Key type. + * @param Value type. + * @author Aleksandar Todorov + * @since 7.2 + * @see FT.HYBRID + * @see HybridSearchArgs + * @see HybridVectorArgs + * @see CombineArgs + * @see PostProcessingArgs + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class HybridArgs { + + private final List> searchArgs = new ArrayList<>(); + + private final List> vectorArgs = new ArrayList<>(); + + private Optional> combineArgs = Optional.empty(); + + private Optional> postProcessingArgs = Optional.empty(); + + private final Map params = new HashMap<>(); + + private Optional timeout = Optional.empty(); + + /** + * @return a new {@link Builder} for {@link HybridArgs}. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Builder for {@link HybridArgs}. + */ + public static class Builder { + + private final HybridArgs instance = new HybridArgs<>(); + + /** + * Build the {@link HybridArgs} instance. + * + * @return the configured arguments + */ + public HybridArgs build() { + return instance; + } + + /** + * Configure the SEARCH clause using {@link HybridSearchArgs}. + * + * @param searchArgs the search arguments + * @return this builder + */ + public Builder search(HybridSearchArgs searchArgs) { + LettuceAssert.notNull(searchArgs, "Search args must not be null"); + instance.searchArgs.add(searchArgs); + return this; + } + + /** + * Configure the VSIM clause using {@link HybridVectorArgs}. + * + * @param vectorArgs the vector search arguments + * @return this builder + */ + public Builder vectorSearch(HybridVectorArgs vectorArgs) { + LettuceAssert.notNull(vectorArgs, "Vector args must not be null"); + instance.vectorArgs.add(vectorArgs); + return this; + } + + /** + * Configure the COMBINE clause using {@link CombineArgs}. + * + * @param combineArgs the combine arguments + * @return this builder + */ + public Builder combine(CombineArgs combineArgs) { + LettuceAssert.notNull(combineArgs, "Combine args must not be null"); + instance.combineArgs = Optional.of(combineArgs); + return this; + } + + /** + * Set the post-processing arguments. + * + * @param postProcessingArgs the post-processing configuration + * @return this builder + */ + public Builder postProcessing(PostProcessingArgs postProcessingArgs) { + LettuceAssert.notNull(postProcessingArgs, "PostProcessingArgs must not be null"); + instance.postProcessingArgs = Optional.of(postProcessingArgs); + return this; + } + + /** + * Add a parameter for parameterized queries. + *

+ * Parameters can be referenced in queries using {@code $name} syntax. + *

+ * + * @param name the parameter name + * @param value the parameter value + * @return this builder + */ + public Builder param(K name, V value) { + LettuceAssert.notNull(name, "Parameter name must not be null"); + LettuceAssert.notNull(value, "Parameter value must not be null"); + instance.params.put(name, value); + return this; + } + + /** + * Set the maximum time to wait for the query to complete. + * + * @param timeout the timeout duration (with millisecond resolution) + * @return this builder + */ + public Builder timeout(Duration timeout) { + LettuceAssert.notNull(timeout, "Timeout must not be null"); + instance.timeout = Optional.of(timeout); + return this; + } + + } + + /** + * Build the command arguments for the configured {@link HybridArgs}. + *

+ * Command structure: SEARCH [SCORER] [YIELD_SCORE_AS] VSIM [KNN/RANGE] [FILTER]* [YIELD_DISTANCE_AS] [COMBINE] + * [YIELD_SCORE_AS] [SORTBY] [FILTER]* [LIMIT] [RETURN] [LOAD] [PARAMS] + *

+ * + * @param args the {@link CommandArgs} to append to + */ + public void build(CommandArgs args) { + // Both SEARCH and VSIM must be configured (per PRD) + LettuceAssert.notNull(searchArgs, "SEARCH clause is required - use search() or search(HybridSearchArgs)"); + LettuceAssert.notNull(vectorArgs, "VSIM clause is required - use vectorSearch() or vectorSearch(HybridVectorArgs)"); + + // SEARCH clause + searchArgs.forEach(searchArg -> searchArg.build(args)); + + // VSIM clause + vectorArgs.forEach(vectorArg -> vectorArg.build(args)); + + // COMBINE clause + if (combineArgs.isPresent()) { + args.add(CommandKeyword.COMBINE); + combineArgs.get().build(args); + } + + // Post-processing operations (LOAD, GROUPBY, APPLY, SORTBY, FILTER, LIMIT) + postProcessingArgs.ifPresent(postProcessing -> postProcessing.build(args)); + + // PARAMS clause + if (!params.isEmpty()) { + args.add(CommandKeyword.PARAMS); + args.add(params.size() * 2L); + params.forEach((name, value) -> { + args.addKey(name); + args.addValue(value); + }); + } + + // TIMEOUT clause + timeout.ifPresent(t -> { + args.add(CommandKeyword.TIMEOUT); + args.add(t.toMillis()); + }); + + } + +} diff --git a/src/main/java/io/lettuce/core/search/arguments/HybridSearchArgs.java b/src/main/java/io/lettuce/core/search/arguments/HybridSearchArgs.java new file mode 100644 index 000000000..de200a56e --- /dev/null +++ b/src/main/java/io/lettuce/core/search/arguments/HybridSearchArgs.java @@ -0,0 +1,184 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.search.arguments; + +import java.util.Optional; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; + +/** + * Arguments for the SEARCH clause in FT.HYBRID command. Configures text search query, scoring function, and score aliasing. + * + * @param Key type + * @param Value type + * @author Aleksandar Todorov + * @since 7.2 + * @see ScoringFunction + * @see Scorer + */ +@Experimental +public class HybridSearchArgs { + + private final V query; + + private final Scorer scorer; + + private final K scoreAlias; + + private HybridSearchArgs(Builder builder) { + this.query = builder.query; + this.scorer = builder.scorer; + this.scoreAlias = builder.scoreAlias; + } + + public static Builder builder() { + return new Builder<>(); + } + + public V getQuery() { + return query; + } + + public Optional getScorer() { + return Optional.ofNullable(scorer); + } + + public Optional getScoreAlias() { + return Optional.ofNullable(scoreAlias); + } + + public static class Builder { + + private V query; + + private Scorer scorer; + + private K scoreAlias; + + /** + * Set the text search query. + * + * @param query the search query + * @return this builder + */ + public Builder query(V query) { + LettuceAssert.notNull(query, "Query must not be null"); + this.query = query; + return this; + } + + /** + * Set the scoring algorithm with optional parameters. + * + * @param scorer the scorer to use + * @return this builder + */ + public Builder scorer(Scorer scorer) { + LettuceAssert.notNull(scorer, "Scorer must not be null"); + this.scorer = scorer; + return this; + } + + /** + * Set an alias for the text search score field. + * + * @param alias the field name to use for the search score + * @return this builder + */ + public Builder scoreAlias(K alias) { + LettuceAssert.notNull(alias, "Score alias must not be null"); + this.scoreAlias = alias; + return this; + } + + /** + * Build the {@link HybridSearchArgs} instance. + * + * @return the configured arguments + */ + public HybridSearchArgs build() { + LettuceAssert.notNull(query, "Query must not be null"); + return new HybridSearchArgs<>(this); + } + + } + + /** + * Build the SEARCH clause arguments. + * + * @param args the {@link CommandArgs} to append to + */ + public void build(CommandArgs args) { + args.add(CommandKeyword.SEARCH); + args.addValue(query); + + // SCORER inside SEARCH + if (scorer != null) { + scorer.build(args); + } + + // YIELD_SCORE_AS for SEARCH + if (scoreAlias != null) { + args.add(CommandKeyword.YIELD_SCORE_AS); + args.addKey(scoreAlias); + } + } + + /** + * Scoring configuration for text search. Specifies the scoring algorithm. + *

+ * Note: Parameter support will be added in a future release when the Redis server supports it. + *

+ * + *

Example:

+ * + *
+     * Scorer.of(ScoringFunction.BM25)
+     * // Output: SCORER BM25
+     * 
+ * + * @author Aleksandar Todorov + * @since 7.2 + * @see ScoringFunction + */ + public static class Scorer { + + private final ScoringFunction algorithm; + + private Scorer(ScoringFunction algorithm) { + this.algorithm = algorithm; + } + + /** + * Create a scorer with the specified algorithm. + * + * @param algorithm the scoring algorithm + * @return a new {@link Scorer} instance + */ + public static Scorer of(ScoringFunction algorithm) { + LettuceAssert.notNull(algorithm, "Algorithm must not be null"); + return new Scorer(algorithm); + } + + /** + * Build the SCORER arguments. + * + * @param args the {@link CommandArgs} to append to + * @param key type + * @param value type + */ + public void build(CommandArgs args) { + args.add(CommandKeyword.SCORER); + args.add(algorithm.toString()); + } + + } + +} diff --git a/src/main/java/io/lettuce/core/search/arguments/HybridVectorArgs.java b/src/main/java/io/lettuce/core/search/arguments/HybridVectorArgs.java new file mode 100644 index 000000000..c09012a3e --- /dev/null +++ b/src/main/java/io/lettuce/core/search/arguments/HybridVectorArgs.java @@ -0,0 +1,360 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.search.arguments; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; +import io.lettuce.core.protocol.CommandType; + +/** + * Arguments for the VSIM clause in FT.HYBRID command. Configures vector similarity search including field, vector data, search + * method (KNN or RANGE), filters, and score aliasing. + * + * @param Key type + * @param Value type + * @author Aleksandar Todorov + * @since 7.2 + * @see VectorSearchMethod + * @see Knn + * @see Range + */ +@Experimental +public class HybridVectorArgs { + + private final K fieldName; + + private final V vectorData; + + private final VectorSearchMethod method; + + private final List filters; + + private final K scoreAlias; + + private HybridVectorArgs(Builder builder) { + this.fieldName = builder.fieldName; + this.vectorData = builder.vectorData; + this.method = builder.method; + this.filters = new ArrayList<>(builder.filters); + this.scoreAlias = builder.scoreAlias; + } + + public static Builder builder() { + return new Builder<>(); + } + + public K getFieldName() { + return fieldName; + } + + public V getVectorData() { + return vectorData; + } + + public Optional getMethod() { + return Optional.ofNullable(method); + } + + public List getFilters() { + return filters; + } + + public Optional getScoreAlias() { + return Optional.ofNullable(scoreAlias); + } + + public static class Builder { + + private K fieldName; + + private V vectorData; + + private VectorSearchMethod method; + + private final List filters = new ArrayList<>(); + + private K scoreAlias; + + /** + * Set the vector field name. + * + * @param fieldName the field name (typically prefixed with '@') + * @return this builder + */ + public Builder field(K fieldName) { + LettuceAssert.notNull(fieldName, "Field name must not be null"); + this.fieldName = fieldName; + return this; + } + + /** + * Set the query vector data. + * + * @param vectorData the vector data (byte array or other format) + * @return this builder + */ + public Builder vector(V vectorData) { + LettuceAssert.notNull(vectorData, "Vector data must not be null"); + this.vectorData = vectorData; + return this; + } + + /** + * Set the query vector data as byte array. + *

+ * This overload allows passing binary vector data (e.g., float arrays encoded as bytes) when using String codec. + *

+ * + * @param vectorData the vector data as byte array + * @return this builder + */ + @SuppressWarnings("unchecked") + public Builder vector(byte[] vectorData) { + LettuceAssert.notNull(vectorData, "Vector data must not be null"); + this.vectorData = (V) vectorData; + return this; + } + + /** + * Set the vector search method (KNN or RANGE). + * + * @param method the search method + * @return this builder + * @see Knn + * @see Range + */ + public Builder method(VectorSearchMethod method) { + LettuceAssert.notNull(method, "Vector search method must not be null"); + this.method = method; + return this; + } + + /** + * Add a FILTER expression for pre-filtering documents before vector scoring. + * + * @param expression the filter expression (e.g., "@brand:{apple|samsung}") + * @return this builder + */ + public Builder filter(String expression) { + LettuceAssert.notNull(expression, "Filter expression must not be null"); + this.filters.add(expression); + return this; + } + + /** + * Set an alias for the vector distance field (normalized vector score). + * + * @param alias the field name to use for the normalized vector distance + * @return this builder + */ + public Builder scoreAlias(K alias) { + LettuceAssert.notNull(alias, "Score alias must not be null"); + this.scoreAlias = alias; + return this; + } + + /** + * Build the {@link HybridVectorArgs} instance. + * + * @return the configured arguments + */ + public HybridVectorArgs build() { + LettuceAssert.notNull(fieldName, "Field name must not be null"); + LettuceAssert.notNull(vectorData, "Vector data must not be null"); + return new HybridVectorArgs<>(this); + } + + } + + /** + * Build the VSIM clause arguments. + * + * @param args the {@link CommandArgs} to append to + */ + public void build(CommandArgs args) { + args.add(CommandType.VSIM); + args.addKey(fieldName); + + // Handle byte[] vectors specially (they're always binary data) + if (vectorData instanceof byte[]) { + args.add((byte[]) vectorData); + } else { + args.addValue((V) vectorData); + } + + // Vector search method (KNN or RANGE) - optional + if (method != null) { + method.build(args); + } + + // FILTER inside VSIM + for (String filter : filters) { + args.add(CommandKeyword.FILTER); + args.add(filter); + } + + // YIELD_SCORE_AS for VSIM (normalized vector distance) + if (scoreAlias != null) { + args.add(CommandKeyword.YIELD_SCORE_AS); + args.addKey(scoreAlias); + } + } + + /** + * Interface for vector search methods used in VSIM clause. + *

+ * Defines how vector similarity search is performed - either by finding K nearest neighbors (KNN) or by finding all vectors + * within a certain distance range. + *

+ */ + public interface VectorSearchMethod { + + /** + * Build the method arguments into the command. + * + * @param args command arguments + * @param key type + * @param value type + */ + void build(CommandArgs args); + + } + + /** + * KNN (K-Nearest Neighbors) search method implementation. + *

+ * Finds the K most similar vectors to the query vector. This is the default and most common vector search method. + *

+ */ + public static class Knn implements VectorSearchMethod { + + private final int k; + + private Integer efRuntime; + + /** + * Creates a new KNN search method. + * + * @param k number of nearest neighbors to return + */ + private Knn(int k) { + LettuceAssert.isTrue(k > 0, "K must be positive"); + this.k = k; + } + + /** + * Static factory method to create a KNN search method. + * + * @param k number of nearest neighbors to return + * @return new KNN search method + */ + public static Knn of(int k) { + return new Knn(k); + } + + /** + * Set the EF_RUNTIME parameter for HNSW search. + *

+ * The EF_RUNTIME parameter controls the size of the dynamic candidate list during HNSW search. Higher values improve + * recall but increase search time. + *

+ * + * @param efRuntime size of the dynamic candidate list for HNSW algorithm + * @return this KNN search method + */ + public Knn efRuntime(int efRuntime) { + LettuceAssert.isTrue(efRuntime > 0, "EF_RUNTIME must be positive"); + this.efRuntime = efRuntime; + return this; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.KNN); + // Count of total items: K + value, optionally EF_RUNTIME + value + int itemCount = efRuntime != null ? 4 : 2; + args.add(itemCount); + args.add(CommandKeyword.K); + args.add(k); + if (efRuntime != null) { + args.add(CommandKeyword.EF_RUNTIME); + args.add(efRuntime); + } + } + + } + + /** + * Range-based search method implementation. + *

+ * Finds all vectors within a specified distance radius from the query vector. + *

+ */ + public static class Range implements VectorSearchMethod { + + private final double radius; + + private Double epsilon; + + /** + * Creates a new Range search method. + * + * @param radius maximum distance from query vector + */ + private Range(double radius) { + LettuceAssert.isTrue(radius > 0, "Radius must be positive"); + this.radius = radius; + } + + /** + * Static factory method to create a Range search method. + * + * @param radius maximum distance from query vector + * @return new Range search method + */ + public static Range of(double radius) { + return new Range(radius); + } + + /** + * Set the epsilon parameter for distance calculation tolerance. + *

+ * The epsilon parameter provides a tolerance for the distance calculation. + *

+ * + * @param epsilon tolerance for distance calculation + * @return this Range search method + */ + public Range epsilon(double epsilon) { + LettuceAssert.isTrue(epsilon > 0, "Epsilon must be positive"); + this.epsilon = epsilon; + return this; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.RANGE); + // Count of key-value pairs: 1 for RADIUS, +1 if EPSILON is present + int pairCount = epsilon != null ? 4 : 2; + args.add(pairCount); + args.add(CommandKeyword.RADIUS); + args.add(radius); + if (epsilon != null) { + args.add(CommandKeyword.EPSILON); + args.add(epsilon); + } + } + + } + +} diff --git a/src/main/java/io/lettuce/core/search/arguments/PostProcessingArgs.java b/src/main/java/io/lettuce/core/search/arguments/PostProcessingArgs.java new file mode 100644 index 000000000..8fbba6355 --- /dev/null +++ b/src/main/java/io/lettuce/core/search/arguments/PostProcessingArgs.java @@ -0,0 +1,608 @@ +/* + * Copyright 2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ + +package io.lettuce.core.search.arguments; + +import io.lettuce.core.annotations.Experimental; +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; +import io.lettuce.core.protocol.ProtocolKeyword; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Argument list builder for FT.HYBRID and FT.AGGREGATE post-processing operations. Operations are applied in user-specified + * order after the COMBINE clause. + * + *

Basic Usage:

+ * + *
+ *
+ * {
+ *     @code
+ *     PostProcessingArgs args = PostProcessingArgs. builder().load("@price", "@category")
+ *             .addOperation(GroupBy.of("@category").reduce(Reducer.of(ReduceFunction.COUNT).as("total")))
+ *             .addOperation(Apply.of("@price * 0.9", "discounted_price"))
+ *             .addOperation(SortBy.of("@discounted_price", SortDirection.DESC))
+ *             .addOperation(Filter.of("@discounted_price > 100")).build();
+ * }
+ * 
+ * + * @param Key type. + * @param Value type. + * @author Aleksandar Todorov + * @since 7.2 + * @see GroupBy + * @see Apply + * @see SortBy + * @see Filter + * @see Limit + */ +@Experimental +public class PostProcessingArgs { + + private final List loadFields = new ArrayList<>(); + + /** + * Ordered list of pipeline operations (GROUPBY, SORTBY, APPLY, FILTER). These operations are applied in the order specified + * by the user. + */ + private final List> postProcessingOperations = new ArrayList<>(); + + /** + * @return a new {@link Builder} for {@link PostProcessingArgs}. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Builder for {@link PostProcessingArgs}. + * + * @param Key type. + * @param Value type. + */ + public static class Builder { + + private final PostProcessingArgs instance = new PostProcessingArgs<>(); + + /** + * Request loading of document attributes. + * + * @param fields the field identifiers + * @return this builder + */ + @SafeVarargs + public final Builder load(K... fields) { + LettuceAssert.notNull(fields, "Fields must not be null"); + for (K field : fields) { + LettuceAssert.notNull(field, "Field must not be null"); + instance.loadFields.add(field); + } + return this; + } + + /** + * Add a post-processing operation to the pipeline. + *

+ * Operations are applied in the order they are added. Supported operations include: + *

+ *
    + *
  • {@link GroupBy} - Group results by properties with optional reducers
  • + *
  • {@link Apply} - Apply expressions to create computed fields
  • + *
  • {@link SortBy} - Sort results by properties
  • + *
  • {@link Filter} - Filter results by expressions
  • + *
+ * + *

Example Usage:

+ * + *
+         * {@code
+         * PostProcessingArgs. builder()
+         *     .addOperation(GroupBy.of("@category").reduce(Reducer.of(ReduceFunction.COUNT).as("total")))
+         *     .addOperation(Apply.of("@price * 0.9", "discounted_price"))
+         *     .addOperation(SortBy.of("@discounted_price", SortDirection.DESC))
+         *     .addOperation(Filter.of("@discounted_price > 100"))
+         *     .build();
+         * }
+         * 
+ * + * @param operation the operation to add (GroupBy, Apply, SortBy, Filter, etc.) + * @return this builder + */ + public Builder addOperation(PostProcessingOperation operation) { + LettuceAssert.notNull(operation, "Operation must not be null"); + instance.postProcessingOperations.add(operation); + return this; + } + + /** + * Build the {@link PostProcessingArgs}. + * + * @return the built {@link PostProcessingArgs} + */ + public PostProcessingArgs build() { + return instance; + } + + } + + /** + * Build the post-processing command arguments. + *

+ * LOAD is built first, then post-processing operations are built in user-specified order. + *

+ * + * @param args the {@link CommandArgs} to append to + */ + public void build(CommandArgs args) { + + args.add(CommandKeyword.LOAD); + args.add(loadFields.size()); // Count prefix required + loadFields.forEach(args::addKey); + + for (PostProcessingOperation operation : postProcessingOperations) { + // Cast is safe because all operations can build with CommandArgs + @SuppressWarnings("unchecked") + PostProcessingOperation typedOperation = (PostProcessingOperation) operation; + typedOperation.build(args); + } + } + + /** + * Interface for post-processing operations applied in user-specified order. + * + * @param Key type. + * @param Value type. + */ + public interface PostProcessingOperation { + + /** + * Build the operation arguments into the command args. + * + * @param args the command args to build into + */ + void build(CommandArgs args); + + } + + /** + * GROUPBY post-processing operation. Groups results by one or more properties with reducer functions. + * + * @param Key type. + * @param Value type. + * @see Reducer + * @see ReduceFunction + */ + public static class GroupBy implements PostProcessingOperation { + + private final List properties; + + private final List> reducers; + + private GroupBy(List properties) { + this.properties = new ArrayList<>(properties); + this.reducers = new ArrayList<>(); + } + + /** + * Add a reducer to this GROUPBY operation. + * + * @param reducer the reducer to add + * @return this GroupBy instance + */ + public GroupBy reduce(Reducer reducer) { + this.reducers.add(reducer); + return this; + } + + /** + * Static factory method to create a GroupBy instance. + * + * @param properties the properties to group by + * @param Key type + * @param Value type + * @return new GroupBy instance + */ + @SafeVarargs + public static GroupBy of(K... properties) { + return new GroupBy<>(Arrays.asList(properties)); + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.GROUPBY); + args.add(properties.size()); + for (K property : properties) { + // Add @ prefix if not already present + String propertyStr = property.toString(); + if (!propertyStr.startsWith("@")) { + args.add("@" + propertyStr); + } else { + args.add(propertyStr); + } + } + + for (Reducer reducer : reducers) { + reducer.build(args); + } + } + + } + + /** + * APPLY post-processing operation. Applies a 1-to-1 transformation expression on properties and stores the result as a new + * property. + * + * @param Key type. + * @param Value type. + */ + public static class Apply implements PostProcessingOperation { + + private final V expression; + + private final K name; + + /** + * Creates a new APPLY operation. + * + * @param expression the expression to apply + * @param name the result field name + */ + public Apply(V expression, K name) { + this.expression = expression; + this.name = name; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.APPLY); + args.addValue(expression); + args.add(CommandKeyword.AS); + args.add(name.toString()); + } + + /** + * Static factory method to create an Apply instance with a single name and expression pair. + * + * @param expression the expression to apply + * @param name the name of the expression + * @param Key type + * @param Value type + * @return new Apply instance + */ + public static Apply of(V expression, K name) { + return new Apply<>(expression, name); + } + + } + + /** + * SORTBY post-processing operation. Sorts results by one or more properties with optional MAX optimization for top-N + * queries. + * + * @param Key type. + * @see SortProperty + * @see SortDirection + */ + public static class SortBy implements PostProcessingOperation { + + private final List> properties; + + /** + * Creates a new SORTBY operation. + * + * @param properties the properties to sort by + */ + public SortBy(List> properties) { + this.properties = new ArrayList<>(properties); + } + + /** + * Static factory method to create a SortBy instance with multiple properties. + * + * @param properties the properties to sort by + * @param Key type + * @return new SortBy instance + */ + @SafeVarargs + public static SortBy of(SortProperty... properties) { + return new SortBy<>(Arrays.asList(properties)); + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.SORTBY); + // Count includes property + direction pairs + args.add(properties.size() * 2L); + for (SortProperty property : properties) { + // Add @ prefix if not already present + String propertyStr = property.property.toString(); + if (!propertyStr.startsWith("@")) { + args.add("@" + propertyStr); + } else { + args.add(propertyStr); + } + args.add(property.direction.name()); + } + } + + } + + /** + * FILTER post-processing operation. Filters results using predicate expressions on the current pipeline state. + * + * @param Key type. + * @param Value type. + */ + public static class Filter implements PostProcessingOperation { + + private final V expression; + + /** + * Creates a new FILTER operation. + * + * @param expression the filter expression (e.g., "@price > 100", "@category == 'electronics'") + */ + public Filter(V expression) { + this.expression = expression; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.FILTER); + args.addValue(expression); + } + + /** + * Static factory method to create a Filter instance. + * + * @param expression the filter expression + * @param Key type + * @param Value type + * @return new Filter instance + */ + public static Filter of(V expression) { + return new Filter<>(expression); + } + + } + + /** + * LIMIT post-processing operation. Limits the number of results for pagination. + * + * @param Key type. + * @param Value type. + */ + public static class Limit implements PostProcessingOperation { + + final long offset; + + final long num; + + private Limit(long offset, long num) { + this.offset = offset; + this.num = num; + } + + @Override + public void build(CommandArgs args) { + args.add(CommandKeyword.LIMIT); + args.add(offset); + args.add(num); + } + + /** + * Static factory method to create a Limit instance. + * + * @param offset the zero-based starting index + * @param num the maximum number of results to return + * @param Key type + * @param Value type + * @return new Limit instance + */ + public static Limit of(long offset, long num) { + return new Limit<>(offset, num); + } + + } + + /** + * REDUCE function for GROUPBY operations. Performs aggregate operations on grouped results with optional aliasing. + * + * @param Key type. + * @param Value type. + * @see ReduceFunction + */ + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public static class Reducer { + + private final ReduceFunction function; + + private final List args; + + private Optional alias = Optional.empty(); + + /** + * Creates a new reducer. + * + * @param function the reducer function + * @param args the arguments to the reducer function + */ + private Reducer(ReduceFunction function, List args) { + this.function = function; + this.args = new ArrayList<>(args); + } + + /** + * Static factory method to create a Reducer with a function and optional arguments. + * + * @param function the reducer function + * @param args the arguments to the reducer function (optional, can be empty for COUNT) + * @param Key type + * @param Value type + * @return new Reducer instance + */ + @SafeVarargs + public static Reducer of(ReduceFunction function, V... args) { + LettuceAssert.notNull(function, "ReduceFunction must not be null"); + return new Reducer<>(function, args.length == 0 ? Collections.emptyList() : Arrays.asList(args)); + } + + /** + * Set an alias for the reducer result. + * + * @param alias the alias name + * @return this reducer + */ + public Reducer as(K alias) { + LettuceAssert.notNull(alias, "Alias must not be null"); + this.alias = Optional.of(alias); + return this; + } + + /** + * Build the reducer into command args. + * + * @param args the command args to build into + */ + public void build(CommandArgs args) { + args.add(CommandKeyword.REDUCE); + args.add(function); + args.add(this.args.size()); + for (V arg : this.args) { + args.addValue(arg); + } + + alias.ifPresent(a -> { + args.add(CommandKeyword.AS); + args.add(a.toString()); + }); + } + + } + + /** + * Enumeration of REDUCE functions for GROUPBY operations. + */ + public enum ReduceFunction implements ProtocolKeyword { + + /** Count the number of records in the group. */ + COUNT, + + /** Count unique values of a field. */ + COUNT_DISTINCT, + + /** Approximate count of unique values using HyperLogLog algorithm. */ + COUNT_DISTINCTISH, + + /** Sum all numeric values of a field. */ + SUM, + + /** Calculate the average of numeric values. */ + AVG, + + /** Find the minimum value. */ + MIN, + + /** Find the maximum value. */ + MAX, + + /** Calculate standard deviation. */ + STDDEV, + + /** Calculate quantile/percentile (e.g., median at 0.5). */ + QUANTILE, + + /** Collect all values into a list. */ + TOLIST, + + /** Get the first value in the group. */ + FIRST_VALUE, + + /** Random sampling from the group. */ + RANDOM_SAMPLE; + + private final byte[] bytes; + + ReduceFunction() { + this.bytes = name().getBytes(StandardCharsets.US_ASCII); + } + + @Override + public byte[] getBytes() { + return bytes; + } + + } + + /** + * Represents a sort property with direction for SORTBY operations. + * + * @param Key type. + */ + public static class SortProperty { + + final K property; + + final SortDirection direction; + + /** + * Creates a new sort property. + * + * @param property the property to sort by + * @param direction the sort direction + */ + public SortProperty(K property, SortDirection direction) { + this.property = property; + this.direction = direction; + } + + /** + * Get the property to sort by. + * + * @return the property + */ + public K getProperty() { + return property; + } + + /** + * Get the sort direction. + * + * @return the sort direction + */ + public SortDirection getDirection() { + return direction; + } + + } + + /** + * Sort direction enumeration for SORTBY operations. + */ + public enum SortDirection { + + /** + * Ascending sort order. + */ + ASC, + + /** + * Descending sort order. + */ + DESC + + } + +} diff --git a/src/main/java/io/lettuce/core/search/arguments/SearchArgs.java b/src/main/java/io/lettuce/core/search/arguments/SearchArgs.java index 6400724b2..5bedf3c9e 100644 --- a/src/main/java/io/lettuce/core/search/arguments/SearchArgs.java +++ b/src/main/java/io/lettuce/core/search/arguments/SearchArgs.java @@ -558,10 +558,19 @@ public void build(CommandArgs args) { if (!returnFields.isEmpty()) { args.add(CommandKeyword.RETURN); - args.add(returnFields.size()); + // Count total number of field specifications (field + optional AS + alias) + int count = returnFields.size(); + + // Add 2 for each "AS" keyword and alias value + count += (int) (returnFields.values().stream().filter(Optional::isPresent).count() * 2); + + args.add(count); returnFields.forEach((field, as) -> { args.addKey(field); - as.ifPresent(args::addKey); + if (as.isPresent()) { + args.add(CommandKeyword.AS); + args.addKey(as.get()); + } }); } diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt index 10e8447d0..5d7cc2f30 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt @@ -24,6 +24,8 @@ import io.lettuce.core.search.arguments.SugGetArgs import io.lettuce.core.search.arguments.SynUpdateArgs import io.lettuce.core.search.AggregationReply.Cursor +import io.lettuce.core.search.HybridReply +import io.lettuce.core.search.arguments.HybridArgs /** * Coroutine executed commands for RediSearch functionality @@ -1231,5 +1233,19 @@ interface RediSearchCoroutinesCommands { @Experimental suspend fun ftCursordel(index: String, cursor: Cursor): String? + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + suspend fun ftHybrid(index: String, args: HybridArgs): HybridReply? + } diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommandsImpl.kt index 1f79a5838..2ee27f6b8 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommandsImpl.kt @@ -23,6 +23,8 @@ import io.lettuce.core.search.arguments.SugAddArgs import io.lettuce.core.search.arguments.SugGetArgs import io.lettuce.core.search.arguments.SynUpdateArgs import io.lettuce.core.search.AggregationReply.Cursor +import io.lettuce.core.search.HybridReply +import io.lettuce.core.search.arguments.HybridArgs import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.asFlow @@ -91,6 +93,9 @@ open class RediSearchCoroutinesCommandsImpl(internal val ops: override suspend fun ftCursordel(index: String, cursor: Cursor): String? = ops.ftCursordel(index, cursor).awaitFirstOrNull() + override suspend fun ftHybrid(index: String, args: HybridArgs): HybridReply? = + ops.ftHybrid(index, args).awaitFirstOrNull() + override suspend fun ftDictadd(dict: String, vararg terms: V): Long? = ops.ftDictadd(dict, *terms).awaitFirstOrNull() diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt index acd55b4d4..50d717b59 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-Present, Redis Ltd. and Contributors + * Copyright 2017-Present, Redis Ltd. and Contributors * All rights reserved. * * Licensed under the MIT License. @@ -20,11 +20,23 @@ package io.lettuce.core.api.coroutines -import io.lettuce.core.* -import kotlinx.coroutines.flow.Flow -import java.time.Duration +import io.lettuce.core.CompareCondition +import java.util.Date import java.time.Instant -import java.util.* +import java.time.Duration + +import io.lettuce.core.CopyArgs +import io.lettuce.core.ExpireArgs +import io.lettuce.core.KeyScanArgs +import io.lettuce.core.KeyScanCursor +import io.lettuce.core.MigrateArgs +import io.lettuce.core.RestoreArgs +import io.lettuce.core.ScanArgs +import io.lettuce.core.ScanCursor +import io.lettuce.core.SortArgs +import io.lettuce.core.ExperimentalLettuceCoroutinesApi +import io.lettuce.core.annotations.Experimental +import kotlinx.coroutines.flow.Flow /** * Coroutine executed commands for Keys (Key manipulation/querying). @@ -67,6 +79,27 @@ interface RedisKeyCoroutinesCommands { */ suspend fun del(vararg keys: K): Long? + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be `null`. + * @return Long integer-reply the number of keys that were removed. + * @since 7.1 + */ + @Experimental + suspend fun delex(key: K, compareCondition: CompareCondition): Long? + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or `null` when `key` does not exist. + * @since 7.1 + */ + @Experimental + suspend fun digestKey(key: K): String? + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt index 5195e705e..062e60524 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt @@ -52,6 +52,12 @@ internal class RedisKeyCoroutinesCommandsImpl(internal val ops override suspend fun unlink(vararg keys: K): Long? = ops.unlink(*keys).awaitFirstOrNull() + override suspend fun delex(key: K, condition: CompareCondition): Long? = + ops.delex(key, condition).awaitFirstOrNull() + + override suspend fun digestKey(key: K): String? = + ops.digestKey(key).awaitFirstOrNull() + override suspend fun dump(key: K): ByteArray? = ops.dump(key).awaitFirstOrNull() override suspend fun exists(vararg keys: K): Long? = diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt index f6500da83..5da68e59b 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt @@ -300,6 +300,17 @@ interface RedisStringCoroutinesCommands { */ suspend fun msetnx(map: Map): Boolean? + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the [MSetExArgs] specifying NX/XX and expiration. + * @return Boolean from integer-reply: `1` if all keys were set, `0` otherwise. + * @since 7.1 + */ + suspend fun msetex(map: Map, args: MSetExArgs): Boolean? + /** * Set the string value of a key. * diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt index 8bfbb56d4..a6a779ffb 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt @@ -98,6 +98,8 @@ internal class RedisStringCoroutinesCommandsImpl(internal val override suspend fun msetnx(map: Map): Boolean? = ops.msetnx(map).awaitFirstOrNull() + override suspend fun msetex(map: Map, args: MSetExArgs): Boolean? = ops.msetex(map, args).awaitFirstOrNull() + override suspend fun set(key: K, value: V): String? = ops.set(key, value).awaitFirstOrNull() override suspend fun set(key: K, value: V, setArgs: SetArgs): String? = ops.set(key, value, setArgs).awaitFirstOrNull() diff --git a/src/main/templates/io/lettuce/core/api/RediSearchCommands.java b/src/main/templates/io/lettuce/core/api/RediSearchCommands.java index 35b3eb1b6..0ec3cf91b 100644 --- a/src/main/templates/io/lettuce/core/api/RediSearchCommands.java +++ b/src/main/templates/io/lettuce/core/api/RediSearchCommands.java @@ -1228,4 +1228,18 @@ public interface RediSearchCommands { @Experimental String ftCursordel(String index, Cursor cursor); + /** + * Execute a hybrid query combining text search and vector similarity. + * + * @param index the index name + * @param args the hybrid query arguments + * @return the hybrid search results + * @see FT.HYBRID + * @see HybridArgs + * @see HybridReply + * @since 7.2 + */ + @Experimental + HybridReply ftHybrid(String index, HybridArgs args); + } diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java index 81217833f..17f50cdb6 100644 --- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java @@ -67,6 +67,29 @@ public interface RedisKeyCommands { */ Long del(K... keys); + /** + * Delete the specified key if the compare condition matches. + * + * @param key the key. + * @param compareCondition the compare condition, must not be {@code null}. + * @return Long integer-reply the number of keys that were removed. + * + * @since 7.1 + */ + @Experimental + Long delex(K key, CompareCondition compareCondition); + + /** + * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string. + * + * @param key the key. + * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist. + * + * @since 7.1 + */ + @Experimental + String digestKey(K key); + /** * Unlink one or more keys (non blocking DEL). * diff --git a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java index d4530aa88..3599f22fa 100644 --- a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java @@ -356,6 +356,17 @@ public interface RedisStringCommands { */ Boolean msetnx(Map map); + /** + * Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one + * of [EX|PX|EXAT|PXAT|KEEPTTL]. + * + * @param map the map of keys and values. + * @param args the {@link MSetExArgs} specifying NX/XX and expiration. + * @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise. + * @since 7.1 + */ + Boolean msetex(Map map, MSetExArgs args); + /** * Set the string value of a key. * diff --git a/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java index d342bae4e..9eae3905d 100644 --- a/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java +++ b/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java @@ -7,20 +7,27 @@ * Licensed under the MIT License. */ import static io.lettuce.core.protocol.CommandType.FT_CURSOR; -import static io.lettuce.core.search.arguments.AggregateArgs.*; import io.lettuce.core.codec.StringCodec; import io.lettuce.core.protocol.Command; +import io.lettuce.core.protocol.CommandArgs; import io.lettuce.core.search.AggregationReply; +import io.lettuce.core.search.HybridReply; import io.lettuce.core.search.SearchReply; +import io.lettuce.core.search.SpellCheckResult; +import io.lettuce.core.search.Suggestion; import io.lettuce.core.search.arguments.AggregateArgs; +import io.lettuce.core.search.arguments.CombineArgs; import io.lettuce.core.search.arguments.CreateArgs; +import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; +import io.lettuce.core.search.arguments.HybridSearchArgs; +import io.lettuce.core.search.arguments.HybridVectorArgs; import io.lettuce.core.search.arguments.NumericFieldArgs; +import io.lettuce.core.search.arguments.PostProcessingArgs; import io.lettuce.core.search.arguments.QueryDialects; -import io.lettuce.core.search.SpellCheckResult; -import io.lettuce.core.search.Suggestion; -import io.lettuce.core.search.arguments.ExplainArgs; +import io.lettuce.core.search.arguments.ScoringFunction; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; import io.lettuce.core.search.arguments.SugAddArgs; @@ -33,6 +40,8 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Arrays; @@ -642,10 +651,11 @@ void shouldMaintainPipelineOperationOrder() { AggregateArgs aggregateArgs = AggregateArgs. builder()// .apply("@price * @quantity", "total_value")// First operation .filter("@total_value > 100")// Second operation - .groupBy(GroupBy. of("category").reduce(Reducer. count().as("count")))// Third - // operation + .groupBy(AggregateArgs.GroupBy. of("category") + .reduce(AggregateArgs.Reducer. count().as("count")))// Third + // operation .limit(0, 5)// Fourth operation - .sortBy(SortBy.of("count", SortDirection.DESC))// Fifth operation + .sortBy(AggregateArgs.SortBy.of("count", AggregateArgs.SortDirection.DESC))// Fifth operation .build(); Command> command = builder.ftAggregate(MY_KEY, MY_QUERY, @@ -673,12 +683,13 @@ void shouldCorrectlyConstructFtAggregateCommandWithArgs() { AggregateArgs aggregateArgs = AggregateArgs. builder()// .verbatim()// .load("title")// - .groupBy(GroupBy. of("category").reduce(Reducer. count().as("count")))// - .sortBy(SortBy.of("count", SortDirection.DESC))// - .apply(Apply.of("@title", "title_upper"))// + .groupBy(AggregateArgs.GroupBy. of("category") + .reduce(AggregateArgs.Reducer. count().as("count")))// + .sortBy(AggregateArgs.SortBy.of("count", AggregateArgs.SortDirection.DESC))// + .apply(AggregateArgs.Apply.of("@title", "title_upper"))// .limit(0, 10)// .filter("@category:{$category}")// - .withCursor(WithCursor.of(10L, Duration.ofSeconds(10)))// + .withCursor(AggregateArgs.WithCursor.of(10L, Duration.ofSeconds(10)))// .param("category", "electronics")// .scorer("TFIDF")// .addScores()// @@ -757,4 +768,64 @@ void shouldCorrectlyConstructFtCursordelCommand() { assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo(result); } + @Test + void returnFieldsWithAlias() { + SearchArgs options = SearchArgs. builder().returnField("as_is") + .returnField("$.field", "alias").build(); + + CommandArgs args = new CommandArgs<>(new StringCodec()); + options.build(args); + + // buggy implementation returns "RETURN 2 key key<$.field> key DIALECT " + assertThat("RETURN 4 key key<$.field> AS key DIALECT 2").isEqualTo(args.toCommandString()); + } + + @Test + void shouldCorrectlyConstructFtHybridCommand() { + + byte[] queryVector = floatArrayToByteArray(new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f }); + + HybridArgs hybridArgs = HybridArgs. builder() + .search(HybridSearchArgs. builder().query("@category:{electronics} smartphone camera") + .scorer(HybridSearchArgs.Scorer.of(ScoringFunction.TF_IDF_NORMALIZED)).scoreAlias("text_score").build()) + .vectorSearch(HybridVectorArgs. builder().field("@image_embedding").vector(queryVector) + .method(HybridVectorArgs.Knn.of(20).efRuntime(150)).filter("@brand:{apple|samsung|google}") + .scoreAlias("vector_score").build()) + .combine(CombineArgs.of(new CombineArgs.Linear().alpha(0.7).beta(0.3))) + .postProcessing(PostProcessingArgs. builder().load("@price", "@brand", "@category") + .addOperation(PostProcessingArgs.GroupBy. of("@brand") + .reduce(PostProcessingArgs.Reducer + . of(PostProcessingArgs.ReduceFunction.SUM, "@price").as("sum")) + .reduce(PostProcessingArgs.Reducer. of(PostProcessingArgs.ReduceFunction.COUNT) + .as("count"))) + .addOperation(PostProcessingArgs.SortBy + .of(new PostProcessingArgs.SortProperty<>("@sum", PostProcessingArgs.SortDirection.ASC))) + .addOperation(PostProcessingArgs.Apply.of("@sum * 0.9", "discounted_price")) + .addOperation(PostProcessingArgs.Filter.of("@sum > 700")) + .addOperation(PostProcessingArgs.Limit.of(0, 20)).build()) + .param("discount_rate", "0.9").param("$vector", new String(queryVector)).build(); + + Command> command = builder.ftHybrid("idx:ecommerce", hybridArgs); + + String args = command.getArgs().toCommandString(); + + String expected = "idx:ecommerce SEARCH value<@category:{electronics} smartphone camera> SCORER TFIDF.DOCNORM " + + "YIELD_SCORE_AS key VSIM key<@image_embedding> zczMPc3MTD6amZk+zczMPgAAAD+amRk/MzMzP83MTD9mZmY/AACAPw== " + + "KNN 4 K 20 EF_RUNTIME 150 FILTER @brand:{apple|samsung|google} YIELD_SCORE_AS key " + + "COMBINE LINEAR 4 ALPHA 0.7 BETA 0.3 LOAD 3 key<@price> key<@brand> key<@category> GROUPBY 1 @brand " + + "REDUCE U1VN 1 value<@price> AS sum REDUCE Q09VTlQ= 0 AS count SORTBY 2 @sum ASC " + + "APPLY value<@sum * 0.9> AS discounted_price FILTER value<@sum > 700> LIMIT 0 20 " + + "PARAMS 4 key value<0.9> key<$vector> value<" + new String(queryVector) + ">"; + + assertThat(args).isEqualTo(expected); + } + + private byte[] floatArrayToByteArray(float[] vector) { + ByteBuffer buffer = ByteBuffer.allocate(vector.length * 4).order(ByteOrder.LITTLE_ENDIAN); + for (float value : vector) { + buffer.putFloat(value); + } + return buffer.array(); + } + } diff --git a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java index 6c77dfed1..84e43f054 100644 --- a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java +++ b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java @@ -10,7 +10,10 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.time.Instant; import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -482,4 +485,243 @@ void xdelexWithPolicyShouldRejectNullKey() { .isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Key must not be null"); } + @Test + void shouldCorrectlyConstructSetWithExAndIfeq() { + Command command = sut.set("mykey", "myvalue", + SetArgs.Builder.ex(100).compareCondition(CompareCondition.valueEq("oldvalue"))); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)) + .isEqualTo("*7\r\n" + "$3\r\n" + "SET\r\n" + "$5\r\n" + "mykey\r\n" + "$7\r\n" + "myvalue\r\n" + "$2\r\n" + + "EX\r\n" + "$3\r\n" + "100\r\n" + "$4\r\n" + "IFEQ\r\n" + "$8\r\n" + "oldvalue\r\n"); + } + + @Test + void shouldCorrectlyConstructSetWithExAtAndIfne() { + Command command = sut.set("mykey", "myvalue", + SetArgs.Builder.exAt(1234567890).compareCondition(CompareCondition.valueNe("wrongvalue"))); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)) + .isEqualTo("*7\r\n" + "$3\r\n" + "SET\r\n" + "$5\r\n" + "mykey\r\n" + "$7\r\n" + "myvalue\r\n" + "$4\r\n" + + "EXAT\r\n" + "$10\r\n" + "1234567890\r\n" + "$4\r\n" + "IFNE\r\n" + "$10\r\n" + "wrongvalue\r\n"); + } + + @Test + void shouldCorrectlyConstructSetGetWithPxAndIfdne() { + Command command = sut.setGet("mykey", "myvalue", + SetArgs.Builder.px(50000).compareCondition(CompareCondition.digestNe("0123456789abcdef"))); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*8\r\n" + "$3\r\n" + "SET\r\n" + "$5\r\n" + "mykey\r\n" + + "$7\r\n" + "myvalue\r\n" + "$2\r\n" + "PX\r\n" + "$5\r\n" + "50000\r\n" + "$5\r\n" + "IFDNE\r\n" + "$16\r\n" + + "0123456789abcdef\r\n" + "$3\r\n" + "GET\r\n"); + } + + @Test + void shouldCorrectlyConstructSetGetWithPxAtAndIfdeq() { + Command command = sut.setGet("mykey", "myvalue", + SetArgs.Builder.pxAt(1234567890123L).compareCondition(CompareCondition.digestEq("fedcba9876543210"))); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*8\r\n" + "$3\r\n" + "SET\r\n" + "$5\r\n" + "mykey\r\n" + + "$7\r\n" + "myvalue\r\n" + "$4\r\n" + "PXAT\r\n" + "$13\r\n" + "1234567890123\r\n" + "$5\r\n" + "IFDEQ\r\n" + + "$16\r\n" + "fedcba9876543210\r\n" + "$3\r\n" + "GET\r\n"); + } + + @Test + void shouldCorrectlyConstructDelexWithValueEq() { + Command command = sut.delex("mykey", CompareCondition.valueEq("expectedvalue")); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$5\r\n" + "DELEX\r\n" + "$5\r\n" + "mykey\r\n" + + "$4\r\n" + "IFEQ\r\n" + "$13\r\n" + "expectedvalue\r\n"); + } + + @Test + void shouldCorrectlyConstructDelexWithDigestNe() { + Command command = sut.delex("mykey", CompareCondition.digestNe("0011223344556677")); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$5\r\n" + "DELEX\r\n" + "$5\r\n" + "mykey\r\n" + + "$5\r\n" + "IFDNE\r\n" + "$16\r\n" + "0011223344556677\r\n"); + } + + @Test + void shouldCorrectlyConstructDigestKey() { + Command command = sut.digestKey("mykey"); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*2\r\n" + "$6\r\n" + "DIGEST\r\n" + "$5\r\n" + "mykey\r\n"); + } + + @Test + void msetex_nxThenEx_seconds_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + MSetExArgs a = MSetExArgs.Builder.nx().ex(Duration.ofSeconds(5)); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value EX 5 NX"); + } + + @Test + void msetex_xxThenKeepTtl_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + MSetExArgs a = MSetExArgs.Builder.xx().keepttl(); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value XX KEEPTTL"); + } + + @Test + void msetex_noConditionThenPx_millis_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + MSetExArgs a = MSetExArgs.Builder.px(Duration.ofMillis(500)); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PX 500"); + } + + @Test + void msetex_noConditionThenPx_duration_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + MSetExArgs a = MSetExArgs.Builder.px(Duration.ofMillis(1234)); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PX 1234"); + } + + @Test + void msetex_exAt_withInstant_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Instant t = Instant.ofEpochSecond(1_234_567_890L); + MSetExArgs a = MSetExArgs.Builder.exAt(t); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value EXAT 1234567890"); + } + + @Test + void msetex_pxAt_withInstant_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Instant t = Instant.ofEpochMilli(4_000L); + MSetExArgs a = MSetExArgs.Builder.pxAt(t); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PXAT 4000"); + } + + @Test + void msetex_exAt_withLong_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + long epochSeconds = 1_234_567_890L; + MSetExArgs a = MSetExArgs.Builder.exAt(Instant.ofEpochSecond(epochSeconds)); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value EXAT 1234567890"); + } + + @Test + void msetex_pxAt_withLong_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + long epochMillis = 4_567L; + MSetExArgs a = MSetExArgs.Builder.pxAt(Instant.ofEpochMilli(epochMillis)); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PXAT 4567"); + } + + @Test + void msetex_nxThenExAt_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Instant t = Instant.ofEpochSecond(42L); + MSetExArgs a = MSetExArgs.Builder.nx().exAt(t); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value EXAT 42 NX"); + } + + @Test + void msetex_xxThenPxAt_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Instant t = Instant.ofEpochMilli(314L); + MSetExArgs a = MSetExArgs.Builder.xx().pxAt(t); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PXAT 314 XX"); + } + + @Test + void msetex_exAt_withDate_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Date ts = new Date(1_234_567_890L * 1000L); + MSetExArgs a = MSetExArgs.Builder.exAt(ts.toInstant()); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value EXAT 1234567890"); + } + + @Test + void msetex_pxAt_withDate_emissionOrder() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + Date ts = new Date(9_999L); + MSetExArgs a = MSetExArgs.Builder.pxAt(ts.toInstant()); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value PXAT 9999"); + } + + @Test + void msetex_noCondition_noExpiration_onlyMapAndCount() { + Map map = new LinkedHashMap<>(); + map.put("k", "v"); + + Command cmd = sut.msetex(map, null); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("1 key value"); + } + + @Test + void msetex_twoPairs_keepttl_orderAndCount() { + Map map = new LinkedHashMap<>(); + map.put("k1", "v1"); + map.put("k2", "v2"); + MSetExArgs a = MSetExArgs.Builder.keepttl(); + + Command cmd = sut.msetex(map, a); + String s = cmd.getArgs().toCommandString(); + assertThat(s).isEqualTo("2 key value key value KEEPTTL"); + } + } diff --git a/src/test/java/io/lettuce/core/RedisURIBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisURIBuilderUnitTests.java index 0f52e63c7..95333b441 100644 --- a/src/test/java/io/lettuce/core/RedisURIBuilderUnitTests.java +++ b/src/test/java/io/lettuce/core/RedisURIBuilderUnitTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; -import reactor.core.publisher.Mono; import reactor.test.StepVerifier; /** @@ -102,6 +101,57 @@ void redisWithLibrary() { assertThat(result.getLibraryVersion()).isEqualTo("1.foo"); } + @Test + void redisWithDriverInfo() { + DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0").build(); + RedisURI result = RedisURI.Builder.redis("localhost").withDriverInfo(driverInfo).build(); + + assertThat(result.getLibraryName()).isEqualTo("Lettuce(spring-data-redis_v3.2.0)"); + } + + @Test + void redisWithDriverInfoMultipleDrivers() { + DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0") + .addUpstreamDriver("spring-boot", "3.3.0").build(); + RedisURI result = RedisURI.Builder.redis("localhost").withDriverInfo(driverInfo).build(); + + // Most recently added driver should appear first (prepend behavior) + assertThat(result.getLibraryName()).isEqualTo("Lettuce(spring-boot_v3.3.0;spring-data-redis_v3.2.0)"); + } + + @Test + void redisWithDriverInfoCustomName() { + DriverInfo driverInfo = DriverInfo.builder().name("my-custom-lib").addUpstreamDriver("spring-data-redis", "3.2.0") + .build(); + RedisURI result = RedisURI.Builder.redis("localhost").withDriverInfo(driverInfo).build(); + + assertThat(result.getLibraryName()).isEqualTo("my-custom-lib(spring-data-redis_v3.2.0)"); + } + + @Test + void redisWithDriverInfoNullShouldFail() { + assertThatThrownBy(() -> RedisURI.Builder.redis("localhost").withDriverInfo(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void redisWithLibraryNameThenDriverInfo() { + // Last call wins: withDriverInfo overwrites previous withLibraryName + DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0").build(); + RedisURI result = RedisURI.Builder.redis("localhost").withLibraryName("my-lib").withDriverInfo(driverInfo).build(); + + assertThat(result.getLibraryName()).isEqualTo("Lettuce(spring-data-redis_v3.2.0)"); + } + + @Test + void redisWithDriverInfoThenLibraryName() { + // Last call wins: withLibraryName overwrites previous withDriverInfo + DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0").build(); + RedisURI result = RedisURI.Builder.redis("localhost").withDriverInfo(driverInfo).withLibraryName("my-lib").build(); + + assertThat(result.getLibraryName()).isEqualTo("my-lib"); + } + @Test void redisHostAndPortWithInvalidPort() { assertThatThrownBy(() -> RedisURI.Builder.redis("localhost", -1)).isInstanceOf(IllegalArgumentException.class); diff --git a/src/test/java/io/lettuce/core/RedisURIUnitTests.java b/src/test/java/io/lettuce/core/RedisURIUnitTests.java index 7c01ce07f..e47e38fad 100644 --- a/src/test/java/io/lettuce/core/RedisURIUnitTests.java +++ b/src/test/java/io/lettuce/core/RedisURIUnitTests.java @@ -31,8 +31,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; -import io.lettuce.core.cluster.ClusterTestSettings; -import io.lettuce.test.settings.TestSettings; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -384,4 +382,106 @@ void shouldApplyAuthentication() { assertThat(sourceCp.getCredentialsProvider()).isEqualTo(targetCp.getCredentialsProvider()); } + @Test + void setDriverInfoSingleDriver() { + RedisURI redisURI = RedisURI.create("redis://localhost"); + DriverInfo driverInfo = DriverInfo.builder().name("lettuce").addUpstreamDriver("spring-data-redis", "3.2.0").build(); + redisURI.setDriverInfo(driverInfo); + + assertThat(redisURI.getLibraryName()).isEqualTo("lettuce(spring-data-redis_v3.2.0)"); + } + + @Test + void setDriverInfoMultipleDrivers() { + RedisURI redisURI = RedisURI.create("redis://localhost"); + DriverInfo driverInfo = DriverInfo.builder().name("lettuce").addUpstreamDriver("spring-data-redis", "3.2.0") + .addUpstreamDriver("spring-boot", "3.3.0").build(); + redisURI.setDriverInfo(driverInfo); + + // Most recently added driver should appear first (prepend behavior) + assertThat(redisURI.getLibraryName()).isEqualTo("lettuce(spring-boot_v3.3.0;spring-data-redis_v3.2.0)"); + } + + @Test + void driverInfoChaining() { + RedisURI redisURI = RedisURI.create("redis://localhost"); + + // Spring Data Redis adds itself + DriverInfo springDataInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0").build(); + redisURI.setDriverInfo(springDataInfo); + + // Spring Session chains onto it + DriverInfo existing = redisURI.getDriverInfo(); + DriverInfo withSession = DriverInfo.builder(existing).addUpstreamDriver("spring-session", "3.3.0").build(); + redisURI.setDriverInfo(withSession); + + assertThat(redisURI.getLibraryName()).isEqualTo("Lettuce(spring-session_v3.3.0;spring-data-redis_v3.2.0)"); + } + + @Test + void driverInfoBuilderInvalidNameShouldFail() { + // Name starting with a number + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("123driver", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + // Name with @ symbol + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("driver@name", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + // Name with dots (not allowed in Maven artifactId) + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("com.example.driver", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + // Name starting with hyphen + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("-spring-data", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + // Name with uppercase letters + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("Spring-Data-Redis", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + // Name with spaces + assertThatThrownBy(() -> DriverInfo.builder().addUpstreamDriver("spring data", "1.0.0")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void driverInfoBuilderValidFormats() { + // Valid Maven artifactId formats (lowercase letters, digits, hyphens) + DriverInfo.builder().addUpstreamDriver("spring-data-redis", "1.0.0").addUpstreamDriver("lettuce-core", "2.0.0") + .addUpstreamDriver("commons-math", "3.0.0").addUpstreamDriver("guava", "4.0.0") + .addUpstreamDriver("jedis", "5.0.0").build(); + + // Valid semantic versions with pre-release and build metadata + DriverInfo.builder().addUpstreamDriver("example-lib", "1.0.0-alpha").addUpstreamDriver("example-lib", "1.0.0-beta.1") + .addUpstreamDriver("example-lib", "1.0.0+build.123").addUpstreamDriver("example-lib", "1.0.0-rc.1+build.456") + .build(); + } + + @Test + void defaultLibraryName() { + // Requirement 4: Default behavior - library name defaults to "Lettuce" + RedisURI redisURI = RedisURI.create("redis://localhost"); + assertThat(redisURI.getLibraryName()).isEqualTo("Lettuce"); + } + + @Test + void getLibraryNameWithoutUpstreamDrivers() { + RedisURI redisURI = RedisURI.create("redis://localhost"); + redisURI.setLibraryName("lettuce"); + + // Without upstream drivers, should return just the library name + assertThat(redisURI.getLibraryName()).isEqualTo("lettuce"); + } + + @Test + void setLibraryNameOverridesDriverInfo() { + RedisURI redisURI = RedisURI.create("redis://localhost"); + + // Set driver info first + DriverInfo driverInfo = DriverInfo.builder().addUpstreamDriver("spring-data-redis", "3.2.0").build(); + redisURI.setDriverInfo(driverInfo); + + // Then override with setLibraryName + redisURI.setLibraryName("my-custom-lib"); + + // setLibraryName should completely replace (no drivers) + assertThat(redisURI.getLibraryName()).isEqualTo("my-custom-lib"); + } + } diff --git a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java index f34205de3..64b5d322a 100644 --- a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java +++ b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java @@ -20,7 +20,7 @@ class ClusterReadOnlyCommandsUnitTests { @Test void testCount() { - assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(100); + assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(102); } @Test diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java index 2e1ba647c..bee20c096 100644 --- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; +import io.lettuce.core.CompareCondition; import io.lettuce.core.CopyArgs; import io.lettuce.core.ExpireArgs; import io.lettuce.core.KeyScanArgs; @@ -44,12 +45,14 @@ import io.lettuce.core.RedisException; import io.lettuce.core.RestoreArgs; import io.lettuce.core.ScanCursor; +import io.lettuce.core.SetArgs; import io.lettuce.core.StreamScanCursor; import io.lettuce.core.TestSupport; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.test.LettuceExtension; import io.lettuce.test.ListStreamingAdapter; import io.lettuce.test.condition.EnabledOnCommand; +import io.lettuce.test.condition.RedisConditions; /** * Integration tests for {@link io.lettuce.core.api.sync.RedisKeyCommands}. @@ -612,4 +615,92 @@ void setup100KeyValues(Set expect) { } } + @Test + @EnabledOnCommand("DELEX") + void set_ex_ifeq_then_delex() { + String k = "k:set-ex-ifeq"; + // Initial set with EX + assertThat(redis.set(k, "v1", SetArgs.Builder.ex(100))).isEqualTo("OK"); + assertThat(redis.ttl(k)).isGreaterThan(0); + + // Conditional update with IFEQ + EX + assertThat(redis.set(k, "v2", SetArgs.Builder.ex(200).compareCondition(CompareCondition.valueEq("v1")))) + .isEqualTo("OK"); + assertThat(redis.get(k)).isEqualTo("v2"); + assertThat(redis.ttl(k)).isGreaterThan(100); + + // Delete with DELEX using value condition + assertThat(redis.delex(k, CompareCondition.valueEq("wrong"))).isEqualTo(0); + assertThat(redis.delex(k, CompareCondition.valueEq("v2"))).isEqualTo(1); + assertThat(redis.exists(k)).isEqualTo(0); + } + + @Test + @EnabledOnCommand("DELEX") + void set_exAt_ifne_then_delex() { + String k = "k:set-exat-ifne"; + long expiryTimestamp = Instant.now().plusSeconds(300).getEpochSecond(); + + // Initial set + redis.set(k, "v1"); + + // Conditional update with IFNE + EXAT + assertThat(redis.set(k, "v2", SetArgs.Builder.exAt(expiryTimestamp).compareCondition(CompareCondition.valueNe("v2")))) + .isEqualTo("OK"); + assertThat(redis.get(k)).isEqualTo("v2"); + assertThat(redis.ttl(k)).isGreaterThan(200); + + // Delete with DELEX using value condition + assertThat(redis.delex(k, CompareCondition.valueNe("v2"))).isEqualTo(0); + assertThat(redis.delex(k, CompareCondition.valueNe("wrong"))).isEqualTo(1); + assertThat(redis.exists(k)).isEqualTo(0); + } + + @Test + @EnabledOnCommand("DELEX") + void setGet_px_ifdne_then_delex() { + String k = "k:setget-px-ifdne"; + String wrongKey = "wrong"; + // Initial set + redis.set(k, "A"); + redis.set(wrongKey, "wrong"); + String digestBefore = redis.digestKey(k); + String digestWrong = redis.digestKey(wrongKey); // digest for non-existing value + + // Conditional setGet with IFDNE + PX (digest not equal) + assertThat(redis.setGet(k, "B", SetArgs.Builder.px(100000).compareCondition(CompareCondition.digestNe(digestWrong)))) + .isEqualTo("A"); + assertThat(redis.get(k)).isEqualTo("B"); + assertThat(redis.pttl(k)).isGreaterThan(90000); + + // Delete with DELEX using digest condition + String digestAfter = redis.digestKey(k); + assertThat(redis.delex(k, CompareCondition.digestNe(digestAfter))).isEqualTo(0); + assertThat(redis.delex(k, CompareCondition.digestNe(digestBefore))).isEqualTo(1); + assertThat(redis.exists(k)).isEqualTo(0); + } + + @Test + @EnabledOnCommand("DELEX") + void setGet_pxAt_ifdeq_then_delex() { + String k = "k:setget-pxat-ifdeq"; + long expiryTimestampMs = Instant.now().plusSeconds(300).toEpochMilli(); + + // Initial set + redis.set(k, "X"); + String digestX = redis.digestKey(k); + + // Conditional setGet with IFDEQ + PXAT (digest equal) + assertThat(redis.setGet(k, "Y", + SetArgs.Builder.pxAt(expiryTimestampMs).compareCondition(CompareCondition.digestEq(digestX)))).isEqualTo("X"); + assertThat(redis.get(k)).isEqualTo("Y"); + assertThat(redis.pttl(k)).isGreaterThan(200000); + + // Delete with DELEX using digest condition + String digestY = redis.digestKey(k); + assertThat(redis.delex(k, CompareCondition.digestEq(digestX))).isEqualTo(0); + assertThat(redis.delex(k, CompareCondition.digestEq(digestY))).isEqualTo(1); + assertThat(redis.exists(k)).isEqualTo(0); + } + } diff --git a/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java index 8278f2014..40b043554 100644 --- a/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java @@ -640,6 +640,15 @@ void clientSetinfo() { assertThat(redis.clientInfo().contains("lib-name=lettuce")).isTrue(); } + @Test + @EnabledOnCommand("WAITAOF") + // Redis 7.2 + void clientSetinfoWithUpstreamDriver() { + redis.clientSetinfo("lib-name", "lettuce(spring-data-redis_v1.0.0)"); + + assertThat(redis.clientInfo().contains("lib-name=lettuce(spring-data-redis_v1.0.0)")).isTrue(); + } + @Test void testReadOnlyCommands() { for (ProtocolKeyword readOnlyCommand : ClusterReadOnlyCommands.getReadOnlyCommands()) { diff --git a/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java index 6da6e48b7..bbae1eb24 100644 --- a/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java @@ -47,6 +47,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import static io.lettuce.TestTags.INTEGRATION_TEST; import static io.lettuce.core.protocol.CommandType.XINFO; @@ -274,6 +275,8 @@ void xreadSingleStream() { assertThat(firstMessage.getStream()).isEqualTo("stream-1"); assertThat(firstMessage.getBody()).hasSize(1).containsEntry("key1", "value1"); + assertThat(firstMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(firstMessage.getDeliveredCount()).isNull(); StreamMessage nextMessage = messages.get(1); @@ -303,12 +306,16 @@ void xreadMultipleStreams() { assertThat(firstMessage.getId()).isEqualTo(initial1); assertThat(firstMessage.getStream()).isEqualTo("{s1}stream-1"); assertThat(firstMessage.getBody()).hasSize(1).containsEntry("key1", "value1"); + assertThat(firstMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(firstMessage.getDeliveredCount()).isNull(); StreamMessage secondMessage = messages.get(3); assertThat(secondMessage.getId()).isEqualTo(message2); assertThat(secondMessage.getStream()).isEqualTo("{s1}stream-2"); assertThat(secondMessage.getBody()).hasSize(2).containsEntry("key4", "value4"); + assertThat(secondMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(secondMessage.getDeliveredCount()).isNull(); } @Test @@ -333,12 +340,16 @@ public void xreadTransactional() { assertThat(firstMessage.getId()).isEqualTo(message1); assertThat(firstMessage.getStream()).isEqualTo("stream-1"); assertThat(firstMessage.getBody()).containsEntry("key3", "value3"); + assertThat(firstMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(firstMessage.getDeliveredCount()).isNull(); StreamMessage secondMessage = messages.get(1); assertThat(secondMessage.getId()).isEqualTo(message2); assertThat(secondMessage.getStream()).isEqualTo("stream-2"); assertThat(secondMessage.getBody()).containsEntry("key4", "value4"); + assertThat(secondMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(secondMessage.getDeliveredCount()).isNull(); } @Test @@ -357,6 +368,8 @@ public void xreadLastVsLatest() { assertThat(lastMessage.getStream()).isEqualTo("stream-1"); assertThat(lastMessage.getBody()).hasSize(1).containsEntry("key2", "value2"); + assertThat(lastMessage.getMillisElapsedFromDelivery()).isNull(); + assertThat(lastMessage.getDeliveredCount()).isNull(); assertThat(latestMessages).isEmpty(); } @@ -1082,4 +1095,147 @@ void xtrimWithTrimmingModeDelref() { assertThat(pending.getCount()).isLessThan(3L); } + // XREADGORUP CLAIM Tests - 8.4 OSS + // since: 7.1 + + private static final String KEY = "it:stream:claim:move:" + UUID.randomUUID(); + + private static final String GROUP = "g"; + + private static final String C1 = "c1"; + + private static final String C2 = "c2"; + + private static final Map BODY = new HashMap() { + + { + put("f", "v"); + } + + }; + + private static final long IDLE_TIME_MS = 5; + + private void beforeEachClaimTest() throws InterruptedException { + assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.4"), "Redis 8.4+ required for XREADGROUP CLAIM"); + + // Produce two entries + redis.xadd(KEY, BODY); + redis.xadd(KEY, BODY); + + // Create group and consume with c1 so entries become pending for c1 + redis.xgroupCreate(XReadArgs.StreamOffset.from(KEY, "0-0"), GROUP); + redis.xreadgroup(Consumer.from(GROUP, C1), XReadArgs.Builder.count(10), XReadArgs.StreamOffset.lastConsumed(KEY)); + + // Ensure idle time so entries are claimable + Thread.sleep(IDLE_TIME_MS); + } + + @Test + void xreadgroupClaim_returnsMetadataOrdered() throws Exception { + beforeEachClaimTest(); + + // Produce fresh entries that are NOT claimed (not pending) + redis.xadd(KEY, BODY); + redis.xadd(KEY, BODY); + + List> consumer2 = redis.xreadgroup(Consumer.from(GROUP, C2), + XReadArgs.Builder.claim(Duration.ofMillis(IDLE_TIME_MS)).count(10), XReadArgs.StreamOffset.lastConsumed(KEY)); + long claimedCount = consumer2.stream().filter(StreamMessage::isClaimed).count(); + long freshCount = consumer2.size() - claimedCount; + StreamMessage first = consumer2.get(0); + StreamMessage second = consumer2.get(1); + StreamMessage third = consumer2.get(2); + StreamMessage fourth = consumer2.get(3); + + // Assertions + assertThat(consumer2).isNotNull(); + assertThat(consumer2).isNotEmpty(); + assertThat(claimedCount).isEqualTo(2); + assertThat(freshCount).isEqualTo(2); + + // Assert order: pending entries are first + assertThat(first.isClaimed()).isTrue(); + assertThat(second.isClaimed()).isTrue(); + assertThat(third.isClaimed()).isFalse(); + assertThat(fourth.isClaimed()).isFalse(); + + // Assert claimed message structure + assertThat(first.getMillisElapsedFromDelivery()).isGreaterThanOrEqualTo(5); + assertThat(first.getDeliveredCount()).isGreaterThanOrEqualTo(1); + assertThat(first.getBody()).containsEntry("f", "v"); + assertThat(fourth.getMillisElapsedFromDelivery()).isEqualTo(0); + assertThat(fourth.getDeliveredCount()).isEqualTo(0); + assertThat(fourth.getBody()).containsEntry("f", "v"); + } + + @Test + void xreadgroupClaim_movesPendingFromC1ToC2AndRemainsPendingUntilAck() throws Exception { + beforeEachClaimTest(); + + PendingMessages before = redis.xpending(KEY, GROUP); + List> res = redis.xreadgroup(Consumer.from(GROUP, C2), + XReadArgs.Builder.claim(Duration.ofMillis(IDLE_TIME_MS)).count(10), XReadArgs.StreamOffset.lastConsumed(KEY)); + PendingMessages afterClaim = redis.xpending(KEY, GROUP); + long acked = redis.xack(KEY, GROUP, res.get(0).getId(), res.get(1).getId()); + PendingMessages afterAck = redis.xpending(KEY, GROUP); + + // Verify pending belongs to c1 + assertThat(before.getCount()).isEqualTo(2); + assertThat(before.getConsumerMessageCount().getOrDefault(C1, 0L)).isEqualTo(2); + + // Verify claim withv c2 + assertThat(res).isNotNull(); + assertThat(res).isNotEmpty(); + long claimed = res.stream().filter(StreamMessage::isClaimed).count(); + assertThat(claimed).isEqualTo(2); + + // After claim: entries are pending for c2 (moved), not acked yet + assertThat(afterClaim.getCount()).isEqualTo(2); + assertThat(afterClaim.getConsumerMessageCount().getOrDefault(C1, 0L)).isEqualTo(0); + assertThat(afterClaim.getConsumerMessageCount().getOrDefault(C2, 0L)).isEqualTo(2); + + // XACK the claimed entries -> PEL should become empty + assertThat(acked).isEqualTo(2); + assertThat(afterAck.getCount()).isEqualTo(0); + } + + @Test + void xreadgroupClaim_claimWithNoackDoesNotCreatePendingAndRemovesClaimedFromPel() throws Exception { + beforeEachClaimTest(); + + PendingMessages before = redis.xpending(KEY, GROUP); + + // Also produce fresh entries that should not be added to PEL when NOACK is set + redis.xadd(KEY, BODY); + redis.xadd(KEY, BODY); + + // Claim with NOACK using c2 + List> res = redis.xreadgroup(Consumer.from(GROUP, C2), + XReadArgs.Builder.claim(Duration.ofMillis(IDLE_TIME_MS)).noack(true).count(10), + XReadArgs.StreamOffset.lastConsumed(KEY)); + PendingMessages afterNoack = redis.xpending(KEY, GROUP); + + assertThat(res).isNotNull(); + assertThat(res).isNotEmpty(); + + long claimedCount = res.stream().filter(StreamMessage::isClaimed).count(); + long freshCount = res.size() - claimedCount; + assertThat(claimedCount).isEqualTo(2); + assertThat(freshCount).isEqualTo(2); + + // After NOACK read, previously pending entries remain pending (NOACK does not remove them) + assertThat(afterNoack.getCount()).isEqualTo(2); + + // Before claim: entries are pending for c1 + assertThat(before.getCount()).isEqualTo(2); + assertThat(before.getConsumerMessageCount().getOrDefault(C1, 0L)).isEqualTo(2); + assertThat(before.getConsumerMessageCount().getOrDefault(C2, 0L)).isEqualTo(0); + + // Claimed entries remain pending and are now owned by c2 (CLAIM reassigns ownership). Fresh entries were not added + // to PEL. + assertThat(afterNoack.getConsumerMessageCount().getOrDefault(C1, 0L)).isEqualTo(0); + assertThat(afterNoack.getConsumerMessageCount().getOrDefault(C2, 0L)).isEqualTo(2); + } + } diff --git a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java index a90c3c27a..9d199deae 100644 --- a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java @@ -25,30 +25,28 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.lang.reflect.Proxy; import java.time.Duration; import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.inject.Inject; -import org.junit.jupiter.api.Assumptions; +import io.lettuce.core.cluster.SlotHash; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + import io.lettuce.core.*; -import io.lettuce.core.api.StatefulConnection; -import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; -import io.lettuce.core.dynamic.Commands; -import io.lettuce.core.dynamic.RedisCommandFactory; -import io.lettuce.core.dynamic.annotation.Command; -import io.lettuce.core.dynamic.annotation.Param; import io.lettuce.test.KeyValueStreamingAdapter; import io.lettuce.test.LettuceExtension; import io.lettuce.test.condition.EnabledOnCommand; @@ -180,6 +178,51 @@ void msetnx() { assertThat(redis.get("two")).isEqualTo("2"); } + @ParameterizedTest(name = "MSETEX NX + {0} with cross-slot keys") + @MethodSource("msetexNxArgsProvider") + @EnabledOnCommand("MSETEX") + protected void msetexNxWithCrossSlotKeys_parametrized(String optionLabel, MSetExArgs args) { + + // Build 16 keys with distinct hash tags so they map across 16 evenly-partitioned buckets over 16000 slots + final int buckets = 16; + Map map = new LinkedHashMap<>(); + + for (int b = 0; b < buckets; b++) { + for (int j = 0;; j++) { // find a tag that lands in bucket b + String k = "msetex:{t" + b + '-' + j + "}"; // only the tag influences the slot + int slot = SlotHash.getSlot(k); + int bucket = Math.min(slot / 1000, buckets - 1); + if (bucket == b) { + map.put(k, "v" + b); + break; + } + } + } + + // Execute MSETEX with NX + the provided option + Boolean result = redis.msetex(map, args); + assertThat(result).isTrue(); + + // Verify TTL semantics depending on option + String anyKey = map.keySet().iterator().next(); + long ttl = redis.ttl(anyKey); + if ("KEEPTTL".equals(optionLabel)) { + // KEEPTTL with NX on new keys -> no expiration should be set + assertThat(ttl).isEqualTo(-1); + } else { + // Expiring variants (EX/PX/EXAT/PXAT): TTL should be > 0 shortly after + assertThat(ttl).isGreaterThan(0L); + } + } + + static Stream msetexNxArgsProvider() { + return Stream.of(Arguments.of("EX", MSetExArgs.Builder.nx().ex(Duration.ofSeconds(5))), + Arguments.of("PX", MSetExArgs.Builder.nx().px(Duration.ofMillis(5000))), + Arguments.of("EXAT", MSetExArgs.Builder.nx().exAt(Instant.now().plusSeconds(5))), + Arguments.of("PXAT", MSetExArgs.Builder.nx().pxAt(Instant.now().plusSeconds(5))), + Arguments.of("KEEPTTL", MSetExArgs.Builder.nx().keepttl())); + } + @Test void set() { assertThat(redis.get(key)).isNull(); diff --git a/src/test/java/io/lettuce/core/json/DelegateJsonArrayUnitTests.java b/src/test/java/io/lettuce/core/json/DelegateJsonArrayUnitTests.java index 32e70f3a0..5b36b290c 100644 --- a/src/test/java/io/lettuce/core/json/DelegateJsonArrayUnitTests.java +++ b/src/test/java/io/lettuce/core/json/DelegateJsonArrayUnitTests.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import java.util.Iterator; +import java.util.List; import static io.lettuce.TestTags.UNIT_TEST; import static org.assertj.core.api.Assertions.assertThat; @@ -214,4 +215,68 @@ void asAnythingElse() { assertThat(underTest.asNumber()).isNull(); } + @Test + void asListWithNestedObjects() { + ObjectMapper objectMapper = new ObjectMapper(); + DefaultJsonParser parser = new DefaultJsonParser(objectMapper); + + // Create an array containing nested objects + DelegateJsonArray underTest = new DelegateJsonArray(objectMapper); + underTest.add(parser.createJsonValue("{\"name\": \"Alice\", \"age\": 30}")); + underTest.add(parser.createJsonValue("{\"name\": \"Bob\", \"age\": 25}")); + + // Test that asList() returns proper DelegateJsonObject instances for nested objects + List list = underTest.asList(); + assertThat(list).hasSize(2); + + // Verify that elements from asList() behave consistently with get() + for (int i = 0; i < list.size(); i++) { + JsonValue listElement = list.get(i); + JsonValue getElement = underTest.get(i); + + // Both should be DelegateJsonObject instances + assertThat(listElement.isJsonObject()).isTrue(); + assertThat(getElement.isJsonObject()).isTrue(); + + // Both should have the same behavior + assertThat(listElement.asJsonObject()).isNotNull(); + assertThat(getElement.asJsonObject()).isNotNull(); + + // Both should return the same string representation + assertThat(listElement.toString()).isEqualTo(getElement.toString()); + } + } + + @Test + void asListWithNestedArrays() { + ObjectMapper objectMapper = new ObjectMapper(); + DefaultJsonParser parser = new DefaultJsonParser(objectMapper); + + // Create an array containing nested arrays + DelegateJsonArray underTest = new DelegateJsonArray(objectMapper); + underTest.add(parser.createJsonValue("[1, 2, 3]")); + underTest.add(parser.createJsonValue("[\"a\", \"b\", \"c\"]")); + + // Test that asList() returns proper DelegateJsonArray instances for nested arrays + List list = underTest.asList(); + assertThat(list).hasSize(2); + + // Verify that elements from asList() behave consistently with get() + for (int i = 0; i < list.size(); i++) { + JsonValue listElement = list.get(i); + JsonValue getElement = underTest.get(i); + + // Both should be DelegateJsonArray instances + assertThat(listElement.isJsonArray()).isTrue(); + assertThat(getElement.isJsonArray()).isTrue(); + + // Both should have the same behavior + assertThat(listElement.asJsonArray()).isNotNull(); + assertThat(getElement.asJsonArray()).isNotNull(); + + // Both should return the same string representation + assertThat(listElement.toString()).isEqualTo(getElement.toString()); + } + } + } diff --git a/src/test/java/io/lettuce/core/output/StreamReadOutputUnitTests.java b/src/test/java/io/lettuce/core/output/StreamReadOutputUnitTests.java index af7a7883d..cc3946df2 100644 --- a/src/test/java/io/lettuce/core/output/StreamReadOutputUnitTests.java +++ b/src/test/java/io/lettuce/core/output/StreamReadOutputUnitTests.java @@ -179,4 +179,135 @@ void shouldDecodeFromTwoStreams() { assertThat(streamMessage2.getBody()).hasSize(1).containsEntry("key2", "value2"); } + @Test + void shouldDecodeClaimedEntryWithMetadata() { + + // Stream and single claimed entry + sut.multi(2); + sut.set(ByteBuffer.wrap("stream-key".getBytes())); + sut.complete(1); + sut.multi(1); + sut.multi(4); + sut.set(ByteBuffer.wrap("1234-12".getBytes())); + sut.complete(3); + sut.multi(2); + sut.set(ByteBuffer.wrap("key".getBytes())); + sut.complete(4); + sut.set(ByteBuffer.wrap("value".getBytes())); + sut.complete(4); + // extras for claimed pending entry + sut.set(5000); + sut.set(2); + sut.complete(3); + sut.complete(2); + sut.complete(1); + sut.complete(0); + + assertThat(sut.get()).hasSize(1); + StreamMessage streamMessage = sut.get().get(0); + assertThat(streamMessage.getMillisElapsedFromDelivery()).isEqualTo(5000); + assertThat(streamMessage.getDeliveredCount()).isEqualTo(2); + assertThat(streamMessage.getBody()).hasSize(1).containsEntry("key", "value"); + } + + @Test + void shouldDecodeFreshEntryWithZeroRedeliveriesAsNotClaimed() { + + // Stream and single entry that carries extras with redeliveryCount=0 + sut.multi(2); + sut.set(ByteBuffer.wrap("stream-key".getBytes())); + sut.complete(1); + sut.multi(1); + sut.multi(4); + sut.set(ByteBuffer.wrap("1234-12".getBytes())); + sut.complete(3); + sut.multi(2); + sut.set(ByteBuffer.wrap("key".getBytes())); + sut.complete(4); + sut.set(ByteBuffer.wrap("value".getBytes())); + sut.complete(4); + // extras indicate not previously delivered (redeliveryCount=0) + sut.set(1000); // ms since last delivery + sut.set(0); // redeliveryCount + sut.complete(3); + sut.complete(2); + sut.complete(1); + sut.complete(0); + + assertThat(sut.get()).hasSize(1); + StreamMessage streamMessage = sut.get().get(0); + assertThat(streamMessage.isClaimed()).isFalse(); + assertThat(streamMessage.getMillisElapsedFromDelivery()).isEqualTo(1000); + assertThat(streamMessage.getDeliveredCount()).isEqualTo(0); + } + + @Test + void shouldDecodeMixedBatchClaimedFirstThenFresh() { + + // One stream with three entries: two claimed (redelivery >= 1) then one fresh (redelivery == 0) + sut.multi(2); + sut.set(ByteBuffer.wrap("stream-key".getBytes())); + sut.complete(1); + sut.multi(3); + + // Entry #1 (claimed) + sut.multi(4); + sut.set(ByteBuffer.wrap("1-0".getBytes())); + sut.complete(3); + sut.multi(2); + sut.set(ByteBuffer.wrap("f1".getBytes())); + sut.complete(4); + sut.set(ByteBuffer.wrap("v1".getBytes())); + sut.complete(4); + sut.set(1500); // msSinceLastDelivery + sut.set(2); // redeliveryCount + sut.complete(3); + sut.complete(2); + + // Entry #2 (claimed) + sut.multi(4); + sut.set(ByteBuffer.wrap("2-0".getBytes())); + sut.complete(3); + sut.multi(2); + sut.set(ByteBuffer.wrap("f2".getBytes())); + sut.complete(4); + sut.set(ByteBuffer.wrap("v2".getBytes())); + sut.complete(4); + sut.set(1200); + sut.set(1); + sut.complete(3); + sut.complete(2); + + // Entry #3 (fresh, still carries metadata with redeliveryCount=0) + sut.multi(4); + sut.set(ByteBuffer.wrap("3-0".getBytes())); + sut.complete(3); + sut.multi(2); + sut.set(ByteBuffer.wrap("f3".getBytes())); + sut.complete(4); + sut.set(ByteBuffer.wrap("v3".getBytes())); + sut.complete(4); + sut.set(10); + sut.set(0); + sut.complete(3); + + sut.complete(2); + + sut.complete(1); + sut.complete(0); + + assertThat(sut.get()).hasSize(3); + StreamMessage m1 = sut.get().get(0); + StreamMessage m2 = sut.get().get(1); + StreamMessage m3 = sut.get().get(2); + + assertThat(m1.isClaimed()).isTrue(); + assertThat(m2.isClaimed()).isTrue(); + assertThat(m3.isClaimed()).isFalse(); + + assertThat(m1.getDeliveredCount()).isEqualTo(2); + assertThat(m2.getDeliveredCount()).isEqualTo(1); + assertThat(m3.getDeliveredCount()).isEqualTo(0); + } + } diff --git a/src/test/java/io/lettuce/core/protocol/CodecFailureIntegrationTests.java b/src/test/java/io/lettuce/core/protocol/CodecFailureIntegrationTests.java new file mode 100644 index 000000000..d4b479346 --- /dev/null +++ b/src/test/java/io/lettuce/core/protocol/CodecFailureIntegrationTests.java @@ -0,0 +1,314 @@ +/* + * Copyright 2011-Present, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.protocol; + +import static io.lettuce.TestTags.INTEGRATION_TEST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import javax.inject.Inject; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.TestSupport; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.RedisCodec; +import io.lettuce.core.event.connection.ReconnectAttemptEvent; +import io.netty.handler.codec.EncoderException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.test.LettuceExtension; + +/** + * Integration tests for command encoding error scenarios with GET/SET commands against a Redis test instance. + * + * @author Lettuce Contributors + */ +@ExtendWith(LettuceExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Tag(INTEGRATION_TEST) +class CodecFailureIntegrationTests extends TestSupport { + + private final RedisClient client; + + private final StatefulRedisConnection connection; + + @Inject + CodecFailureIntegrationTests(RedisClient client, StatefulRedisConnection connection) { + this.client = client; + this.connection = connection; + } + + @BeforeEach + void setUp() { + this.connection.async().flushall(); + } + + @Test + void testCommandsWithCustomCodecRuntimeException() { + + final Integer[] reconnects = { 0 }; + client.getResources().eventBus().get().subscribe(event -> { + if (event instanceof ReconnectAttemptEvent) { + reconnects[0]++; + } + }); + + try (StatefulRedisConnection customConnection = client.connect(failingCodec)) { + RedisCommands customRedis = customConnection.sync(); + + // First, test that normal values work fine + String normalKey = "normal-key"; + String normalValue = "normal-value"; + + String result = customRedis.set(normalKey, normalValue); + assertThat(result).isEqualTo("OK"); + + String retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Now test that the specific failure value throws an exception + String failingKey = "failing-key"; + String failingValue = "encoding_failure"; + + assertThatThrownBy(() -> customRedis.set(failingKey, failingValue)).isInstanceOf(EncoderException.class) + .hasMessageContaining( + "Cannot encode command. Closing the connection as the connection state may be out of sync."); + + // test that commands are executed after reconnecting + retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // verify that we have reconnected after the exception + assertThat(reconnects[0]).isEqualTo(1); + } + } + + @Test + void testCommandsWithCustomCodecOutOfMemoryError() { + + try (StatefulRedisConnection customConnection = client.connect(failingCodecOOM)) { + RedisCommands customRedis = customConnection.sync(); + + // First, test that normal values work fine + String normalKey = "normal-key"; + String normalValue = "normal-value"; + + String result = customRedis.set(normalKey, normalValue); + assertThat(result).isEqualTo("OK"); + + String retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Now test that the specific failure value throws an exception + String failingKey = "failing-key"; + String failingValue = "encoding_failure"; + + assertThatThrownBy(() -> customRedis.set(failingKey, failingValue)).isInstanceOf(EncoderException.class) + .hasMessageContaining( + "Cannot encode command. Closing the connection as the connection state may be out of sync."); + + // test that we can get correct response after encoding failure + retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + } + } + + @Test + void testDecodeFailureForReply() { + // First, set a value using the normal connection + String testKey = "decode-failure-key"; + String testValue = "decode_failure_trigger"; + + connection.sync().set(testKey, testValue); + + // Create a codec that fails during value decoding for specific values + RedisCodec decodingFailureCodec = new RedisCodec() { + + @Override + public String decodeKey(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public String decodeValue(ByteBuffer bytes) { + String value = StandardCharsets.UTF_8.decode(bytes).toString(); + // Throw exception when decoding specific value + if ("decode_failure_trigger".equals(value)) { + throw new RuntimeException("Simulated decoding failure during value decoding"); + } + return value; + } + + @Override + public ByteBuffer encodeKey(String key) { + return StandardCharsets.UTF_8.encode(key); + } + + @Override + public ByteBuffer encodeValue(String value) { + return StandardCharsets.UTF_8.encode(value); + } + + }; + + try (StatefulRedisConnection customConnection = client.connect(decodingFailureCodec)) { + RedisCommands customRedis = customConnection.sync(); + + // Test that normal values work fine + String normalKey = "normal-decode-key"; + String normalValue = "normal-value"; + + customRedis.set(normalKey, normalValue); + String retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Now test that decoding the problematic value throws an exception + // The command was executed on Redis (the value is there), but decoding fails + assertThatThrownBy(() -> customRedis.get(testKey)) + .hasMessageContaining("Simulated decoding failure during value decoding"); + + // Verify that the connection remains usable after decode failure + retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Verify the value is actually stored in Redis using the normal connection + String valueFromNormalConnection = connection.sync().get(testKey); + assertThat(valueFromNormalConnection).isEqualTo(testValue); + } + } + + @Test + void testDecodeFailureForReplyOOM() { + // First, set a value using the normal connection + String testKey = "decode-failure-key"; + String testValue = "decode_failure_trigger"; + + connection.sync().set(testKey, testValue); + + // Create a codec that fails during value decoding for specific values + RedisCodec decodingFailureCodec = new RedisCodec() { + + @Override + public String decodeKey(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public String decodeValue(ByteBuffer bytes) { + String value = StandardCharsets.UTF_8.decode(bytes).toString(); + // Throw exception when decoding specific value + if ("decode_failure_trigger".equals(value)) { + throw new OutOfMemoryError("Simulated decoding failure during value decoding"); + } + return value; + } + + @Override + public ByteBuffer encodeKey(String key) { + return StandardCharsets.UTF_8.encode(key); + } + + @Override + public ByteBuffer encodeValue(String value) { + return StandardCharsets.UTF_8.encode(value); + } + + }; + + try (StatefulRedisConnection customConnection = client.connect(decodingFailureCodec)) { + RedisCommands customRedis = customConnection.sync(); + + // Test that normal values work fine + String normalKey = "normal-decode-key"; + String normalValue = "normal-value"; + + customRedis.set(normalKey, normalValue); + String retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Now test that decoding the problematic value throws an exception + // The command was executed on Redis (the value is there), but decoding fails + assertThatThrownBy(() -> customRedis.get(testKey)) + .hasMessageContaining("Simulated decoding failure during value decoding"); + + // Verify that the connection remains usable after decode failure + retrieved = customRedis.get(normalKey); + assertThat(retrieved).isEqualTo(normalValue); + + // Verify the value is actually stored in Redis using the normal connection + String valueFromNormalConnection = connection.sync().get(testKey); + assertThat(valueFromNormalConnection).isEqualTo(testValue); + } + } + + // Create a codec that fails during value encoding with "encoding_failure" keyword + RedisCodec failingCodec = new RedisCodec() { + + @Override + public String decodeKey(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public String decodeValue(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public ByteBuffer encodeKey(String key) { + return StandardCharsets.UTF_8.encode(key); + } + + @Override + public ByteBuffer encodeValue(String value) { + // Only throw exception for specific value to test selective encoding failure + if ("encoding_failure".equals(value)) { + throw new RuntimeException("Simulated encoding failure during value encoding"); + } + return StandardCharsets.UTF_8.encode(value); + } + + }; + + // Create a codec that fails during value encoding with "encoding_failure" keyword + RedisCodec failingCodecOOM = new RedisCodec() { + + @Override + public String decodeKey(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public String decodeValue(ByteBuffer bytes) { + return StandardCharsets.UTF_8.decode(bytes).toString(); + } + + @Override + public ByteBuffer encodeKey(String key) { + return StandardCharsets.UTF_8.encode(key); + } + + @Override + public ByteBuffer encodeValue(String value) { + // Only throw exception for specific value to test selective encoding failure + if ("encoding_failure".equals(value)) { + throw new OutOfMemoryError("JVM running out of memory during decoding"); + } + return StandardCharsets.UTF_8.encode(value); + } + + }; + +} diff --git a/src/test/java/io/lettuce/core/protocol/ReactiveStreamErrorIntegrationTests.java b/src/test/java/io/lettuce/core/protocol/ReactiveStreamErrorIntegrationTests.java new file mode 100644 index 000000000..586feedb3 --- /dev/null +++ b/src/test/java/io/lettuce/core/protocol/ReactiveStreamErrorIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2011-2025, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + */ +package io.lettuce.core.protocol; + +import static io.lettuce.TestTags.INTEGRATION_TEST; +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.SecureRandom; +import java.time.Duration; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.TestSupport; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.test.LettuceExtension; + +/** + * Integration tests simulating reactive stream errors similar to the Spring Data example. This test reproduces the issue where + * OutOfMemoryError thrown during reactive operations can cause out-of-order responses and result mismatches. + * + * @author Tihomir Mateev + * @see Lettuce OutOfOrder repo + */ +@ExtendWith(LettuceExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Tag(INTEGRATION_TEST) +class ReactiveStreamErrorIntegrationTests extends TestSupport { + + private final StatefulRedisConnection connection; + + private static final SecureRandom random = new SecureRandom(); + + private static final String KEY_TO_WORK_ON = "lettuce:session-1234-4567-7890"; + + @Inject + ReactiveStreamErrorIntegrationTests(RedisClient client, StatefulRedisConnection connection) { + this.connection = connection; + } + + @BeforeEach + void setUp() { + this.connection.sync().flushall(); + } + + @Test + void errorSimulation() throws InterruptedException { + ExecutorService executorService = Executors.newCachedThreadPool(); + AtomicInteger messageOutOfOrder = new AtomicInteger(0); + + RedisReactiveCommands reactive = connection.reactive(); + + Runnable readTask = () -> IntStream.range(0, 100).forEach(i -> { + reactive.get(KEY_TO_WORK_ON).doOnNext(result -> { + if (result.equals("OK")) { + messageOutOfOrder.getAndIncrement(); + } + }).block(Duration.ofMillis(100)); + }); + + Runnable writeTask = () -> IntStream.range(0, 100).forEach(i -> { + String id = "value:" + i; + reactive.set(KEY_TO_WORK_ON, id).doOnNext(result -> { + + if (random.nextBoolean()) { + throw new OutOfMemoryError("Simulated error"); + } + }).block(Duration.ofMillis(100)); + }); + + IntStream.range(0, 1000).forEach(i -> executorService.submit(readTask)); + IntStream.range(0, 1000).forEach(i -> executorService.submit(writeTask)); + + executorService.shutdown(); + assertThat(executorService.awaitTermination(1, TimeUnit.MINUTES)).isTrue(); + + Assertions.assertEquals(0, messageOutOfOrder.get()); + } + +} diff --git a/src/test/java/io/lettuce/core/reliability/AtLeastOnceIntegrationTests.java b/src/test/java/io/lettuce/core/reliability/AtLeastOnceIntegrationTests.java index 91720410b..b8fe4857f 100644 --- a/src/test/java/io/lettuce/core/reliability/AtLeastOnceIntegrationTests.java +++ b/src/test/java/io/lettuce/core/reliability/AtLeastOnceIntegrationTests.java @@ -174,7 +174,7 @@ public void encode(ByteBuf buf) { assertThat(verificationConnection.get(key)).isEqualTo("2"); - assertThat(ConnectionTestUtil.getStack(connection.getStatefulConnection())).isNotEmpty(); + assertThat(connection.get(key)).isEqualTo("2"); connection.getStatefulConnection().close(); } diff --git a/src/test/java/io/lettuce/core/reliability/AtMostOnceIntegrationTests.java b/src/test/java/io/lettuce/core/reliability/AtMostOnceIntegrationTests.java index f2874d5c6..413156440 100644 --- a/src/test/java/io/lettuce/core/reliability/AtMostOnceIntegrationTests.java +++ b/src/test/java/io/lettuce/core/reliability/AtMostOnceIntegrationTests.java @@ -172,16 +172,8 @@ public void encode(ByteBuf buf) { assertThat(command.isCancelled()).isFalse(); assertThat(getException(command)).isInstanceOf(EncoderException.class); - Wait.untilTrue(() -> !ConnectionTestUtil.getStack(connection).isEmpty()).waitOrTimeout(); - - assertThat(ConnectionTestUtil.getStack(connection)).isNotEmpty(); - ConnectionTestUtil.getStack(connection).clear(); - assertThat(sync.get(key)).isEqualTo("2"); - assertThat(ConnectionTestUtil.getStack(connection)).isEmpty(); - assertThat(ConnectionTestUtil.getCommandBuffer(connection)).isEmpty(); - connection.close(); } diff --git a/src/test/java/io/lettuce/core/search/RediSearchAggregateIntegrationTests.java b/src/test/java/io/lettuce/core/search/RediSearchAggregateIntegrationTests.java index 2511d7fd5..a937f888a 100644 --- a/src/test/java/io/lettuce/core/search/RediSearchAggregateIntegrationTests.java +++ b/src/test/java/io/lettuce/core/search/RediSearchAggregateIntegrationTests.java @@ -28,6 +28,9 @@ import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.json.JsonObject; +import io.lettuce.core.json.JsonParser; +import io.lettuce.core.json.JsonPath; import io.lettuce.core.search.arguments.AggregateArgs; import io.lettuce.core.search.arguments.AggregateArgs.GroupBy; import io.lettuce.core.search.arguments.AggregateArgs.Reducer; @@ -35,6 +38,7 @@ import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.FieldArgs; import io.lettuce.core.search.arguments.NumericFieldArgs; +import io.lettuce.core.search.arguments.QueryDialects; import io.lettuce.core.search.arguments.TagFieldArgs; import io.lettuce.core.search.arguments.TextFieldArgs; @@ -110,9 +114,12 @@ void shouldPerformBasicAggregation() { // If documents are indexed, we should have 1 aggregation group (no grouping) // If no documents, we should have 0 aggregation groups if (searchResult.getCount() > 0) { - assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 1 aggregation group (no grouping) - assertThat(result.getReplies()).hasSize(1); // Should have 1 SearchReply containing all documents - assertThat(result.getReplies().get(0).getResults()).hasSize(4); // Should have 4 documents in the single reply + assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 1 aggregation group (no + // grouping) + assertThat(result.getReplies()).hasSize(1); // Should have 1 SearchReply containing all + // documents + assertThat(result.getReplies().get(0).getResults()).hasSize(4); // Should have 4 documents in + // the single reply // Each result should be empty since no LOAD was specified for (SearchReply.SearchResult aggregateResult : result.getReplies().get(0).getResults()) { @@ -247,7 +254,8 @@ void shouldPerformAggregationWithLoadAll() { assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 1 aggregation group (no grouping) assertThat(result.getReplies()).hasSize(1); // Should have 1 SearchReply containing all documents SearchReply searchReply = result.getReplies().get(0); - assertThat(searchReply.getResults()).hasSize(2); // Should have 2 documents (only doc:1 and doc:2 added in this test) + assertThat(searchReply.getResults()).hasSize(2); // Should have 2 documents (only doc:1 and doc:2 added + // in this test) // Check that all fields are loaded for (SearchReply.SearchResult aggregateResult : searchReply.getResults()) { @@ -274,8 +282,10 @@ void shouldHandleEmptyResults() { AggregationReply result = redis.ftAggregate("empty-test-idx", "*"); assertThat(result).isNotNull(); - assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 0 aggregation groups for empty index - assertThat(result.getReplies().get(0).getResults()).isEmpty(); // Should have no SearchReply objects for empty results + assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 0 aggregation groups for empty + // index + assertThat(result.getReplies().get(0).getResults()).isEmpty(); // Should have no SearchReply objects for + // empty results assertThat(redis.ftDropindex("empty-test-idx")).isEqualTo("OK"); } @@ -983,7 +993,8 @@ void shouldPerformAggregationWithLimit() { // Verify we got the correct subset - let's check what we actually get List> results = searchReply.getResults(); - // The results should be sorted in descending order and limited to 3 items starting from offset 2 + // The results should be sorted in descending order and limited to 3 items + // starting from offset 2 // So we should get items with scores: 80, 70, 60 (3rd, 4th, 5th highest) // But let's verify what we actually get and adjust accordingly assertThat(results.get(0).getFields().get("score")).isIn("80", "70"); // Could be 3rd or 4th highest @@ -1131,7 +1142,8 @@ void shouldPerformAggregationWithCursorAndCount() { SearchReply nextSearchReply = nextResult.getReplies().get(0); assertThat(nextSearchReply.getResults()).hasSize(5); // Should return 5 results as specified assertThat(nextResult.getCursor()).isPresent(); - assertThat(nextResult.getCursor().get().getCursorId()).isNotEqualTo(0L); // Should still have more results + assertThat(nextResult.getCursor().get().getCursorId()).isNotEqualTo(0L); // Should still have more + // results // Read final page AggregationReply finalResult = redis.ftCursorread("cursor-count-test-idx", @@ -1349,7 +1361,8 @@ NumericFieldArgs. builder().name("price").build(), assertThat(nextResult.getReplies()).hasSize(1); // Should have 1 SearchReply SearchReply nextSearchReply = nextResult.getReplies().get(0); assertThat(nextSearchReply.getResults()).hasSize(1); // Should return second group - // RediSearch may either omit the cursor on the final page, or return a non-zero cursor + // RediSearch may either omit the cursor on the final page, or return a non-zero + // cursor // that requires one more empty READ to return 0. Be tolerant across versions. long effective = nextResult.getCursor().map(AggregationReply.Cursor::getCursorId).orElse(0L); if (effective != 0L) { @@ -1386,8 +1399,10 @@ void shouldHandleEmptyResultsWithCursor() { AggregationReply result = redis.ftAggregate("cursor-empty-test-idx", "*", args); assertThat(result).isNotNull(); - assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 0 aggregation groups for empty index - assertThat(result.getReplies().get(0).getResults()).isEmpty(); // Should have no SearchReply objects for empty results + assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 0 aggregation groups for empty + // index + assertThat(result.getReplies().get(0).getResults()).isEmpty(); // Should have no SearchReply objects for + // empty results assertThat(result.getCursor()).isPresent(); assertThat(result.getCursor().get().getCursorId()).isEqualTo(0L); // Should indicate no more results @@ -1495,14 +1510,16 @@ NumericFieldArgs. builder().name("experience").sortable().build(), assertThat(multiGroupResult.getReplies()).hasSize(1); SearchReply multiGroupReply = multiGroupResult.getReplies().get(0); - // Should have 4 groups: Engineering-Senior, Engineering-Junior, Marketing-Senior, Marketing-Junior + // Should have 4 groups: Engineering-Senior, Engineering-Junior, + // Marketing-Senior, Marketing-Junior assertThat(multiGroupReply.getResults()).hasSize(4); // Verify each group has the expected fields for (SearchReply.SearchResult group : multiGroupReply.getResults()) { assertThat(group.getFields()).containsKeys("department", "role", "count", "avg_salary", "avg_performance"); - // Verify department and role combinations are valid (Redis may normalize to lowercase) + // Verify department and role combinations are valid (Redis may normalize to + // lowercase) String dept = group.getFields().get("department"); String role = group.getFields().get("role"); assertThat(dept.toLowerCase()).isIn("engineering", "marketing"); @@ -1514,7 +1531,8 @@ NumericFieldArgs. builder().name("experience").sortable().build(), @Test void shouldPerformAggregationWithSortByAndMaxOptimization() { - // Create an index with sortable numeric fields for testing sorting functionality + // Create an index with sortable numeric fields for testing sorting + // functionality List> fields = Arrays.asList(TextFieldArgs. builder().name("product_name").build(), TextFieldArgs. builder().name("category").sortable().build(), NumericFieldArgs. builder().name("price").sortable().build(), @@ -1536,7 +1554,8 @@ NumericFieldArgs. builder().name("rating").sortable().build(), // Test 1: Basic sorting (should return results in correct order) AggregateArgs basicSortArgs = AggregateArgs. builder().loadAll() - .sortBy(AggregateArgs.SortBy.of("price", SortDirection.ASC)).limit(0, 5) // Only get top 5 results + .sortBy(AggregateArgs.SortBy.of("price", SortDirection.ASC)).limit(0, 5) // Only get top + // 5 results .build(); AggregationReply basicSortResult = redis.ftAggregate("sortby-max-test-idx", "*", basicSortArgs); @@ -1553,7 +1572,8 @@ NumericFieldArgs. builder().name("rating").sortable().build(), // Check that we have the expected number of results assertThat(sortedResults).hasSize(5); - // Verify sorting: first result should have highest price, last should have lowest + // Verify sorting: first result should have highest price, last should have + // lowest double firstPrice = Double.parseDouble(sortedResults.get(0).getFields().get("price")); double lastPrice = Double.parseDouble(sortedResults.get(sortedResults.size() - 1).getFields().get("price")); assertThat(firstPrice).isLessThanOrEqualTo(lastPrice); @@ -1611,7 +1631,9 @@ NumericFieldArgs. builder().name("units_sold").sortable().build(), salesRecord.put("product_type", productType); salesRecord.put("revenue", String.valueOf(1000 + recordId * 100)); salesRecord.put("units_sold", String.valueOf(50 + recordId * 5)); - salesRecord.put("profit_margin", String.valueOf(0.15 + (recordId % 3) * 0.05)); // 0.15, 0.20, 0.25 + salesRecord.put("profit_margin", String.valueOf(0.15 + (recordId % 3) * 0.05)); // 0.15, + // 0.20, + // 0.25 assertThat(redis.hmset("sales:" + recordId, salesRecord)).isEqualTo("OK"); recordId++; } @@ -1801,9 +1823,11 @@ NumericFieldArgs. builder().name("quantity").sortable().build(), // This specific order: APPLY -> FILTER -> GROUPBY -> LIMIT -> SORTBY // should work correctly and produce meaningful results AggregateArgs args = AggregateArgs. builder().load("title").load("price") - .load("quantity").load("category").apply("@price * @quantity", "total_value") // Calculate total + .load("quantity").load("category").apply("@price * @quantity", "total_value") // Calculate + // total // value first - .filter("@total_value > 550") // Filter by total value (should keep only products 1 and 2, both electronics) + .filter("@total_value > 550") // Filter by total value (should keep only products 1 and + // 2, both electronics) .groupBy(GroupBy. of("category").reduce(Reducer. count().as("product_count")) .reduce(Reducer. sum("@total_value").as("category_total"))) .limit(0, 10) // Limit results @@ -1816,7 +1840,8 @@ NumericFieldArgs. builder().name("quantity").sortable().build(), assertThat(result.getReplies()).hasSize(1); SearchReply searchReply = result.getReplies().get(0); - // Should have only electronics category since books total_value (50*10=500) < 550 + // Should have only electronics category since books total_value (50*10=500) < + // 550 // but electronics products (100*5=500, 200*3=600) both > 550 assertThat(searchReply.getResults()).hasSize(1); @@ -1910,7 +1935,8 @@ NumericFieldArgs. builder().name("rating").sortable().build(), @Test void shouldSupportMultipleRepeatedOperations() { // Test that operations can be repeated multiple times in the pipeline - // This demonstrates the re-entrant nature where each operation can appear multiple times + // This demonstrates the re-entrant nature where each operation can appear + // multiple times List> fields = Arrays.asList(TextFieldArgs. builder().name("employee_name").build(), TagFieldArgs. builder().name("department").sortable().build(), @@ -1942,7 +1968,8 @@ NumericFieldArgs. builder().name("experience").sortable().build(), } // Pipeline with repeated operations demonstrating re-entrant nature: - // Multiple APPLY operations, multiple FILTER operations, multiple GROUPBY operations + // Multiple APPLY operations, multiple FILTER operations, multiple GROUPBY + // operations AggregateArgs repeatedOpsArgs = AggregateArgs. builder().load("department") .load("level").load("salary").load("experience").load("performance_score") // First APPLY: Calculate salary per experience year @@ -2009,7 +2036,8 @@ NumericFieldArgs. builder().name("quantity").sortable().build(), assertThat(redis.ftCreate("interleaved-ops-idx", fields)).isEqualTo("OK"); - // Add transaction data representing different customer segments, regions, and categories + // Add transaction data representing different customer segments, regions, and + // categories String[][] transactions = { { "txn:1", "T001", "premium", "electronics", "north", "1500", "2", "5" }, { "txn:2", "T002", "premium", "electronics", "south", "1200", "1", "10" }, { "txn:3", "T003", "standard", "electronics", "north", "800", "3", "0" }, @@ -2046,7 +2074,8 @@ NumericFieldArgs. builder().name("quantity").sortable().build(), // Apply transformation to calculate revenue per transaction .apply("@segment_revenue / @segment_transactions", "revenue_per_transaction") // Sort by group size (segment_transactions) and limit to top results - .sortBy("segment_transactions", SortDirection.DESC).limit(0, 10) // Top 10 segments by transaction count + .sortBy("segment_transactions", SortDirection.DESC).limit(0, 10) // Top 10 segments by + // transaction count // Filter segments with significant revenue .filter("@segment_revenue > 500") // Apply value score calculation @@ -2085,7 +2114,8 @@ NumericFieldArgs. builder().name("quantity").sortable().build(), @Test void shouldSupportPipelineWithMultipleFiltersAndSorts() { // Test pipeline with multiple FILTER and SORTBY operations at different stages - // This demonstrates that operations can be repeated and applied at various pipeline stages + // This demonstrates that operations can be repeated and applied at various + // pipeline stages List> fields = Arrays.asList(TextFieldArgs. builder().name("product_id").build(), TagFieldArgs. builder().name("category").sortable().build(), @@ -2188,7 +2218,8 @@ NumericFieldArgs. builder().name("rating").sortable().build(), @Test void shouldSupportAdvancedDynamicPipelineWithConditionalLogic() { // Test the most advanced scenario: dynamic pipeline with conditional logic, - // multiple re-entrant operations, and complex transformations that build upon each other + // multiple re-entrant operations, and complex transformations that build upon + // each other // This represents a real-world business intelligence scenario List> fields = Arrays.asList(TextFieldArgs. builder().name("order_id").build(), @@ -2229,7 +2260,8 @@ NumericFieldArgs. builder().name("shipping_cost").sortable().build(), assertThat(redis.hmset(order[0], doc)).isEqualTo("OK"); } - // Advanced dynamic pipeline with conditional logic and multiple re-entrant operations: + // Advanced dynamic pipeline with conditional logic and multiple re-entrant + // operations: AggregateArgs advancedArgs = AggregateArgs. builder().load("customer_type") .load("product_line").load("sales_channel").load("season").load("order_value").load("cost") .load("shipping_cost").load("customer_satisfaction") @@ -2321,4 +2353,116 @@ NumericFieldArgs. builder().name("shipping_cost").sortable().build(), } } + @Test + void shouldPerformAggregationOnJson() { + // Create an index + List> fields = Arrays.asList(TextFieldArgs. builder().name("$.country").as("country").build(), + TextFieldArgs. builder().name("$.city").as("city").build(), + TextFieldArgs. builder().name("$.office").as("office").build(), + TextFieldArgs. builder().name("$.code").as("code").build()); + CreateArgs args = CreateArgs. builder().on(CreateArgs.TargetType.JSON) + .withPrefix("doc:").build(); + + assertThat(redis.ftCreate("args-test-idx", args, fields)).isEqualTo("OK"); + + JsonParser parser = redis.getJsonParser(); + + // Add some test documents + JsonObject doc1 = parser.createJsonObject(); + doc1.put("country", parser.createJsonValue("\"SE\"")); + doc1.put("city", parser.createJsonValue("\"Stockholm\"")); + doc1.put("office", parser.createJsonValue("\"HQ\"")); + doc1.put("code", parser.createJsonValue("\"S1\"")); + assertThat(redis.jsonSet("doc:1", JsonPath.ROOT_PATH, doc1)).isEqualTo("OK"); + + JsonObject doc2 = parser.createJsonObject(); + doc2.put("country", parser.createJsonValue("\"FI\"")); + doc2.put("city", parser.createJsonValue("\"Åbo\"")); + doc2.put("office", parser.createJsonValue("\"Office2\"")); + doc2.put("code", parser.createJsonValue("\"S2\"")); + assertThat(redis.jsonSet("doc:2", JsonPath.ROOT_PATH, doc2)).isEqualTo("OK"); + + // Perform aggregation with arguments - LOAD fields + AggregateArgs.GroupBy groupBy = AggregateArgs.GroupBy + . of("country", "city", "office", "code") + .reduce(AggregateArgs.Reducer. count().as("__count")); + + AggregateArgs aggargs = AggregateArgs. builder().loadAll().groupBy(groupBy) + .dialect(QueryDialects.DIALECT2).build(); + + AggregationReply result = redis.ftAggregate("args-test-idx", "*", aggargs); + + assertThat(result).isNotNull(); + assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 1 aggregation group (no grouping) + assertThat(result.getReplies()).hasSize(1); // Should have 1 SearchReply containing all documents + SearchReply searchReply = result.getReplies().get(0); + assertThat(searchReply.getResults()).hasSize(2); // Should have 2 documents (doc:1, doc:2) + + // Check that loaded fields are present in results + for (SearchReply.SearchResult aggregateResult : searchReply.getResults()) { + assertThat(aggregateResult.getFields().containsKey("country")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("city")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("office")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("code")).isTrue(); + assertThat(aggregateResult.getFields().get("country")).isNotNull(); + assertThat(aggregateResult.getFields().get("city")).isNotNull(); + assertThat(aggregateResult.getFields().get("office")).isNotNull(); + assertThat(aggregateResult.getFields().get("code")).isNotNull(); + } + + assertThat(redis.ftDropindex("args-test-idx")).isEqualTo("OK"); + } + + @Test + void shouldPerformAggregationOnJsonWithNulls() { + // Create an index + List> fields = Arrays.asList(TextFieldArgs. builder().name("$.country").as("country").build(), + TextFieldArgs. builder().name("$.city").as("city").build(), + TextFieldArgs. builder().name("$.office").as("office").build(), + TextFieldArgs. builder().name("$.code").as("code").build()); + CreateArgs args = CreateArgs. builder().on(CreateArgs.TargetType.JSON) + .withPrefix("doc:").build(); + + assertThat(redis.ftCreate("args-test-idx", args, fields)).isEqualTo("OK"); + + JsonParser parser = redis.getJsonParser(); + + // Add some test documents + JsonObject doc1 = parser.createJsonObject(); + doc1.put("country", parser.createJsonValue("\"SE\"")); + doc1.put("city", parser.createJsonValue("null")); + doc1.put("office", parser.createJsonValue("\"HQ\"")); + doc1.put("code", parser.createJsonValue("\"S1\"")); + assertThat(redis.jsonSet("doc:1", JsonPath.ROOT_PATH, doc1)).isEqualTo("OK"); + + // Perform aggregation with arguments - LOAD fields + AggregateArgs.GroupBy groupBy = AggregateArgs.GroupBy + . of("country", "city", "office", "code") + .reduce(AggregateArgs.Reducer. count().as("__count")); + + AggregateArgs aggArgs = AggregateArgs. builder().loadAll().groupBy(groupBy) + .dialect(QueryDialects.DIALECT2).build(); + + AggregationReply result = redis.ftAggregate("args-test-idx", "*", aggArgs); + + assertThat(result).isNotNull(); + assertThat(result.getAggregationGroups()).isEqualTo(1); // Should have 1 aggregation group (no grouping) + assertThat(result.getReplies()).hasSize(1); // Should have 1 SearchReply containing all documents + SearchReply searchReply = result.getReplies().get(0); + assertThat(searchReply.getResults()).hasSize(1); // Should have 1 documents (doc:1) + + // Check that loaded fields are present in results + SearchReply.SearchResult aggregateResult = searchReply.getResults().get(0); + assertThat(aggregateResult.getFields().containsKey("country")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("city")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("office")).isTrue(); + assertThat(aggregateResult.getFields().containsKey("code")).isTrue(); + assertThat(aggregateResult.getFields().get("country")).isEqualTo("SE"); + assertThat(aggregateResult.getFields().get("city")).isNull(); + assertThat(aggregateResult.getFields().get("office")).isEqualTo("HQ"); + assertThat(aggregateResult.getFields().get("code")).isEqualTo("S1"); + + assertThat(redis.ftDropindex("args-test-idx")).isEqualTo("OK"); + } + } diff --git a/src/test/java/io/lettuce/core/search/RediSearchIntegrationTests.java b/src/test/java/io/lettuce/core/search/RediSearchIntegrationTests.java index e6272e873..4c06d0bee 100644 --- a/src/test/java/io/lettuce/core/search/RediSearchIntegrationTests.java +++ b/src/test/java/io/lettuce/core/search/RediSearchIntegrationTests.java @@ -12,12 +12,18 @@ import io.lettuce.core.RedisCommandExecutionException; import io.lettuce.core.RedisURI; import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import io.lettuce.core.search.arguments.CombineArgs; import io.lettuce.core.search.arguments.CreateArgs; +import io.lettuce.core.search.arguments.ExplainArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; +import io.lettuce.core.search.arguments.HybridSearchArgs; +import io.lettuce.core.search.arguments.HybridVectorArgs; import io.lettuce.core.search.arguments.NumericFieldArgs; -import io.lettuce.core.search.arguments.ExplainArgs; - +import io.lettuce.core.search.arguments.PostProcessingArgs; import io.lettuce.core.search.arguments.QueryDialects; +import io.lettuce.core.search.arguments.ScoringFunction; import io.lettuce.core.search.arguments.SearchArgs; import io.lettuce.core.search.arguments.SortByArgs; import io.lettuce.core.search.arguments.SpellCheckArgs; @@ -26,11 +32,15 @@ import io.lettuce.core.search.arguments.SynUpdateArgs; import io.lettuce.core.search.arguments.TagFieldArgs; import io.lettuce.core.search.arguments.TextFieldArgs; +import io.lettuce.core.search.arguments.VectorFieldArgs; +import io.lettuce.test.condition.EnabledOnCommand; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.time.Duration; import java.util.Arrays; import java.util.Collections; @@ -76,11 +86,14 @@ public class RediSearchIntegrationTests { protected static RedisCommands redis; + protected static RedisCommands redisBinary; + public RediSearchIntegrationTests() { RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build(); client = RedisClient.create(redisURI); client.setOptions(getOptions()); redis = client.connect().sync(); + redisBinary = client.connect(ByteArrayCodec.INSTANCE).sync(); } protected ClientOptions getOptions() { @@ -1037,6 +1050,90 @@ void testFtListCommand() { assertThat(finalIndexes.size()).isEqualTo(initialIndexes.size()); } + /** + * Test field aliases in RETURN clause to rename fields in search results. + */ + @Test + void testSearchWithFieldAliases() { + String testIndex = "alias-field-idx"; + + // Create index with multiple fields + FieldArgs titleField = TextFieldArgs. builder().name("title").build(); + FieldArgs authorField = TextFieldArgs. builder().name("author").build(); + FieldArgs priceField = NumericFieldArgs. builder().name("price").build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix("book:") + .on(CreateArgs.TargetType.HASH).build(); + + assertThat(redis.ftCreate(testIndex, createArgs, Arrays.asList(titleField, authorField, priceField))).isEqualTo("OK"); + + // Add sample books + Map book1 = new HashMap<>(); + book1.put("title", "Redis in Action"); + book1.put("author", "Josiah Carlson"); + book1.put("price", "39.99"); + redis.hmset("book:1", book1); + + Map book2 = new HashMap<>(); + book2.put("title", "Redis Essentials"); + book2.put("author", "Maxwell Dayvson"); + book2.put("price", "29.99"); + redis.hmset("book:2", book2); + + // Test 1: Search with field alias - rename single field + SearchArgs aliasArgs = SearchArgs. builder().returnField("title", "book_title").build(); + SearchReply results = redis.ftSearch(testIndex, "Redis", aliasArgs); + + assertThat(results.getCount()).isEqualTo(2); + assertThat(results.getResults()).hasSize(2); + + // Verify that the field is returned with the alias name + for (SearchReply.SearchResult result : results.getResults()) { + assertThat(result.getFields()).containsKey("book_title"); + assertThat(result.getFields()).doesNotContainKey("title"); + assertThat(result.getFields().get("book_title")).contains("Redis"); + } + + // Test 2: Search with multiple field aliases + SearchArgs multiAliasArgs = SearchArgs. builder().returnField("title", "book_title") + .returnField("author", "writer").returnField("price", "cost").build(); + results = redis.ftSearch(testIndex, "Redis", multiAliasArgs); + + assertThat(results.getCount()).isEqualTo(2); + for (SearchReply.SearchResult result : results.getResults()) { + // Verify aliased fields are present + assertThat(result.getFields()).containsKey("book_title"); + assertThat(result.getFields()).containsKey("writer"); + assertThat(result.getFields()).containsKey("cost"); + + // Verify original field names are not present + assertThat(result.getFields()).doesNotContainKey("title"); + assertThat(result.getFields()).doesNotContainKey("author"); + assertThat(result.getFields()).doesNotContainKey("price"); + } + + // Test 3: Mix of aliased and non-aliased fields + SearchArgs mixedArgs = SearchArgs. builder().returnField("title", "book_title") + .returnField("author").build(); + results = redis.ftSearch(testIndex, "Redis", mixedArgs); + + assertThat(results.getCount()).isEqualTo(2); + for (SearchReply.SearchResult result : results.getResults()) { + // Verify aliased field + assertThat(result.getFields()).containsKey("book_title"); + assertThat(result.getFields()).doesNotContainKey("title"); + + // Verify non-aliased field + assertThat(result.getFields()).containsKey("author"); + + // Verify price is not returned + assertThat(result.getFields()).doesNotContainKey("price"); + } + + // Cleanup + assertThat(redis.ftDropindex(testIndex)).isEqualTo("OK"); + } + /** * Test FT.SYNDUMP and FT.SYNUPDATE commands for synonym management. */ @@ -1103,4 +1200,131 @@ void testFtSynonymCommands() { assertThat(redis.ftDropindex(testIndex)).isEqualTo("OK"); } + @Test + @EnabledOnCommand("FT.HYBRID") + void ftHybridAdvancedMultiQueryWithPostProcessing() { + String indexName = "idx:ecommerce"; + + FieldArgs titleField = TextFieldArgs. builder().name("title").build(); + FieldArgs categoryField = TagFieldArgs. builder().name("category").build(); + FieldArgs brandField = TagFieldArgs. builder().name("brand").build(); + FieldArgs priceField = NumericFieldArgs. builder().name("price").build(); + FieldArgs ratingField = NumericFieldArgs. builder().name("rating").build(); + + FieldArgs vectorField = VectorFieldArgs. builder().name("image_embedding").hnsw() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(10).distanceMetric(VectorFieldArgs.DistanceMetric.COSINE) + .build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix("product:") + .on(CreateArgs.TargetType.HASH).build(); + + assertThat(redis.ftCreate(indexName, createArgs, + Arrays.asList(titleField, categoryField, brandField, priceField, ratingField, vectorField))).isEqualTo("OK"); + + // Add sample products + createProduct("1", "Apple iPhone 15 Pro smartphone with advanced camera", "electronics", "apple", "999", "4.8", + new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f }); + + createProduct("2", "Samsung Galaxy S24 smartphone camera", "electronics", "samsung", "799", "4.6", + new float[] { 0.15f, 0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.9f }); + + createProduct("3", "Google Pixel 8 Pro camera smartphone", "electronics", "google", "699", "4.5", + new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 0.8f }); + + createProduct("4", "Apple iPhone 15 Pro smartphone camera", "electronics", "apple", "999", "4.8", + new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f }); + + createProduct("5", "Samsung Galaxy S24", "electronics", "samsung", "799", "4.6", + new float[] { 0.15f, 0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.9f }); + + createProduct("6", "Google Pixel 8 Pro", "electronics", "google", "699", "4.5", + new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 0.8f }); + + createProduct("7", "Best T-shirt", "apparel", "denim", "255", "4.2", + new float[] { 0.12f, 0.22f, 0.32f, 0.42f, 0.52f, 0.62f, 0.72f, 0.82f, 0.92f, 0.85f }); + + createProduct("8", "Best makeup", "beauty", "loreal", "155", "4.4", + new float[] { 0.18f, 0.28f, 0.38f, 0.48f, 0.58f, 0.68f, 0.78f, 0.88f, 0.98f, 0.75f }); + + createProduct("9", "Best punching bag", "sports", "lonsdale", "733", "4.6", + new float[] { 0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f, 0.91f, 0.95f }); + + createProduct("10", "Apple iPhone 15 Pro smartphone camera", "electronics", "apple", "999", "4.8", + new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f }); + + byte[] queryVector = floatArrayToByteArray(new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f }); + + HybridArgs hybridArgs = HybridArgs. builder() + .search(HybridSearchArgs. builder().query("@category:{electronics} smartphone camera") + .scorer(HybridSearchArgs.Scorer.of(ScoringFunction.BM25)).scoreAlias("text_score").build()) + .vectorSearch(HybridVectorArgs. builder().field("@image_embedding").vector(queryVector) + .method(HybridVectorArgs.Knn.of(20).efRuntime(150)).filter("@brand:{apple|samsung|google}") + .scoreAlias("vector_score").build()) + .combine(CombineArgs.of(new CombineArgs.Linear().alpha(0.7).beta(0.3))) + .postProcessing(PostProcessingArgs. builder().load("@price", "@brand", "@category") + .addOperation(PostProcessingArgs.GroupBy. of("@brand") + .reduce(PostProcessingArgs.Reducer + . of(PostProcessingArgs.ReduceFunction.SUM, "@price").as("sum")) + .reduce(PostProcessingArgs.Reducer. of(PostProcessingArgs.ReduceFunction.COUNT) + .as("count"))) + .addOperation(PostProcessingArgs.SortBy.of( + new PostProcessingArgs.SortProperty<>("@sum", PostProcessingArgs.SortDirection.ASC), + new PostProcessingArgs.SortProperty<>("@count", PostProcessingArgs.SortDirection.DESC))) + .addOperation(PostProcessingArgs.Apply.of("@sum * 0.9", "discounted_price")) + .addOperation(PostProcessingArgs.Filter.of("@sum > 700")) + .addOperation(PostProcessingArgs.Limit.of(0, 20)).build()) + .param("discount_rate", "0.9").param("$vector", new String(queryVector)).build(); + + HybridReply reply = redis.ftHybrid(indexName, hybridArgs); + + // Verify results + assertThat(reply).isNotNull(); + assertThat(reply.getResults()).isNotEmpty(); + assertThat(reply.getTotalResults()).isEqualTo(3); + assertThat(reply.getResults()).isNotEmpty(); + assertThat(reply.getWarnings().size()).isGreaterThanOrEqualTo(0); + assertThat(reply.getExecutionTime()).isGreaterThan(0L); + + // Verify first result (google) + Map r1 = reply.getResults().get(0).getFields(); + assertThat(r1.get("brand")).isEqualTo("google"); + assertThat(r1.get("count")).isEqualTo("2"); + assertThat(r1.get("sum")).isEqualTo("1398"); + assertThat(r1.get("discounted_price")).isEqualTo("1258.2"); + + // Verify second result (samsung) + Map r2 = reply.getResults().get(1).getFields(); + assertThat(r2.get("brand")).isEqualTo("samsung"); + assertThat(r2.get("count")).isEqualTo("2"); + assertThat(r2.get("sum")).isEqualTo("1598"); + assertThat(r2.get("discounted_price")).isEqualTo("1438.2"); + + // Verify third result (apple) + Map r3 = reply.getResults().get(2).getFields(); + assertThat(r3.get("brand")).isEqualTo("apple"); + assertThat(r3.get("count")).isEqualTo("3"); + assertThat(r3.get("sum")).isEqualTo("2997"); + assertThat(r3.get("discounted_price")).isEqualTo("2697.3"); + + redis.ftDropindex(indexName); + } + + private void createProduct(String id, String title, String category, String brand, String price, String rating, + float[] embedding) { + redis.hset("product:" + id, "title", title); + redis.hset("product:" + id, "category", category); + redis.hset("product:" + id, "brand", brand); + redis.hset("product:" + id, "price", price); + redis.hset("product:" + id, "rating", rating); + redisBinary.hset(("product:" + id).getBytes(), "image_embedding".getBytes(), floatArrayToByteArray(embedding)); + } + + private byte[] floatArrayToByteArray(float[] vector) { + ByteBuffer buffer = ByteBuffer.allocate(vector.length * 4).order(ByteOrder.LITTLE_ENDIAN); + for (float value : vector) { + buffer.putFloat(value); + } + return buffer.array(); + } + } diff --git a/src/test/java/io/lettuce/core/search/RediSearchKeylessRoutingIntegrationTests.java b/src/test/java/io/lettuce/core/search/RediSearchKeylessRoutingIntegrationTests.java index f16da0bee..56fc225f6 100644 --- a/src/test/java/io/lettuce/core/search/RediSearchKeylessRoutingIntegrationTests.java +++ b/src/test/java/io/lettuce/core/search/RediSearchKeylessRoutingIntegrationTests.java @@ -13,18 +13,25 @@ import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; import io.lettuce.core.cluster.models.partitions.Partitions; +import io.lettuce.core.codec.ByteArrayCodec; import io.lettuce.core.cluster.models.partitions.RedisClusterNode; import io.lettuce.core.search.arguments.AggregateArgs; import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.FieldArgs; +import io.lettuce.core.search.arguments.HybridArgs; +import io.lettuce.core.search.arguments.HybridSearchArgs; +import io.lettuce.core.search.arguments.HybridVectorArgs; import io.lettuce.core.search.arguments.NumericFieldArgs; import io.lettuce.core.search.arguments.TagFieldArgs; import io.lettuce.core.search.arguments.TextFieldArgs; +import io.lettuce.core.search.arguments.VectorFieldArgs; import io.lettuce.test.LettuceExtension; import io.lettuce.core.metrics.CommandLatencyId; import io.lettuce.core.metrics.CommandMetrics; +import io.lettuce.test.condition.EnabledOnCommand; import io.lettuce.test.condition.RedisConditions; +import java.nio.ByteBuffer; import java.util.*; import javax.inject.Inject; import org.junit.jupiter.api.*; @@ -52,10 +59,16 @@ public class RediSearchKeylessRoutingIntegrationTests extends TestSupport { private static final String PREFIX = "book:keyless:routing:"; + private static final String HYBRID_INDEX = "hybrid-keyless-routing-idx"; + + private static final String HYBRID_PREFIX = "product:hybrid:routing:"; + private final RedisClusterClient clusterClient; private StatefulRedisClusterConnection connection; + private StatefulRedisClusterConnection binaryConnection; + private RedisAdvancedClusterAsyncCommands async; @Inject @@ -66,6 +79,7 @@ public class RediSearchKeylessRoutingIntegrationTests extends TestSupport { @BeforeEach void open() { connection = clusterClient.connect(); + binaryConnection = clusterClient.connect(ByteArrayCodec.INSTANCE); async = connection.async(); } @@ -74,6 +88,9 @@ void close() { if (connection != null) { connection.close(); } + if (binaryConnection != null) { + binaryConnection.close(); + } } @BeforeEach @@ -82,7 +99,7 @@ void setUp() { assumeTrue(RedisConditions.of(connection.sync()).hasVersionGreaterOrEqualsTo("8.0")); connection.sync().flushall(); - // Schema + // Schema for text search tests FieldArgs title = TextFieldArgs. builder().name("title").build(); FieldArgs author = TagFieldArgs. builder().name("author").build(); FieldArgs year = NumericFieldArgs. builder().name("year").sortable().build(); @@ -113,6 +130,10 @@ void tearDown() { connection.sync().ftDropindex(INDEX); } catch (Exception ignore) { } + try { + connection.sync().ftDropindex(HYBRID_INDEX); + } catch (Exception ignore) { + } connection.sync().flushall(); } @@ -327,4 +348,96 @@ private void clearLatencyMetrics() { } } + private byte[] floatArrayToByteArray(float[] floats) { + ByteBuffer buffer = ByteBuffer.allocate(floats.length * 4); + for (float f : floats) { + buffer.putFloat(f); + } + return buffer.array(); + } + + private HybridArgs hybridArgs() { + float[] queryVector = { 0.15f, 0.25f, 0.35f, 0.45f }; + return HybridArgs. builder() + .search(HybridSearchArgs. builder().query("@category:{electronics}").build()) + .vectorSearch(HybridVectorArgs. builder().field("@embedding") + .vector(floatArrayToByteArray(queryVector)).method(HybridVectorArgs.Knn.of(5)).build()) + .build(); + } + + private void prepareHybrid() { + // Schema for hybrid search tests + FieldArgs category = TagFieldArgs. builder().name("category").build(); + FieldArgs price = NumericFieldArgs. builder().name("price").sortable().build(); + FieldArgs embedding = VectorFieldArgs. builder().name("embedding").hnsw() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(4).distanceMetric(VectorFieldArgs.DistanceMetric.COSINE) + .build(); + + CreateArgs hybridCreateArgs = CreateArgs. builder().withPrefix(HYBRID_PREFIX) + .on(CreateArgs.TargetType.HASH).build(); + assertThat(connection.sync().ftCreate(HYBRID_INDEX, hybridCreateArgs, Arrays.asList(category, price, embedding))) + .isEqualTo("OK"); + + // Add hybrid search data + String[] categories = { "electronics", "clothing", "electronics", "clothing" }; + String[] prices = { "29.99", "49.99", "39.99", "59.99" }; + float[][] vectors = { { 0.1f, 0.2f, 0.3f, 0.4f }, { 0.5f, 0.6f, 0.7f, 0.8f }, { 0.2f, 0.3f, 0.4f, 0.5f }, + { 0.6f, 0.7f, 0.8f, 0.9f } }; + + for (int i = 0; i < categories.length; i++) { + Map doc = new HashMap<>(); + doc.put("category", categories[i]); + doc.put("price", prices[i]); + connection.sync().hmset(HYBRID_PREFIX + i, doc); + binaryConnection.sync().hset((HYBRID_PREFIX + i).getBytes(), "embedding".getBytes(), + floatArrayToByteArray(vectors[i])); + } + } + + @Test + @EnabledOnCommand("FT.HYBRID") + void hybrid_routesRandomly_acrossUpstreams_whenReadFromUpstream() { + prepareHybrid(); + long upstreams = connection.getPartitions().stream().filter(n -> n.is(RedisClusterNode.NodeFlag.UPSTREAM)).count(); + assumeTrue(upstreams >= 2, "requires >= 2 upstream nodes"); + + connection.setReadFrom(ReadFrom.UPSTREAM); + clearLatencyMetrics(); + + Set nodes = new HashSet<>(); + for (int i = 0; i < 40 && nodes.size() < (int) upstreams; i++) { + connection.sync().ftHybrid(HYBRID_INDEX, hybridArgs()); + nodes.addAll(observedNodeIdsFor(CommandType.FT_HYBRID)); + } + + assertThat(nodes).isNotEmpty(); + nodes.forEach( + id -> assertThat(connection.getPartitions().getPartitionByNodeId(id).is(RedisClusterNode.NodeFlag.UPSTREAM)) + .isTrue()); + assertThat(nodes.size()).isEqualTo((int) upstreams); + } + + @Test + @EnabledOnCommand("FT.HYBRID") + void hybrid_routesRandomly_acrossReplicas_whenReadFromAnyReplica() { + prepareHybrid(); + long replicas = connection.getPartitions().stream().filter(n -> n.is(RedisClusterNode.NodeFlag.REPLICA)).count(); + assumeTrue(replicas >= 1, "requires >= 1 replica node"); + + connection.setReadFrom(ReadFrom.ANY_REPLICA); + clearLatencyMetrics(); + + Set nodes = new HashSet<>(); + for (int i = 0; i < 60 && nodes.size() < replicas; i++) { + connection.sync().ftHybrid(HYBRID_INDEX, hybridArgs()); + nodes.addAll(observedNodeIdsFor(CommandType.FT_HYBRID)); + } + + assertThat(nodes).isNotEmpty(); + nodes.forEach( + id -> assertThat(connection.getPartitions().getPartitionByNodeId(id).is(RedisClusterNode.NodeFlag.REPLICA)) + .isTrue()); + assertThat(nodes.size()).isLessThanOrEqualTo((int) replicas); + } + } diff --git a/src/test/resources/docker-env/.env b/src/test/resources/docker-env/.env index 4b009a719..5abd67dfe 100644 --- a/src/test/resources/docker-env/.env +++ b/src/test/resources/docker-env/.env @@ -1,3 +1,3 @@ REDIS_ENV_WORK_DIR=../../../../work/docker -REDIS_VERSION=8.2.2 -REDIS_STACK_VERSION=8.2.2 +REDIS_VERSION=8.4.0 +REDIS_STACK_VERSION=8.4.0 diff --git a/src/test/resources/docker-env/.env.v8.2 b/src/test/resources/docker-env/.env.v8.2 new file mode 100644 index 000000000..4b009a719 --- /dev/null +++ b/src/test/resources/docker-env/.env.v8.2 @@ -0,0 +1,3 @@ +REDIS_ENV_WORK_DIR=../../../../work/docker +REDIS_VERSION=8.2.2 +REDIS_STACK_VERSION=8.2.2 diff --git a/src/test/resources/docker-env/.env.v8.4 b/src/test/resources/docker-env/.env.v8.4 deleted file mode 100644 index 2733c1d4f..000000000 --- a/src/test/resources/docker-env/.env.v8.4 +++ /dev/null @@ -1,3 +0,0 @@ -REDIS_ENV_WORK_DIR=../../../../work/docker -REDIS_VERSION=8.4-GA-pre -REDIS_STACK_VERSION=8.4-GA-pre diff --git a/src/test/resources/docker-env/docker-compose.yml b/src/test/resources/docker-env/docker-compose.yml index bb4b90269..cafcbeb98 100644 --- a/src/test/resources/docker-env/docker-compose.yml +++ b/src/test/resources/docker-env/docker-compose.yml @@ -1,7 +1,7 @@ x-client-libs-image: &client-libs-image - image: "redislabs/client-libs-test:${REDIS_VERSION:-8.2.2}" + image: "redislabs/client-libs-test:${REDIS_VERSION:-8.4.0}" x-client-libs-stack-image: &client-libs-stack-image - image: "redislabs/client-libs-test:${REDIS_STACK_VERSION:-8.2.2}" + image: "redislabs/client-libs-test:${REDIS_STACK_VERSION:-8.4.0}" services: # Test infrastructure used for simulating network issues to test multi-db client failover