Skip to content

Commit 13ddc96

Browse files
committed
perf: performance-driven re-design
BREAKING CHANGE: This release introduces significant performance improvements that change the internal storage format and key structure. Checkpoints created with earlier versions are incompatible with v0.1.0. Key performance improvements: - Replace some FT.SEARCH operations with sorted sets for write tracking - Add checkpoint-based key registry eliminating expensive SCAN/KEYS operations - Implement multi-level caching for frequently accessed keys and data - Optimize batch operations with pipelined Redis commands - Add lazy TTL refresh to reduce unnecessary operations - Improve index schemas for better query performance Architectural changes: - New CheckpointKeyRegistry tracks writes per checkpoint using sorted sets - Cached key generation methods reduce string concatenation overhead - Batch loading methods for pending writes and sends - Optimized get_tuple with direct document access patterns - Improved TTL management with threshold-based refresh Testing improvements: - Add comprehensive test coverage for new registry functionality - Test TTL behaviors, caching mechanisms, and error paths - Add integration tests for blob handling and metadata operations - Improve test isolation using unique thread IDs instead of flushdb The new architecture provides: - 50-70% reduction in Redis operations for typical workflows - Better scalability with checkpoint-scoped write tracking - Reduced memory footprint through efficient caching - Improved cluster mode compatibility
1 parent 8e87eba commit 13ddc96

File tree

77 files changed

+10843
-3827
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+10843
-3827
lines changed

.github/workflows/claude.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ name: Claude Code
22

33
on:
44
issue_comment:
5-
types: [created]
5+
types: [ created ]
66
pull_request_review_comment:
7-
types: [created]
7+
types: [ created ]
88
issues:
9-
types: [opened, assigned]
9+
types: [ opened, assigned ]
1010
pull_request_review:
11-
types: [submitted]
11+
types: [ submitted ]
1212

1313
jobs:
1414
claude:

.github/workflows/lint.yml

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
name: Lint
32

43
on:
@@ -30,18 +29,18 @@ jobs:
3029
- "3.12"
3130

3231
steps:
33-
- uses: actions/checkout@v2
34-
- name: Set up Python ${{ matrix.python-version }}
35-
uses: actions/setup-python@v2
36-
with:
37-
python-version: ${{ matrix.python-version }}
38-
- name: Install Poetry
39-
uses: snok/install-poetry@v1
40-
with:
41-
version: ${{ env.POETRY_VERSION }}
42-
- name: Install dependencies
43-
run: |
44-
poetry install --all-extras
45-
- name: lint
46-
run: |
47-
make lint
32+
- uses: actions/checkout@v2
33+
- name: Set up Python ${{ matrix.python-version }}
34+
uses: actions/setup-python@v2
35+
with:
36+
python-version: ${{ matrix.python-version }}
37+
- name: Install Poetry
38+
uses: snok/install-poetry@v1
39+
with:
40+
version: ${{ env.POETRY_VERSION }}
41+
- name: Install dependencies
42+
run: |
43+
poetry install --all-extras
44+
- name: lint
45+
run: |
46+
make lint

.github/workflows/release-drafter.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
branches:
77
- main
88

9-
permissions: {}
9+
permissions: { }
1010
jobs:
1111
update_release_draft:
1212
permissions:
@@ -19,6 +19,6 @@ jobs:
1919
- uses: release-drafter/release-drafter@v5
2020
with:
2121
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
22-
config-name: release-drafter-config.yml
22+
config-name: release-drafter-config.yml
2323
env:
2424
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Publish Release
22

33
on:
44
release:
5-
types: [published]
5+
types: [ published ]
66

77
env:
88
PYTHON_VERSION: "3.11"

.github/workflows/test.yml

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,45 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
python-version: [3.9, '3.10', 3.11, 3.12, 3.13]
28-
redis-version: ['6.2.6-v9', 'latest', '8.0.2']
27+
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]
28+
redis-version: [ '6.2.6-v9', 'latest', '8.0.2' ]
2929

3030
steps:
31-
- name: Check out repository
32-
uses: actions/checkout@v3
31+
- name: Check out repository
32+
uses: actions/checkout@v3
3333

34-
- name: Set up Python ${{ matrix.python-version }}
35-
uses: actions/setup-python@v4
36-
with:
37-
python-version: ${{ matrix.python-version }}
38-
cache: 'pip'
34+
- name: Set up Python ${{ matrix.python-version }}
35+
uses: actions/setup-python@v4
36+
with:
37+
python-version: ${{ matrix.python-version }}
38+
cache: 'pip'
3939

40-
- name: Install Poetry
41-
uses: snok/install-poetry@v1
42-
with:
43-
version: ${{ env.POETRY_VERSION }}
40+
- name: Install Poetry
41+
uses: snok/install-poetry@v1
42+
with:
43+
version: ${{ env.POETRY_VERSION }}
4444

45-
- name: Install dependencies
46-
run: |
47-
pip wheel --no-cache-dir --use-pep517 ml-dtypes
48-
poetry install --all-extras
45+
- name: Install dependencies
46+
run: |
47+
pip wheel --no-cache-dir --use-pep517 ml-dtypes
48+
poetry install --all-extras
4949
50-
- name: Set Redis image name
51-
run: |
52-
if [[ "${{ matrix.redis-version }}" == "8.0.2" ]]; then
53-
echo "REDIS_IMAGE=redis:${{ matrix.redis-version }}" >> $GITHUB_ENV
54-
else
55-
echo "REDIS_IMAGE=redis/redis-stack-server:${{ matrix.redis-version }}" >> $GITHUB_ENV
56-
fi
50+
- name: Set Redis image name
51+
run: |
52+
if [[ "${{ matrix.redis-version }}" == "8.0.2" ]]; then
53+
echo "REDIS_IMAGE=redis:${{ matrix.redis-version }}" >> $GITHUB_ENV
54+
else
55+
echo "REDIS_IMAGE=redis/redis-stack-server:${{ matrix.redis-version }}" >> $GITHUB_ENV
56+
fi
5757
58-
- name: Run API tests
59-
if: matrix.redis-version == 'latest'
60-
env:
61-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
62-
run: |
63-
make test-all
58+
- name: Run API tests
59+
if: matrix.redis-version == 'latest'
60+
env:
61+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
62+
run: |
63+
make test-all
6464
65-
- name: Run tests
66-
if: matrix.redis-version != 'latest'
67-
run: |
68-
make test
65+
- name: Run tests
66+
if: matrix.redis-version != 'latest'
67+
run: |
68+
make test

CLAUDE.md

Lines changed: 124 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,94 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5+
## How to Write Tests for Coverage
6+
7+
When improving test coverage, follow these principles:
8+
9+
1. **Focus on Integration Tests**: Write tests that use real Redis instances and test actual usage scenarios. Unit tests
10+
are secondary to integration tests.
11+
12+
2. **Test What Code SHOULD Do**: Don't write tests that mirror what the code currently does. Test against the expected
13+
behavior and requirements.
14+
15+
3. **Use Meaningful Test Names**: Test names should describe the behavior being tested, not generic names like "
16+
test_function_x".
17+
18+
4. **Research Before Writing**: Find and understand existing tests for the feature/area before adding new tests.
19+
20+
5. **Test Error Paths and Edge Cases**: Focus on uncovered error handling, boundary conditions, and edge cases.
21+
22+
6. **Run Tests Incrementally**: Run `make test-all` after every 5 tests to ensure no regressions.
23+
24+
7. **Avoid "Ugly Mirror" Testing**: Don't create tests that simply verify the current implementation. Test the contract
25+
and expected behavior.
26+
27+
Example of a good integration test for error handling:
28+
29+
```python
30+
def test_malformed_base64_blob_handling(redis_url: str) -> None:
31+
"""Test handling of malformed base64 data in blob decoding."""
32+
with _saver(redis_url) as saver:
33+
# Set up real scenario
34+
# Test error condition
35+
# Verify graceful handling
36+
```
37+
38+
## CRITICAL: Always Use TestContainers for Redis
39+
40+
**NEVER use Docker directly or manually start Redis containers!** All tests, benchmarks, and profiling scripts MUST use
41+
TestContainers. The library handles container lifecycle automatically.
42+
43+
```python
44+
from testcontainers.redis import RedisContainer
45+
46+
# Use redis:8 (has all required modules) or redis/redis-stack-server:latest
47+
redis_container = RedisContainer("redis:8")
48+
redis_container.start()
49+
try:
50+
redis_url = f"redis://{redis_container.get_container_host_ip()}:{redis_container.get_exposed_port(6379)}"
51+
# Use redis_url...
52+
finally:
53+
redis_container.stop()
54+
```
55+
556
## Development Commands
657

758
### Setup and Dependencies
859

960
```bash
1061
poetry install --all-extras # Install all dependencies with poetry (from README)
11-
make redis-start # Start Redis Stack container (includes RedisJSON and RediSearch)
12-
make redis-stop # Stop Redis container
1362
```
1463

1564
### Testing
1665

1766
```bash
18-
make test # Run tests with verbose output
19-
make test-all # Run all tests including API tests
67+
make test-all # PREFERRED: Run all tests including API tests when evaluating changes
68+
make test # Run tests with verbose output
69+
make test-coverage # Run tests with coverage
70+
make coverage-report # Show coverage report in terminal
71+
make coverage-html # Generate HTML coverage report
2072
pytest tests/test_specific.py # Run specific test file
2173
pytest tests/test_specific.py::test_function # Run specific test
2274
pytest --run-api-tests # Include API integration tests
2375
```
2476

77+
**Important**: Always use `make test-all` when evaluating changes to ensure all tests pass, including API integration
78+
tests.
79+
80+
Note: Tests automatically use TestContainers for Redis - do not manually start Redis containers.
81+
2582
### Code Quality
2683

2784
```bash
2885
make format # Format code with black and isort
2986
make lint # Run formatting, type checking, and other linters
3087
make check-types # Run mypy type checking
3188
make check # Run both linting and tests
89+
make find-dead-code # Find unused code with vulture
90+
poetry run check-format # Check formatting without modifying
91+
poetry run check-sort-imports # Check import sorting
92+
poetry run check-lint # Run all linting checks
3293
```
3394

3495
### Development
@@ -37,6 +98,19 @@ make check # Run both linting and tests
3798
make clean # Remove cache and build artifacts
3899
```
39100

101+
## Code Style Guidelines
102+
103+
- Use Black for formatting with target versions py39-py313
104+
- Sort imports with isort (black profile)
105+
- Strict typing required (disallow_untyped_defs=True)
106+
- Follow PEP 8 naming conventions (snake_case for functions/variables)
107+
- Type annotations required for all function parameters and return values
108+
- Explicit error handling with descriptive error messages
109+
- Test all functionality with both sync and async variants
110+
- Maintain test coverage with pytest
111+
- Use contextlib for resource management
112+
- Document public APIs with docstrings
113+
40114
## Architecture Overview
41115

42116
### Core Components
@@ -47,6 +121,8 @@ make clean # Remove cache and build artifacts
47121
- `__init__.py`: `RedisSaver` - Standard sync implementation
48122
- `aio.py`: `AsyncRedisSaver` - Async implementation
49123
- `shallow.py` / `ashallow.py`: Shallow variants that store only latest checkpoint
124+
- `key_registry.py`: Checkpoint key registry using sorted sets for efficient write tracking
125+
- `scan_utils.py`: Utilities for efficient key scanning and pattern matching
50126

51127
**Stores** (`langgraph/store/redis/`):
52128

@@ -56,17 +132,26 @@ make clean # Remove cache and build artifacts
56132

57133
### Key Architecture Patterns
58134

59-
**Dual Implementation Strategy**: Each major component has both sync and async variants that share common base classes. The base classes (`BaseRedisSaver`, `BaseRedisStore`) contain the bulk of the business logic, while concrete implementations handle Redis client management and specific I/O patterns.
135+
**Dual Implementation Strategy**: Each major component has both sync and async variants that share common base classes.
136+
The base classes (`BaseRedisSaver`, `BaseRedisStore`) contain the bulk of the business logic, while concrete
137+
implementations handle Redis client management and specific I/O patterns.
60138

61-
**Redis Module Dependencies**: The library requires RedisJSON and RediSearch modules. Redis 8.0+ includes these by default; earlier versions need Redis Stack. All operations use structured JSON storage with search indices for efficient querying.
139+
**Redis Module Dependencies**: The library requires RedisJSON and RediSearch modules. Redis 8.0+ includes these by
140+
default; earlier versions need Redis Stack. All operations use structured JSON storage with search indices for efficient
141+
querying.
62142

63-
**Schema-Driven Indexing**: Both checkpoints and stores use predefined schemas (`SCHEMAS` constants) that define Redis Search indices. Checkpoint indices track thread/namespace/version hierarchies; store indices support both key-value lookup and optional vector similarity search.
143+
**Schema-Driven Indexing**: Both checkpoints and stores use predefined schemas (`SCHEMAS` constants) that define Redis
144+
Search indices. Checkpoint indices track thread/namespace/version hierarchies; store indices support both key-value
145+
lookup and optional vector similarity search.
64146

65-
**TTL Integration**: Native Redis TTL support is integrated throughout, with configurable defaults and refresh-on-read capabilities. TTL applies to all related keys (main document, vectors, writes) atomically.
147+
**TTL Integration**: Native Redis TTL support is integrated throughout, with configurable defaults and refresh-on-read
148+
capabilities. TTL applies to all related keys (main document, vectors, writes) atomically.
66149

67-
**Cluster Support**: Full Redis Cluster support with automatic detection and cluster-aware operations (individual key operations vs. pipelined operations).
150+
**Cluster Support**: Full Redis Cluster support with automatic detection and cluster-aware operations (individual key
151+
operations vs. pipelined operations).
68152

69-
**Type System**: Heavy use of generics (`BaseRedisSaver[RedisClientType, IndexType]`) to maintain type safety across sync/async variants while sharing implementation code.
153+
**Type System**: Heavy use of generics (`BaseRedisSaver[RedisClientType, IndexType]`) to maintain type safety across
154+
sync/async variants while sharing implementation code.
70155

71156
### Redis Key Patterns
72157

@@ -82,10 +167,36 @@ Tests are organized by functionality:
82167

83168
- `test_sync.py` / `test_async.py`: Core checkpoint functionality
84169
- `test_store.py` / `test_async_store.py`: Store operations
85-
- `test_cluster_mode.py`: Redis Cluster specific tests
86-
- `test_*_ttl.py`: TTL functionality
87-
- `test_key_parsing.py`: Key generation and parsing logic
170+
- `test_cluster_mode.py` / `test_async_cluster_mode.py`: Redis Cluster specific tests
171+
- `test_checkpoint_ttl.py`: TTL functionality for checkpoints
172+
- `test_key_parsing.py` / `test_subgraph_key_parsing.py`: Key generation and parsing logic
88173
- `test_semantic_search_*.py`: Vector search capabilities
174+
- `test_interruption.py` / `test_streaming*.py`: Advanced workflow tests
175+
- `test_shallow_*.py`: Shallow checkpoint implementation tests
176+
- `test_decode_responses.py`: Redis response decoding tests
177+
- `test_crossslot_integration.py`: Cross-slot operation tests
178+
179+
## Notebooks and Examples
180+
181+
The `examples/` directory contains Jupyter notebooks demonstrating Redis integration with LangGraph:
182+
183+
- All notebooks MUST use Redis implementations (RedisSaver, RedisStore), not in-memory equivalents
184+
- Notebooks can be run via Docker Compose: `cd examples && docker compose up`
185+
- Each notebook includes installation of required dependencies within the notebook cells
186+
- TestContainers should be used for any new notebook examples requiring Redis
187+
188+
### Running Notebooks
189+
190+
1. With Docker (recommended):
191+
```bash
192+
cd examples
193+
docker compose up
194+
```
195+
196+
2. Locally:
197+
- Ensure Redis is running with required modules (RedisJSON, RediSearch)
198+
- Install dependencies: `pip install langgraph-checkpoint-redis jupyter`
199+
- Run: `jupyter notebook`
89200

90201
### Important Dependencies
91202

0 commit comments

Comments
 (0)