Skip to content

Lazy stream/consumer group bootstrap#215

Merged
chrisguidry merged 2 commits intomainfrom
forgiveness-bootstrap-206
Dec 9, 2025
Merged

Lazy stream/consumer group bootstrap#215
chrisguidry merged 2 commits intomainfrom
forgiveness-bootstrap-206

Conversation

@chrisguidry
Copy link
Copy Markdown
Owner

Instead of proactively creating the Redis stream and consumer group on every
async with Docket(...), we now create them lazily when actually needed. This
avoids the noisy BUSYGROUP errors that were showing up in production logs.

The worker now catches NOGROUP errors from xautoclaim/xreadgroup and creates
the group on demand. Similarly, snapshot() handles NOGROUP (with a workaround
for a redis-py 7.0 bug where pipeline errors lose the original message).

For the memory:// backend (fakeredis), we proactively create the group in
snapshot() since fakeredis has a bug where it raises TypeError instead of
NOGROUP.

Closes #206

🤖 Generated with Claude Code

Instead of proactively creating the Redis stream and consumer group on every
`async with Docket(...)`, we now create them lazily when actually needed. This
avoids the noisy BUSYGROUP errors that were showing up in production logs.

The worker now catches NOGROUP errors from xautoclaim/xreadgroup and creates
the group on demand. Similarly, snapshot() handles NOGROUP (with a workaround
for a redis-py 7.0 bug where pipeline errors lose the original message).

For the memory:// backend (fakeredis), we proactively create the group in
snapshot() since fakeredis has a bug where it raises TypeError instead of
NOGROUP.

Closes #206

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Dec 9, 2025

Claude finished @chrisguidry's task —— View job


Code Review Complete ✅

I've completed my review of PR #215 implementing lazy stream/consumer group bootstrap. This is a well-architected solution that addresses production issues while maintaining backward compatibility.

Review Checklist:

  • Analyze implementation approach
  • Review error handling
  • Check test coverage
  • Evaluate performance implications
  • Security assessment
  • Code quality and best practices

Summary

Overall Assessment:Approved with minor suggestions

This PR successfully implements lazy initialization of Redis streams and consumer groups, moving from proactive creation to on-demand creation. This eliminates noisy BUSYGROUP errors in production logs while maintaining robust error handling.

Strengths

1. Clean Architecture 🏗️

  • The lazy initialization pattern is implemented cleanly without breaking existing functionality
  • Proper separation of concerns with _ensure_stream_and_group() method
  • Maintains backward compatibility for all existing users

2. Comprehensive Error Handling 🛡️

  • Correctly handles NOGROUP errors in both xautoclaim (src/docket/worker.py:339) and xreadgroup (src/docket/worker.py:362)
  • Includes workaround for redis-py 7.0 bug where pipeline errors lose the original message (src/docket/docket.py:1020)
  • Handles BUSYGROUP errors gracefully when multiple workers race to create the group

3. Excellent Test Coverage

  • Tests verify lazy bootstrap behavior (tests/test_docket.py:690-710)
  • Tests confirm idempotency of group creation (tests/test_docket.py:671-687)
  • Tests cover NOGROUP error handling in both worker operations (tests/test_worker.py:992+)
  • Maintains 100% code coverage requirement

Potential Improvements

1. Minor: Consider Retry Logic Enhancement

In worker.py lines 339-341 and 362-364, the recursive retry after NOGROUP could theoretically cause stack issues if the group creation repeatedly fails. Consider a retry limit:

async def get_redeliveries(redis: Redis, retry_count: int = 0) -> RedisReadGroupResponse:
    if retry_count > 3:
        raise RuntimeError("Failed to create consumer group after multiple attempts")
    # ... existing code ...

2. Documentation: Add Migration Note

Consider adding a comment in docket.py explaining the migration from proactive to lazy initialization for future maintainers.

3. Chaos Driver Enhancement

Good addition of NOGROUP handling in chaos/driver.py:250-255. This ensures the chaos testing continues to work with the new lazy initialization.

Performance Considerations ⚡

Positive Impact:

  • Eliminates unnecessary Redis calls during Docket initialization when only producing tasks
  • Reduces connection overhead for task producers
  • No performance regression for workers (group creation happens once on first task)

Security Assessment 🔒

No security concerns identified:

  • No new attack vectors introduced
  • Error messages don't leak sensitive information
  • Maintains existing Redis authentication/authorization patterns

Code Quality 📊

High quality implementation:

  • Clear, readable code with appropriate abstractions
  • Follows project conventions and style
  • Well-structured error handling
  • Comprehensive test coverage maintained at 100%

Minor Observations

  1. FakeRedis Workaround: The proactive group creation for memory:// backend (src/docket/docket.py:1000-1002) is a pragmatic solution to the fakeredis TypeError bug. Consider filing an issue with fakeredis if not already done.

  2. Redis-py Bug Workaround: The XPENDING string check (line 1020) is a clever workaround for the redis-py 7.0 pipeline error bug. Consider adding the redis-py issue number in the comment if available.

Conclusion

This is a solid implementation that solves a real production issue (#206) elegantly. The lazy initialization pattern is the right approach here, and the implementation is robust with proper error handling and test coverage. The code is production-ready.

Recommendation: Merge

Great work on this improvement! The lazy initialization will make Docket more efficient in production environments.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 9, 2025

📚 Documentation has been built for this PR!

You can download the documentation directly here:
https://github.com/chrisguidry/docket/actions/runs/20073815863/artifacts/4814686942

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Dec 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (0acb295) to head (1b8d8e4).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##              main      #215    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           48        48            
  Lines         7600      7740   +140     
  Branches       397       402     +5     
==========================================
+ Hits          7600      7740   +140     
Flag Coverage Δ
python-3.10 100.00% <100.00%> (ø)
python-3.11 98.95% <96.05%> (-0.06%) ⬇️
python-3.12 100.00% <100.00%> (ø)
python-3.13 100.00% <100.00%> (ø)
python-3.14 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/docket/docket.py 100.00% <100.00%> (ø)
src/docket/worker.py 100.00% <100.00%> (ø)
tests/test_docket.py 100.00% <100.00%> (ø)
tests/test_worker.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Replace asyncio.TaskGroup with asyncio.gather for Python 3.10 compat
- Handle NOGROUP errors in chaos driver when workers haven't started yet

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chrisguidry chrisguidry merged commit 2507be2 into main Dec 9, 2025
58 checks passed
@chrisguidry chrisguidry deleted the forgiveness-bootstrap-206 branch December 9, 2025 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ask for forgiveness rather than permission to bootstrap the stream/group

2 participants