Skip to content

Commit 01e267e

Browse files
fix(redis): add compatibility with redis version 6.2.0 (#14475)
Github Issue: #13598 - Fix Redis asyncio cluster pipeline tracing for redis-py ≥ 6.2.0 - now supports both command_stack and _command_stack since _command_stack was removed - Prevents AttributeError and ensures pipelines are traced correctly across versions ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent 8824931 commit 01e267e

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

ddtrace/contrib/internal/redis/asyncio_patch.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ async def instrumented_async_execute_cluster_pipeline(func, instance, args, kwar
3030
if not pin or not pin.enabled():
3131
return await func(*args, **kwargs)
3232

33-
cmds = [stringify_cache_args(c.args, cmd_max_len=config.redis.cmd_max_length) for c in instance._command_stack]
33+
# Try to access command_stack, fallback to _command_stack for backward compatibility
34+
command_stack = getattr(instance, "command_stack", None)
35+
if command_stack is None:
36+
command_stack = getattr(instance, "_command_stack", [])
37+
38+
cmds = [stringify_cache_args(c.args, cmd_max_len=config.redis.cmd_max_length) for c in command_stack]
3439
with _instrument_redis_execute_pipeline(pin, config.redis, cmds, instance):
3540
return await func(*args, **kwargs)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
redis: Fix pipeline compatibility with redis >= 6.2.0 after ``_command_stack`` removal.

tests/contrib/redis/test_redis_cluster_asyncio.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,41 @@ async def test_pipeline(traced_redis_cluster):
115115
assert span.get_metric("redis.pipeline_length") == 3
116116

117117

118+
@pytest.mark.snapshot(wait_for_num_traces=1)
119+
@pytest.mark.asyncio
120+
async def test_pipeline_command_stack_count_matches_metric(redis_cluster):
121+
patch()
122+
try:
123+
async with redis_cluster.pipeline(transaction=False) as p:
124+
p.set("a", 1)
125+
p.get("a")
126+
await p.execute()
127+
finally:
128+
unpatch()
129+
130+
131+
@pytest.mark.asyncio
132+
async def test_pipeline_command_stack_parity_when_visible(traced_redis_cluster):
133+
cluster, test_spans = traced_redis_cluster
134+
async with cluster.pipeline(transaction=False) as p:
135+
p.set("a", 1)
136+
p.get("a")
137+
queued = None
138+
if hasattr(p, "command_stack"):
139+
queued = len(p.command_stack)
140+
elif hasattr(p, "_command_stack"):
141+
queued = len(p._command_stack)
142+
await p.execute()
143+
144+
traces = test_spans.pop_traces()
145+
spans = traces[0]
146+
span = spans[0]
147+
assert span.resource == "SET\nGET"
148+
assert span.get_metric("redis.pipeline_length") == 2
149+
if queued is not None:
150+
assert span.get_metric("redis.pipeline_length") == queued
151+
152+
118153
@pytest.mark.skipif(redis.VERSION < (4, 3, 0), reason="redis.asyncio.cluster is not implemented in redis<4.3.0")
119154
@pytest.mark.asyncio
120155
async def test_patch_unpatch(redis_cluster):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[[
2+
{
3+
"name": "redis.command",
4+
"service": "redis",
5+
"resource": "SET\nGET",
6+
"trace_id": 0,
7+
"span_id": 1,
8+
"parent_id": 0,
9+
"type": "redis",
10+
"error": 0,
11+
"meta": {
12+
"_dd.base_service": "tests.contrib.redis",
13+
"_dd.p.dm": "-0",
14+
"_dd.p.tid": "68b8917800000000",
15+
"component": "redis",
16+
"db.system": "redis",
17+
"language": "python",
18+
"redis.raw_command": "SET a 1\nGET a",
19+
"runtime-id": "73c800bba8084530a15a15dbcea1a8bc",
20+
"span.kind": "client"
21+
},
22+
"metrics": {
23+
"_dd.measured": 1,
24+
"_dd.top_level": 1,
25+
"_dd.tracer_kr": 1.0,
26+
"_sampling_priority_v1": 1,
27+
"process_id": 4422,
28+
"redis.pipeline_length": 2
29+
},
30+
"duration": 1411000,
31+
"start": 1756926328130176000
32+
}]]

0 commit comments

Comments
 (0)