Skip to content

DOC-7226 Move prefetch-cache, time-series-dashboard, pub-sub and streaming Jedis use cases to RedisClient - #4389

Merged
andy-stark-redis merged 2 commits into
mainfrom
DOC-7226-jedis-pool-b
Oct 30, 2026
Merged

andy-stark-redis merged 2 commits into
mainfrom
DOC-7226-jedis-pool-b

Conversation

@andy-stark-redis

@andy-stark-redis andy-stark-redis commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Moves four more java-jedis use cases from JedisPool to RedisClient (Jedis 8.0.2).

  • time-series, pub-sub and streaming used APIs RedisClient lacks (getConnection(), pubsubChannels/NumSub/NumPat, deprecated sendCommand); they now use executeCommand. UNRECEIVED_ENTRY becomes XREADGROUP_UNDELIVERED_ENTRY.
  • pub-sub: each subscription's subscribe() holds one pooled connection; the shared client is never closed by a subscription. Prose and the sharded pub/sub sentence updated.
  • Page run commands list the six runtime jars (pub/sub fails without redis-authx-core).

Verified: 0 deprecation warnings; streaming scenario output identical to the original on Jedis 5.2.0 (produce, consume, crash, XAUTOCLAIM, replay); pub/sub 150 subscribe/unsubscribe cycles against a pool of 64; page commands run in a clean directory. Not run: Hugo build.

Part of DOC-7226.

🤖 Generated with Claude Code


Note

Low Risk
Changes are limited to documentation and local demo Java sources; behavior was manually verified for pub/sub and streaming per the PR description.

Overview
Updates the prefetch-cache, pub-sub, streaming, and time-series-dashboard java-jedis demos and docs from JedisPool / Jedis 5–6 to RedisClient / Jedis 8.0.2, including compile/run instructions that list the full runtime classpath (e.g. redis-authx-core).

Prefetch-cache and most helpers now hold a shared RedisClient instead of borrowing a Jedis per call; pipelines and multi() use try-with-resources on the pipeline/transaction types (AbstractTransaction for upserts).

Pub-sub passes a dedicated pooled Connection into JedisPubSub.proceed() / proceedWithPatterns() (so shutdown can disconnect() if needed), and sends PUBSUB introspection via executeCommand because those helpers are not on RedisClient.

Streaming switches the undelivered read sentinel to StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY, drops per-call pool checkout, and uses executeCommand for raw XAUTOCLAIM replies (deleted-ID list). Time-series routes TimeSeries module commands through jedis.executeCommand(CommandArguments) instead of Connection.sendCommand.

Prose changes cover ConnectionPoolConfig / RedisClient.builder() tuning and updated Maven/Gradle versions.

Reviewed by Cursor Bugbot for commit 3ef1c87. Bugbot is set up for automated code reviews on this repo. Configure here.

Prefetch cache, time-series dashboard, pub/sub and streaming (java-jedis) take a
RedisClient instead of a JedisPool. As in the first batch, getResource() blocks
are gone, pipelines and MULTI transactions are in try-with-resources, demo
servers build the client with RedisClient.create or RedisClient.builder() plus a
ConnectionPoolConfig where they size the pool, and each page's Maven/Gradle
version, prose and javac/java commands move to Jedis 8.0.2 with the six runtime
jars.

The three directories that used APIs RedisClient lacks needed more than a rename:

- time-series-dashboard reached for jedis.getConnection() and wrote raw
  commands to it. It now goes through jedis.executeCommand(new
  CommandArguments(cmd).addObjects(args)). UnifiedJedis.sendCommand(ProtocolCommand,
  String...) is the obvious swap, but Jedis 8 deprecates it, which would trade one
  deprecated call for another.
- pub-sub used Jedis.pubsubChannels/pubsubNumSub/pubsubNumPat, which exist only on
  the legacy Jedis class. They are now executeCommand(new CommandObject<>(
  new CommandArguments(Protocol.Command.PUBSUB).add(Protocol.Keyword.X), BuilderFactory.Y)).
  Each Subscription no longer owns a Jedis; its worker thread calls
  jedis.subscribe(listener, ...) on the shared client, which borrows and holds a
  pooled connection until unsubscribe. The "close the Jedis to force subscribe()
  to return" fallback is removed because closing the shared client would be wrong.
- streaming's XAUTOCLAIM raw-reply path moves from sendCommand to executeCommand,
  and StreamEntryID.UNRECEIVED_ENTRY (deprecated) becomes
  XREADGROUP_UNDELIVERED_ENTRY. The page's claim that the typed xautoclaim drops
  the deleted-ids slot still holds in 8.0.2 (checked with javap), so the hand
  parsing stays.

Verification, compiled with -Xlint:deprecation (0 warnings) against Jedis 8.0.2 and
run on redis:8.10. Prefetch: bulk load, sync-worker transaction, add, delete, 200
concurrent mixed calls. Time series: snapshot parsing matches the original on
Jedis 5.2.0 (types and min<=avg<=max), 200 concurrent snapshots. Pub/sub: exact and
pattern delivery counts exact (203 = 3 + 200), PUBSUB introspection correct,
150 subscribe/unsubscribe cycles against a pool of 64 (a leak would hang by ~60).
Streaming: the same scenario on the original (5.2.0) and migrated builds gives
identical output for produce, consume, crash, pending=3, XAUTOCLAIM claimed=3,
add/remove worker, trim and replay; only the final concurrent-load tail differs,
which is timing. Every page's javac/java commands were extracted and run
literally in a clean directory with only the six jars; all four compile and serve.

Constraint: RedisClient has no pubsubChannels/NumSub/NumPat, getConnection(), or non-deprecated sendCommand; use executeCommand(CommandArguments) or executeCommand(CommandObject)
Constraint: a subscribe()/psubscribe() on a RedisClient holds one pooled connection for the subscription's life, so maxTotal must cover subscribers plus command traffic
Learned: JedisPubSub needs redis.clients.authentication.core.Token (redis-authx-core) at class-load time, so a three-jar classpath serves every other demo and fails only pub/sub
Learned: an unrelated JVM left deadlocked from an earlier probe held 8 pooled connections and inflated every later "clients: N" count; check pgrep before reading connection counts
Learned: the streaming "crash" button drops the next N deliveries, so a scenario must crash before producing or nothing is ever pending
Ticket: DOC-7226
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Redis Memory

Found 6 related items from repository history (1 new this commit):

Memory updated at 3ef1c87

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

DOC-7226

@andy-stark-redis
andy-stark-redis requested a review from a team October 30, 2026 13:01
@andy-stark-redis andy-stark-redis self-assigned this Oct 30, 2026
@andy-stark-redis andy-stark-redis added the clients Client library docs label Oct 30, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@dwdougherty dwdougherty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skimmed. Approved.

…BE never completes

Cursor Bugbot found that Subscription.close() could no longer force a stuck subscribe()
to return. The earlier change in this PR called jedis.subscribe(listener, ...) on the shared
RedisClient, which borrows its connection internally, so close() had no handle on it. On a
half-open socket UNSUBSCRIBE never completes and the pooled connection stays checked out for
the life of the process.

Each Subscription now borrows its own Connection with jedis.getPool().getResource() and runs
listener.proceed(connection, ...) or proceedWithPatterns(...) on it, which is what
UnifiedJedis.subscribe does internally. The worker thread returns the connection in a finally
block, and close() restores the last resort the pre-migration code had: if the thread has not
exited a second after UNSUBSCRIBE, it disconnects the connection so the blocked read fails.

Reproduced first, then fixed. A TCP proxy between the demo and Redis was frozen to make a
half-open link: before, close() returned after about 1 s and the pool still showed 1 active
connection; after, close() takes about 1.5 s and the pool shows 0. The normal path is unchanged:
delivery counts exact (203 = 3 + 200), PUBSUB introspection correct, 150 subscribe/unsubscribe
cycles against a pool of 64, no deprecation warnings, nested-borrow check clean. The page text
that described subscribe() borrowing the connection now describes the borrow.

Constraint: UnifiedJedis.subscribe() borrows its connection internally; a caller that needs to break a stuck subscription must borrow the Connection itself and call JedisPubSub.proceed(connection, ...)
Learned: to reproduce a stuck unsubscribe, freeze a TCP proxy between the client and Redis rather than killing either end; a dead socket makes subscribe() throw and masks the bug
Ticket: DOC-7226
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3ef1c87. Configure here.

*/
public long deleteConsumer(String group, String consumer) {
try (Jedis jedis = pool.getResource()) {
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XAUTOCLAIM parser drops RESP3 fields

High Severity

Jedis 8 negotiates RESP3 by default, so XAUTOCLAIM entry fields arrive as maps rather than flat lists. decodeFieldArray only walks a List and returns an empty map otherwise, so claimed entries lose their payload and the crash-recovery path cannot process them.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ef1c87. Configure here.

@andy-stark-redis
andy-stark-redis merged commit e4dc3b2 into main Oct 30, 2026
105 checks passed
@andy-stark-redis
andy-stark-redis deleted the DOC-7226-jedis-pool-b branch October 30, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clients Client library docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants