From 6711b042061e4bc11e34476a7cae1cb5c45a67bc Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 30 Oct 2026 11:48:10 +0000 Subject: [PATCH 1/2] DOC-7226 Move three Jedis use cases from JedisPooled to RedisClient Memory layer, semantic cache and recommendation engine (java-jedis) take a RedisClient instead of a JedisPooled. Jedis 8.0 removed JedisPooled, so these did not compile against the 8.0.2 that DOC-7213 made the docs baseline. The demo servers build the client with RedisClient.builder().hostAndPort(...) .clientConfig(...).poolConfig(...).build() so the existing pool and timeout settings carry over, and the page snippets use RedisClient.create(...). The poms move to Jedis 8.0.2 (memory layer and semantic cache from 5.2.0, recommendation engine from 6.2.0). Unlike the JedisPool directories, nothing structural changed: JedisPooled already borrowed a connection per multi()/pipelined() and these classes already closed them in try-with-resources, which is why this group is a type rename plus the constructors. Verification: all three compile with Maven offline against Jedis 8.0.2 with deprecation reporting on and no warnings. Each demo ran against redis:8.10 and was compared with the same request sequence on the original code (memory layer and semantic cache on Jedis 5.2.0, recommendation engine on 6.2.0). Memory layer gave identical dedup distances (0.873, 0.554, 0.582); semantic cache gave identical hit/miss and distances (0.0, 0.0665); recommendation engine gave identical ranked ids and scores for plain, session-aware and filtered+reranked searches. 40-60 concurrent requests per demo all returned 200. Constraint: RedisClient.builder() needs a ConnectionPoolConfig (GenericObjectPoolConfig), which these demos already used Learned: the recommendation-engine demo cannot start on a fresh Redis 8.10, original code included: dropIndex tolerates only "no such index" / "unknown index name" and 8.10 says "SEARCH_INDEX_NOT_FOUND Index not found". Left unfixed (out of scope); I verified by pre-creating a dummy recommend:idx Learned: recommendation-engine builds a plain jar with no Main-Class; run it with mvn exec:java or a dependency:build-classpath classpath, not java -jar Learned: an empty "recalled" in the memory-layer demo's first turns is baseline behavior (recall threshold), not a search-parsing regression; compare against the original before suspecting the migration Ticket: DOC-7226 Co-Authored-By: Claude Sonnet 5.5 --- .../use-cases/memory-layer/java-jedis/_index.md | 4 ++-- .../use-cases/memory-layer/java-jedis/pom.xml | 2 +- .../main/java/com/redis/agentmem/AgentEventLog.java | 6 +++--- .../main/java/com/redis/agentmem/AgentSession.java | 6 +++--- .../main/java/com/redis/agentmem/DemoServer.java | 13 +++++++------ .../java/com/redis/agentmem/LongTermMemory.java | 6 +++--- .../java-jedis/DemoServer.java | 11 +++++++---- .../java-jedis/Recommender.java | 8 ++++---- .../recommendation-engine/java-jedis/_index.md | 8 ++++---- .../recommendation-engine/java-jedis/pom.xml | 2 +- .../use-cases/semantic-cache/java-jedis/_index.md | 4 ++-- .../use-cases/semantic-cache/java-jedis/pom.xml | 2 +- .../main/java/com/redis/semcache/DemoServer.java | 13 +++++++------ .../java/com/redis/semcache/RedisSemanticCache.java | 6 +++--- 14 files changed, 48 insertions(+), 43 deletions(-) diff --git a/content/develop/use-cases/memory-layer/java-jedis/_index.md b/content/develop/use-cases/memory-layer/java-jedis/_index.md index d85ccdbe50..9f56c8ef71 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/_index.md +++ b/content/develop/use-cases/memory-layer/java-jedis/_index.md @@ -57,9 +57,9 @@ The embedding is computed once and reused for steps 3 and 4 — there's no point import com.redis.agentmem.AgentSession; import com.redis.agentmem.SessionState; import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; -JedisPooled jedis = new JedisPooled(new HostAndPort("localhost", 6379)); +RedisClient jedis = RedisClient.create(new HostAndPort("localhost", 6379)); AgentSession session = new AgentSession(jedis, "agent:session:", 3600, 20); String threadId = session.newThreadId(); diff --git a/content/develop/use-cases/memory-layer/java-jedis/pom.xml b/content/develop/use-cases/memory-layer/java-jedis/pom.xml index a25b43e75e..7834142d49 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/pom.xml +++ b/content/develop/use-cases/memory-layer/java-jedis/pom.xml @@ -21,7 +21,7 @@ 17 17 UTF-8 - 5.2.0 + 8.0.2 0.33.0 20240303 diff --git a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentEventLog.java b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentEventLog.java index 832c8a0758..0c49b63bcc 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentEventLog.java +++ b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentEventLog.java @@ -1,6 +1,6 @@ package com.redis.agentmem; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.StreamEntryID; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.resps.StreamEntry; @@ -35,11 +35,11 @@ public final class AgentEventLog { /** Approximate cap on stream length. */ public static final long DEFAULT_MAX_LEN = 1000L; - private final JedisPooled jedis; + private final RedisClient jedis; private final String keyPrefix; private final long maxLen; - public AgentEventLog(JedisPooled jedis, String keyPrefix, long maxLen) { + public AgentEventLog(RedisClient jedis, String keyPrefix, long maxLen) { this.jedis = jedis; this.keyPrefix = keyPrefix; this.maxLen = maxLen > 0 ? maxLen : DEFAULT_MAX_LEN; diff --git a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentSession.java b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentSession.java index f05bfa0c69..47f7206fa2 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentSession.java +++ b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/AgentSession.java @@ -4,7 +4,7 @@ import org.json.JSONException; import org.json.JSONObject; import redis.clients.jedis.AbstractTransaction; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import java.util.ArrayList; import java.util.HashMap; @@ -38,13 +38,13 @@ public final class AgentSession { /** Default rolling-window size. */ public static final int DEFAULT_MAX_TURNS = 20; - private final JedisPooled jedis; + private final RedisClient jedis; private final String keyPrefix; private final long defaultTtlSeconds; private final int maxTurns; public AgentSession( - JedisPooled jedis, + RedisClient jedis, String keyPrefix, long defaultTtlSeconds, int maxTurns) { diff --git a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/DemoServer.java b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/DemoServer.java index 086b7cfa0b..3cacb1556b 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/DemoServer.java +++ b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/DemoServer.java @@ -8,7 +8,7 @@ import redis.clients.jedis.ConnectionPoolConfig; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import java.io.IOException; import java.io.InputStream; @@ -61,13 +61,14 @@ public static void main(String[] argv) throws Exception { poolConfig.setMaxTotal(16); poolConfig.setMaxIdle(4); poolConfig.setMinIdle(1); - JedisPooled jedis = new JedisPooled( - poolConfig, - new HostAndPort(args.redisHost, args.redisPort), - DefaultJedisClientConfig.builder() + RedisClient jedis = RedisClient.builder() + .hostAndPort(new HostAndPort(args.redisHost, args.redisPort)) + .poolConfig(poolConfig) + .clientConfig(DefaultJedisClientConfig.builder() .socketTimeoutMillis(2000) .connectionTimeoutMillis(2000) - .build()); + .build()) + .build(); try { jedis.ping(); } catch (Exception ex) { diff --git a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/LongTermMemory.java b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/LongTermMemory.java index d9c3707a5c..539d445e34 100644 --- a/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/LongTermMemory.java +++ b/content/develop/use-cases/memory-layer/java-jedis/src/main/java/com/redis/agentmem/LongTermMemory.java @@ -2,7 +2,7 @@ import org.json.JSONObject; import redis.clients.jedis.AbstractTransaction; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.json.Path2; import redis.clients.jedis.search.Document; @@ -97,7 +97,7 @@ public static Map defaultTtlByKind() { */ private static final String TAG_SPECIAL = "\\,.<>{}[]\"':;!@#$%^&*()-+=~| "; - private final JedisPooled jedis; + private final RedisClient jedis; private final String indexName; private final String keyPrefix; private final int vectorDim; @@ -106,7 +106,7 @@ public static Map defaultTtlByKind() { private final Map ttlByKind; public LongTermMemory( - JedisPooled jedis, + RedisClient jedis, String indexName, String keyPrefix, int vectorDim, diff --git a/content/develop/use-cases/recommendation-engine/java-jedis/DemoServer.java b/content/develop/use-cases/recommendation-engine/java-jedis/DemoServer.java index bd529b54f3..28a3f8d9bd 100644 --- a/content/develop/use-cases/recommendation-engine/java-jedis/DemoServer.java +++ b/content/develop/use-cases/recommendation-engine/java-jedis/DemoServer.java @@ -24,7 +24,7 @@ import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.DefaultJedisClientConfig; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.ConnectionPoolConfig; /** @@ -49,7 +49,7 @@ public class DemoServer { private static final String DEMO_USER_ID = "demo"; - private static JedisPooled jedisClient; + private static RedisClient jedisClient; private static Recommender recommender; private static LocalEmbedder embedder; private static DemoState state; @@ -94,8 +94,11 @@ public static void main(String[] args) throws Exception { poolConfig.setMaxTotal(32); poolConfig.setMaxIdle(16); JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().build(); - jedisClient = new JedisPooled( - new HostAndPort(redisHost, redisPort), clientConfig, poolConfig); + jedisClient = RedisClient.builder() + .hostAndPort(new HostAndPort(redisHost, redisPort)) + .clientConfig(clientConfig) + .poolConfig(poolConfig) + .build(); jedisClient.ping(); } catch (Exception exc) { System.err.printf("Failed to connect to Redis at %s:%d: %s%n", diff --git a/content/develop/use-cases/recommendation-engine/java-jedis/Recommender.java b/content/develop/use-cases/recommendation-engine/java-jedis/Recommender.java index c7eaf65cc2..6617a16b86 100644 --- a/content/develop/use-cases/recommendation-engine/java-jedis/Recommender.java +++ b/content/develop/use-cases/recommendation-engine/java-jedis/Recommender.java @@ -10,7 +10,7 @@ import java.util.TreeSet; import redis.clients.jedis.AbstractPipeline; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.Response; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.search.Document; @@ -72,18 +72,18 @@ public class Recommender { public static final String DEFAULT_KEY_PREFIX = "product:"; public static final String DEFAULT_USER_KEY_PREFIX = "user:"; - private final JedisPooled client; + private final RedisClient client; private final String indexName; private final String keyPrefix; private final String userKeyPrefix; private final int vectorDim; - public Recommender(JedisPooled client) { + public Recommender(RedisClient client) { this(client, DEFAULT_INDEX_NAME, DEFAULT_KEY_PREFIX, DEFAULT_USER_KEY_PREFIX, VECTOR_DIM_DEFAULT); } - public Recommender(JedisPooled client, String indexName, String keyPrefix, + public Recommender(RedisClient client, String indexName, String keyPrefix, String userKeyPrefix, int vectorDim) { if (client == null) { throw new IllegalArgumentException("client is required"); diff --git a/content/develop/use-cases/recommendation-engine/java-jedis/_index.md b/content/develop/use-cases/recommendation-engine/java-jedis/_index.md index dcc1da5116..aab8b7b5d8 100644 --- a/content/develop/use-cases/recommendation-engine/java-jedis/_index.md +++ b/content/develop/use-cases/recommendation-engine/java-jedis/_index.md @@ -59,11 +59,11 @@ The `Recommender` class wraps the Redis Search index and the retrieval flow ([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/recommendation-engine/java-jedis/Recommender.java)): ```java -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; -// JedisPooled is a pool-backed UnifiedJedis: it exposes both the +// RedisClient is a pool-backed client: it exposes both the // FT.* search commands and a pipelined() API in one client. -JedisPooled jedis = new JedisPooled("localhost", 6379); +RedisClient jedis = RedisClient.create("localhost", 6379); Recommender recommender = new Recommender(jedis, "recommend:idx", "product:", "user:", 384); @@ -166,7 +166,7 @@ FT.SEARCH recommend:idx When there's no filter, the pre-filter clause is just `(*)`. `vector_score` is the cosine *distance* (0 means identical, 2 means opposite), so the result is sorted ascending and the top row is the closest candidate to the query. -The Jedis equivalent uses `JedisPooled.ftSearch(String, Query)` with the binary parameter wired through `Query.addParam("vec", floatsToBytes(vec))`: +The Jedis equivalent uses `RedisClient.ftSearch(String, Query)` with the binary parameter wired through `Query.addParam("vec", floatsToBytes(vec))`: ```java String knn = filterClause + "=>[KNN " + k + " @embedding $vec AS vector_score]"; diff --git a/content/develop/use-cases/recommendation-engine/java-jedis/pom.xml b/content/develop/use-cases/recommendation-engine/java-jedis/pom.xml index 64be1b01df..a0711f110c 100644 --- a/content/develop/use-cases/recommendation-engine/java-jedis/pom.xml +++ b/content/develop/use-cases/recommendation-engine/java-jedis/pom.xml @@ -32,7 +32,7 @@ redis.clients jedis - 6.2.0 + 8.0.2 diff --git a/content/develop/use-cases/semantic-cache/java-jedis/_index.md b/content/develop/use-cases/semantic-cache/java-jedis/_index.md index 8aa335afef..dc2b26af3a 100644 --- a/content/develop/use-cases/semantic-cache/java-jedis/_index.md +++ b/content/develop/use-cases/semantic-cache/java-jedis/_index.md @@ -57,13 +57,13 @@ The `RedisSemanticCache` class wraps the Redis Search index and the lookup / wri ([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/RedisSemanticCache.java)): ```java -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import com.redis.semcache.RedisSemanticCache; import com.redis.semcache.LocalEmbedder; import com.redis.semcache.LookupResult; import com.redis.semcache.CacheHit; -JedisPooled jedis = new JedisPooled("localhost", 6379); +RedisClient jedis = RedisClient.create("localhost", 6379); LocalEmbedder embedder = LocalEmbedder.create(); // sentence-transformers/all-MiniLM-L6-v2 RedisSemanticCache cache = new RedisSemanticCache( diff --git a/content/develop/use-cases/semantic-cache/java-jedis/pom.xml b/content/develop/use-cases/semantic-cache/java-jedis/pom.xml index 6f87085b1f..5c0d2c3e71 100644 --- a/content/develop/use-cases/semantic-cache/java-jedis/pom.xml +++ b/content/develop/use-cases/semantic-cache/java-jedis/pom.xml @@ -21,7 +21,7 @@ 17 17 UTF-8 - 5.2.0 + 8.0.2 0.33.0 20240303 diff --git a/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/DemoServer.java b/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/DemoServer.java index a8089e9fb0..423dbc7c9c 100644 --- a/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/DemoServer.java +++ b/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/DemoServer.java @@ -7,7 +7,7 @@ import org.json.JSONObject; import redis.clients.jedis.ConnectionPoolConfig; import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import java.io.IOException; import java.io.InputStream; @@ -63,13 +63,14 @@ public static void main(String[] argv) throws Exception { poolConfig.setMaxTotal(16); poolConfig.setMaxIdle(4); poolConfig.setMinIdle(1); - JedisPooled jedis = new JedisPooled( - poolConfig, - new HostAndPort(args.redisHost, args.redisPort), - redis.clients.jedis.DefaultJedisClientConfig.builder() + RedisClient jedis = RedisClient.builder() + .hostAndPort(new HostAndPort(args.redisHost, args.redisPort)) + .poolConfig(poolConfig) + .clientConfig(redis.clients.jedis.DefaultJedisClientConfig.builder() .socketTimeoutMillis(2000) .connectionTimeoutMillis(2000) - .build()); + .build()) + .build(); try { jedis.ping(); } catch (Exception ex) { diff --git a/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/RedisSemanticCache.java b/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/RedisSemanticCache.java index a10bb02448..e7c1efb1a9 100644 --- a/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/RedisSemanticCache.java +++ b/content/develop/use-cases/semantic-cache/java-jedis/src/main/java/com/redis/semcache/RedisSemanticCache.java @@ -1,7 +1,7 @@ package com.redis.semcache; import redis.clients.jedis.AbstractTransaction; -import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.Response; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.search.Document; @@ -63,7 +63,7 @@ public final class RedisSemanticCache { */ private static final String TAG_SPECIAL = "\\,.<>{}[]\"':;!@#$%^&*()-+=~| "; - private final JedisPooled jedis; + private final RedisClient jedis; private final String indexName; private final String keyPrefix; private final int vectorDim; @@ -71,7 +71,7 @@ public final class RedisSemanticCache { private final long defaultTtlSeconds; public RedisSemanticCache( - JedisPooled jedis, + RedisClient jedis, String indexName, String keyPrefix, int vectorDim, From 45b2b2cd725bf9e44d54a145ce67cf1a6b2ec50b Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 30 Oct 2026 12:05:53 +0000 Subject: [PATCH 2/2] DOC-7226 Move the feature-store Jedis use case from JedisPool to RedisClient FeatureStore, BuildFeatures and DemoServer take a RedisClient instead of a JedisPool. getResource() blocks are gone, the three pipelines are in try-with-resources, DemoServer builds the client with RedisClient.builder() and a ConnectionPoolConfig (maxTotal 64, maxIdle 32, minIdle 4, as before), and BuildFeatures closes a RedisClient it creates with RedisClient.create. The pom pins Jedis 8.0.2 (was 6.2.0), and the page's snippets, prerequisites ("Jedis 8.0 or later") and pool-sizing section follow. One scoping trap: updateStreaming read the HEXPIRE Response after the pipeline block, so the Response variable moves out of the try. Responses stay readable after the pipeline closes because sync() resolves them. Verification: compiles with Maven offline and deprecation reporting on, against Jedis 8.0.2, no warnings. The migrated build and the original (Jedis 6.2.0) ran the same scenario against redis:8.10 with the streaming worker paused for the static part: identical batch fields and values for u0001, identical /read and /batch-read output, key TTL ~86400, per-field TTL on every streaming field in (0, 300] after the worker ran (the HEXPIRE pipeline), bulk-load key TTL ~600. The bulk-load response lists "loaded" and "ttl_seconds" in a different order between runs; that is Map.of's per-JVM randomized iteration order in unmodified code. Constraint: pipeline Response values may be read after the try-with-resources closes the pipeline, but only if sync() ran inside it Learned: comparing against the original on a different Jedis version needs invariants, not byte equality, wherever the demo uses Map.of for JSON Ticket: DOC-7226 Co-Authored-By: Claude Sonnet 5.5 --- .../java-jedis/BuildFeatures.java | 6 +- .../feature-store/java-jedis/DemoServer.java | 17 ++- .../java-jedis/FeatureStore.java | 125 ++++++++---------- .../feature-store/java-jedis/_index.md | 57 ++++---- .../feature-store/java-jedis/pom.xml | 4 +- 5 files changed, 94 insertions(+), 115 deletions(-) diff --git a/content/develop/use-cases/feature-store/java-jedis/BuildFeatures.java b/content/develop/use-cases/feature-store/java-jedis/BuildFeatures.java index 670a07cfae..8109ff3d10 100644 --- a/content/develop/use-cases/feature-store/java-jedis/BuildFeatures.java +++ b/content/develop/use-cases/feature-store/java-jedis/BuildFeatures.java @@ -3,7 +3,7 @@ import java.util.Map; import java.util.Random; -import redis.clients.jedis.JedisPool; +import redis.clients.jedis.RedisClient; /** * Synthesize a small batch of users with realistic-looking features @@ -78,8 +78,8 @@ public static void main(String[] args) { } } - try (JedisPool pool = new JedisPool(redisHost, redisPort)) { - FeatureStore store = new FeatureStore(pool, keyPrefix, ttlSeconds, + try (RedisClient jedis = RedisClient.create(redisHost, redisPort)) { + FeatureStore store = new FeatureStore(jedis, keyPrefix, ttlSeconds, FeatureStore.DEFAULT_STREAMING_TTL_SECONDS); Map> rows = synthesizeUsers(count, seed); int loaded = store.bulkLoad(rows, ttlSeconds); diff --git a/content/develop/use-cases/feature-store/java-jedis/DemoServer.java b/content/develop/use-cases/feature-store/java-jedis/DemoServer.java index a8939a60eb..8486229f9c 100644 --- a/content/develop/use-cases/feature-store/java-jedis/DemoServer.java +++ b/content/develop/use-cases/feature-store/java-jedis/DemoServer.java @@ -16,8 +16,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.RedisClient; /** * Redis feature-store demo server (Jedis + JDK HttpServer). @@ -35,7 +35,7 @@ public class DemoServer { private static FeatureStore store; private static StreamingWorker worker; private static FeatureStoreDemo demo; - private static JedisPool jedisPool; + private static RedisClient jedis; public static void main(String[] args) throws Exception { String host = "127.0.0.1"; @@ -77,13 +77,16 @@ public static void main(String[] args) throws Exception { } } - JedisPoolConfig poolCfg = new JedisPoolConfig(); + ConnectionPoolConfig poolCfg = new ConnectionPoolConfig(); poolCfg.setMaxTotal(64); poolCfg.setMaxIdle(32); poolCfg.setMinIdle(4); - jedisPool = new JedisPool(poolCfg, redisHost, redisPort); + jedis = RedisClient.builder() + .hostAndPort(redisHost, redisPort) + .poolConfig(poolCfg) + .build(); - store = new FeatureStore(jedisPool, keyPrefix, + store = new FeatureStore(jedis, keyPrefix, batchTtlSeconds, streamingTtlSeconds); worker = new StreamingWorker(store, 1000L, usersPerTick, 1337L); demo = new FeatureStoreDemo(store, worker, 42L); @@ -120,7 +123,7 @@ public static void main(String[] args) throws Exception { System.out.println("\nShutting down..."); worker.stop(); server.stop(0); - jedisPool.close(); + jedis.close(); })); Thread.currentThread().join(); diff --git a/content/develop/use-cases/feature-store/java-jedis/FeatureStore.java b/content/develop/use-cases/feature-store/java-jedis/FeatureStore.java index 019a1e1c5e..32cbef86b4 100644 --- a/content/develop/use-cases/feature-store/java-jedis/FeatureStore.java +++ b/content/develop/use-cases/feature-store/java-jedis/FeatureStore.java @@ -5,9 +5,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; +import redis.clients.jedis.RedisClient; import redis.clients.jedis.Response; /** @@ -67,7 +66,7 @@ public class FeatureStore { public static final long DEFAULT_STREAMING_TTL_SECONDS = 5L * 60L; public static final String DEFAULT_KEY_PREFIX = "fs:user:"; - private final JedisPool pool; + private final RedisClient jedis; private final String keyPrefix; private final long batchTtlSeconds; private final long streamingTtlSeconds; @@ -77,15 +76,15 @@ public class FeatureStore { private final AtomicLong readsTotal = new AtomicLong(); private final AtomicLong readFieldsTotal = new AtomicLong(); - public FeatureStore(JedisPool pool) { - this(pool, DEFAULT_KEY_PREFIX, + public FeatureStore(RedisClient jedis) { + this(jedis, DEFAULT_KEY_PREFIX, DEFAULT_BATCH_TTL_SECONDS, DEFAULT_STREAMING_TTL_SECONDS); } - public FeatureStore(JedisPool pool, String keyPrefix, + public FeatureStore(RedisClient jedis, String keyPrefix, long batchTtlSeconds, long streamingTtlSeconds) { - this.pool = pool; + this.jedis = jedis; this.keyPrefix = keyPrefix; this.batchTtlSeconds = batchTtlSeconds; this.streamingTtlSeconds = streamingTtlSeconds; @@ -115,8 +114,7 @@ public String keyFor(String entityId) { */ public int bulkLoad(Map> rows, long ttlSeconds) { if (rows.isEmpty()) return 0; - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + try (Pipeline pipe = jedis.pipelined()) { for (Map.Entry> e : rows.entrySet()) { String key = keyFor(e.getKey()); Map encoded = encode(e.getValue()); @@ -139,9 +137,7 @@ public int bulkLoad(Map> rows) { * pipelines flow through {@link #bulkLoad}. */ public void updateBatchFeature(String entityId, String field, Object value) { - try (Jedis jedis = pool.getResource()) { - jedis.hset(keyFor(entityId), field, encodeValue(value)); - } + jedis.hset(keyFor(entityId), field, encodeValue(value)); batchWritesTotal.incrementAndGet(); } @@ -180,13 +176,13 @@ public void updateStreaming(String entityId, Map fields, long tt String[] names = encoded.keySet().toArray(new String[0]); List expireCodes; - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + Response> expireResp; + try (Pipeline pipe = jedis.pipelined()) { pipe.hset(key, encoded); - Response> expireResp = pipe.hexpire(key, ttlSeconds, names); + expireResp = pipe.hexpire(key, ttlSeconds, names); pipe.sync(); - expireCodes = expireResp.get(); } + expireCodes = expireResp.get(); for (Long code : expireCodes) { if (code == null || code != 1L) { throw new IllegalStateException( @@ -215,19 +211,15 @@ public Map getFeatures(String entityId, List fieldNames) String key = keyFor(entityId); Map out = new LinkedHashMap<>(); if (fieldNames == null) { - try (Jedis jedis = pool.getResource()) { - Map all = jedis.hgetAll(key); - if (all != null) out.putAll(all); - } + Map all = jedis.hgetAll(key); + if (all != null) out.putAll(all); readsTotal.incrementAndGet(); readFieldsTotal.addAndGet(out.size()); return out; } if (fieldNames.isEmpty()) return out; List values; - try (Jedis jedis = pool.getResource()) { - values = jedis.hmget(key, fieldNames.toArray(new String[0])); - } + values = jedis.hmget(key, fieldNames.toArray(new String[0])); for (int i = 0; i < fieldNames.size(); i++) { String v = values.get(i); if (v != null) out.put(fieldNames.get(i), v); @@ -253,8 +245,7 @@ public Map> batchGetFeatures( String[] names = fieldNames.toArray(new String[0]); Map> out = new LinkedHashMap<>(); List>> responses = new ArrayList<>(entityIds.size()); - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + try (Pipeline pipe = jedis.pipelined()) { for (String id : entityIds) { responses.add(pipe.hmget(keyFor(id), names)); } @@ -287,9 +278,7 @@ public Map> batchGetFeatures( * key-level TTL is set, {@code -2} if the key doesn't exist. */ public long keyTtlSeconds(String entityId) { - try (Jedis jedis = pool.getResource()) { - return jedis.ttl(keyFor(entityId)); - } + return jedis.ttl(keyFor(entityId)); } /** @@ -302,9 +291,7 @@ public long keyTtlSeconds(String entityId) { public Map fieldTtlsSeconds(String entityId, List fieldNames) { if (fieldNames.isEmpty()) return Collections.emptyMap(); List codes; - try (Jedis jedis = pool.getResource()) { - codes = jedis.httl(keyFor(entityId), fieldNames.toArray(new String[0])); - } + codes = jedis.httl(keyFor(entityId), fieldNames.toArray(new String[0])); Map out = new LinkedHashMap<>(); for (int i = 0; i < fieldNames.size(); i++) { // HTTL on a missing key returns a flat list of -2s; jedis @@ -329,24 +316,22 @@ public List listEntityIds(int limit) { List ids = new ArrayList<>(); String pattern = keyPrefix + "*"; String cursor = "0"; - try (Jedis jedis = pool.getResource()) { - do { - redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() - .match(pattern) - .count(200); - redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); - for (String k : sr.getResult()) { - if (k.length() > keyPrefix.length()) { - ids.add(k.substring(keyPrefix.length())); - if (ids.size() >= limit) { - Collections.sort(ids); - return ids; - } + do { + redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() + .match(pattern) + .count(200); + redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); + for (String k : sr.getResult()) { + if (k.length() > keyPrefix.length()) { + ids.add(k.substring(keyPrefix.length())); + if (ids.size() >= limit) { + Collections.sort(ids); + return ids; } } - cursor = sr.getCursor(); - } while (!"0".equals(cursor)); - } + } + cursor = sr.getCursor(); + } while (!"0".equals(cursor)); Collections.sort(ids); return ids; } @@ -360,23 +345,19 @@ public long countEntities() { long count = 0; String pattern = keyPrefix + "*"; String cursor = "0"; - try (Jedis jedis = pool.getResource()) { - do { - redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() - .match(pattern) - .count(500); - redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); - count += sr.getResult().size(); - cursor = sr.getCursor(); - } while (!"0".equals(cursor)); - } + do { + redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() + .match(pattern) + .count(500); + redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); + count += sr.getResult().size(); + cursor = sr.getCursor(); + } while (!"0".equals(cursor)); return count; } public long deleteEntity(String entityId) { - try (Jedis jedis = pool.getResource()) { - return jedis.del(keyFor(entityId)); - } + return jedis.del(keyFor(entityId)); } /** @@ -389,19 +370,17 @@ public long reset() { long deleted = 0; String pattern = keyPrefix + "*"; String cursor = "0"; - try (Jedis jedis = pool.getResource()) { - do { - redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() - .match(pattern) - .count(500); - redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); - List batch = sr.getResult(); - if (!batch.isEmpty()) { - deleted += jedis.del(batch.toArray(new String[0])); - } - cursor = sr.getCursor(); - } while (!"0".equals(cursor)); - } + do { + redis.clients.jedis.params.ScanParams params = new redis.clients.jedis.params.ScanParams() + .match(pattern) + .count(500); + redis.clients.jedis.resps.ScanResult sr = jedis.scan(cursor, params); + List batch = sr.getResult(); + if (!batch.isEmpty()) { + deleted += jedis.del(batch.toArray(new String[0])); + } + cursor = sr.getCursor(); + } while (!"0".equals(cursor)); return deleted; } diff --git a/content/develop/use-cases/feature-store/java-jedis/_index.md b/content/develop/use-cases/feature-store/java-jedis/_index.md index b1a5894629..9db6763da4 100644 --- a/content/develop/use-cases/feature-store/java-jedis/_index.md +++ b/content/develop/use-cases/feature-store/java-jedis/_index.md @@ -126,13 +126,16 @@ The `FeatureStore` class wraps the read/write paths ([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/feature-store/java-jedis/FeatureStore.java)): ```java -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.RedisClient; -JedisPoolConfig cfg = new JedisPoolConfig(); +ConnectionPoolConfig cfg = new ConnectionPoolConfig(); cfg.setMaxTotal(64); -JedisPool pool = new JedisPool(cfg, "localhost", 6379); -FeatureStore store = new FeatureStore(pool, +RedisClient jedis = RedisClient.builder() + .hostAndPort("localhost", 6379) + .poolConfig(cfg) + .build(); +FeatureStore store = new FeatureStore(jedis, "fs:user:", 24L * 60L * 60L, // whole-entity TTL aligned with the daily batch cycle 5L * 60L // per-field TTL on each streaming feature @@ -220,8 +223,7 @@ server side but the client only pays one RTT. ```java public int bulkLoad(Map> rows, long ttlSeconds) { if (rows.isEmpty()) return 0; - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + try (Pipeline pipe = jedis.pipelined()) { for (Map.Entry> e : rows.entrySet()) { String key = keyFor(e.getKey()); Map encoded = encode(e.getValue()); @@ -265,13 +267,13 @@ public void updateStreaming(String entityId, Map fields, long tt String[] names = encoded.keySet().toArray(new String[0]); List expireCodes; - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + Response> expireResp; + try (Pipeline pipe = jedis.pipelined()) { pipe.hset(key, encoded); - Response> expireResp = pipe.hexpire(key, ttlSeconds, names); + expireResp = pipe.hexpire(key, ttlSeconds, names); pipe.sync(); - expireCodes = expireResp.get(); } + expireCodes = expireResp.get(); for (Long code : expireCodes) { if (code == null || code != 1L) { throw new IllegalStateException( @@ -309,7 +311,7 @@ in the model itself. > **HEXPIRE requires Redis 7.4 or later.** `HEXPIRE` and the field-level TTL > commands (`HTTL`, `HPERSIST`, `HEXPIREAT`, `HPEXPIRE`, `HPEXPIREAT`, > `HPTTL`, `HEXPIRETIME`, `HPEXPIRETIME`) were added in Redis 7.4. Jedis 5.2 -> was the first release with the bindings; the demo's `pom.xml` pins 6.2. +> was the first release with the bindings; the demo's `pom.xml` pins 8.0.2. > On older Redis builds you would have to put streaming features on their > own keys (one key per feature, or one key per feature group) and set a > key-level `EXPIRE` instead — at the cost of giving up the single-`HMGET` @@ -324,17 +326,13 @@ public Map getFeatures(String entityId, List fieldNames) String key = keyFor(entityId); Map out = new LinkedHashMap<>(); if (fieldNames == null) { - try (Jedis jedis = pool.getResource()) { - Map all = jedis.hgetAll(key); - if (all != null) out.putAll(all); - } + Map all = jedis.hgetAll(key); + if (all != null) out.putAll(all); return out; } if (fieldNames.isEmpty()) return out; List values; - try (Jedis jedis = pool.getResource()) { - values = jedis.hmget(key, fieldNames.toArray(new String[0])); - } + values = jedis.hmget(key, fieldNames.toArray(new String[0])); for (int i = 0; i < fieldNames.size(); i++) { String v = values.get(i); if (v != null) out.put(fieldNames.get(i), v); @@ -368,8 +366,7 @@ public Map> batchGetFeatures( } String[] names = fieldNames.toArray(new String[0]); List>> responses = new ArrayList<>(entityIds.size()); - try (Jedis jedis = pool.getResource()) { - Pipeline pipe = jedis.pipelined(); + try (Pipeline pipe = jedis.pipelined()) { for (String id : entityIds) { responses.add(pipe.hmget(keyFor(id), names)); } @@ -513,8 +510,8 @@ pool. The HTML page lets you: read time. The server holds one `FeatureStore` and one `StreamingWorker` for the -lifetime of the process, plus a `JedisPool` that all handlers borrow -connections from. Endpoints: +lifetime of the process, plus a `RedisClient` whose connection pool all +handlers borrow from. Endpoints: | Endpoint | What it does | |---------------------------|-------------------------------------------------------------------------------------| @@ -533,9 +530,9 @@ connections from. Endpoints: demo relies on per-field TTL for the mixed-staleness story. * **Java 17 or later.** The demo uses switch expressions with arrow labels (`case "..." -> ...`), records, and text blocks. -* **Jedis 5.2 or later.** The demo's `pom.xml` pins - `redis.clients:jedis:6.2.0`. Field-level TTL bindings (`hexpire`, `httl`, - `hpersist`) ship from Jedis 5.2. +* **Jedis 8.0 or later.** The demo uses `RedisClient`, and its `pom.xml` + pins `redis.clients:jedis:8.0.2`. Field-level TTL bindings (`hexpire`, + `httl`, `hpersist`) ship from Jedis 5.2. If your Redis server is running elsewhere, start the demo with `--redis-host` and `--redis-port`. @@ -607,7 +604,7 @@ is `fs:user:`. Pass `--no-reset` to keep existing data across restarts, or The guidance below focuses on the production concerns that are specific to running a feature store on Redis. For the generic Jedis production checklist -— `JedisPool` sizing, AUTH/ACL, retry policy, sentinel/cluster failover — +— connection pool sizing, AUTH/ACL, retry policy, sentinel/cluster failover — see the [Jedis production usage guide](/content/develop/clients/jedis/produsage.md). For TLS specifically, follow the @@ -679,11 +676,11 @@ The exception is debugging and feature-set discovery, where you genuinely want the full hash. The demo's "Inspect" button uses `hgetAll` for exactly this reason. -### Size the JedisPool for the request shape +### Size the connection pool for the request shape Every `FeatureStore` helper method borrows a connection from the -`JedisPool` for the duration of one Redis call (or one `Pipeline.sync()`) -and returns it via the try-with-resources block. One HTTP handler can +`RedisClient` pool for the duration of one Redis call (or one pipeline, +until try-with-resources closes it) and returns it afterwards. One HTTP handler can therefore borrow several connections sequentially — `/read`, for example, makes one `hmget` call, one `httl` call, and one `ttl` call, each of which is its own borrow. diff --git a/content/develop/use-cases/feature-store/java-jedis/pom.xml b/content/develop/use-cases/feature-store/java-jedis/pom.xml index fbed81d6dd..6431c94e29 100644 --- a/content/develop/use-cases/feature-store/java-jedis/pom.xml +++ b/content/develop/use-cases/feature-store/java-jedis/pom.xml @@ -30,11 +30,11 @@ + (hexpire / httl / hpersist); the demo pins 8.0.2. --> redis.clients jedis - 6.2.0 + 8.0.2