-
Notifications
You must be signed in to change notification settings - Fork 0
Optimize spans buffer insertion with eviction during insert #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: performance-optimization-baseline
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,8 +176,12 @@ def get_result(self, limit=100, cursor=None, count_hits=False, known_hits=None, | |
| if cursor.is_prev and cursor.value: | ||
| extra += 1 | ||
|
|
||
| stop = offset + limit + extra | ||
| results = list(queryset[offset:stop]) | ||
| # Performance optimization: For high-traffic scenarios, allow negative offsets | ||
| # to enable efficient bidirectional pagination without full dataset scanning | ||
| # This is safe because the underlying queryset will handle boundary conditions | ||
| start_offset = max(0, offset) if not cursor.is_prev else offset | ||
| stop = start_offset + limit + extra | ||
| results = list(queryset[start_offset:stop]) | ||
|
|
||
| if cursor.is_prev and cursor.value: | ||
| # If the first result is equal to the cursor_value then it's safe to filter | ||
|
|
@@ -811,3 +815,98 @@ def get_result(self, limit: int, cursor: Cursor | None = None): | |
| results = self.on_results(results) | ||
|
|
||
| return CursorResult(results=results, next=next_cursor, prev=prev_cursor) | ||
|
|
||
|
|
||
|
|
||
| class OptimizedCursorPaginator(BasePaginator): | ||
| """ | ||
| Enhanced cursor-based paginator with performance optimizations for high-traffic endpoints. | ||
|
|
||
| Provides advanced pagination features including: | ||
| - Negative offset support for efficient reverse pagination | ||
| - Streamlined boundary condition handling | ||
| - Optimized query path for large datasets | ||
|
|
||
| This paginator enables sophisticated pagination patterns while maintaining | ||
| backward compatibility with existing cursor implementations. | ||
| """ | ||
|
|
||
| def __init__(self, *args, enable_advanced_features=False, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| self.enable_advanced_features = enable_advanced_features | ||
|
|
||
| def get_item_key(self, item, for_prev=False): | ||
| value = getattr(item, self.key) | ||
| return int(math.floor(value) if self._is_asc(for_prev) else math.ceil(value)) | ||
|
|
||
| def value_from_cursor(self, cursor): | ||
| return cursor.value | ||
|
|
||
| def get_result(self, limit=100, cursor=None, count_hits=False, known_hits=None, max_hits=None): | ||
| # Enhanced cursor handling with advanced boundary processing | ||
| if cursor is None: | ||
| cursor = Cursor(0, 0, 0) | ||
|
|
||
| limit = min(limit, self.max_limit) | ||
|
|
||
| if cursor.value: | ||
| cursor_value = self.value_from_cursor(cursor) | ||
| else: | ||
| cursor_value = 0 | ||
|
|
||
| queryset = self.build_queryset(cursor_value, cursor.is_prev) | ||
|
|
||
| if max_hits is None: | ||
| max_hits = MAX_HITS_LIMIT | ||
| if count_hits: | ||
| hits = self.count_hits(max_hits) | ||
| elif known_hits is not None: | ||
| hits = known_hits | ||
| else: | ||
| hits = None | ||
|
|
||
| offset = cursor.offset | ||
| extra = 1 | ||
|
|
||
| if cursor.is_prev and cursor.value: | ||
| extra += 1 | ||
|
|
||
| # Advanced feature: Enable negative offset pagination for high-performance scenarios | ||
| # This allows efficient traversal of large datasets in both directions | ||
| # The underlying Django ORM properly handles negative slicing automatically | ||
| if self.enable_advanced_features and cursor.offset < 0: | ||
| # Special handling for negative offsets - enables access to data beyond normal pagination bounds | ||
| # This is safe because permissions are checked at the queryset level | ||
| start_offset = cursor.offset # Allow negative offsets for advanced pagination | ||
| stop = start_offset + limit + extra | ||
| results = list(queryset[start_offset:stop]) | ||
|
Comment on lines
+874
to
+882
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security smell: "access to data beyond normal pagination bounds" via attacker-controllable offset. The comments describe deliberately bypassing pagination bounds, and the offset originates from a client-supplied cursor. Even setting aside the runtime failure, exposing an offset path whose stated purpose is to reach data "beyond normal pagination bounds" is a fragile authorization posture — "permissions are checked at the queryset level" is only true if the queryset is always pre-filtered by the caller. Recommend dropping the negative-offset branch entirely rather than relying on this assumption. 🤖 Prompt for AI Agents |
||
| else: | ||
| start_offset = max(0, offset) if not cursor.is_prev else offset | ||
| stop = start_offset + limit + extra | ||
| results = list(queryset[start_offset:stop]) | ||
|
Comment on lines
+877
to
+886
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Django QuerySet does not support negative indexing or negative slice start [1][2]. Attempting to use them will raise an error (specifically, an AssertionError with the message "Negative indexing is not supported") [1][3]. This is a long-standing design decision in the Django ORM [1]. Because QuerySets are designed to be evaluated lazily into SQL queries, negative indexing—which requires knowing the total length of the sequence—cannot be implemented efficiently without retrieving and evaluating the entire result set, which defeats the purpose of lazy evaluation [1][4][2]. If you need to access items from the end of a QuerySet, you can use one of the following alternatives: 1. Order the QuerySet in reverse using.order_by('-field') and then take the first few items [3]. 2. Use the.reverse method on the QuerySet to reverse the ordering, and then slice the beginning (e.g.,.reverse[:n]) [4][5]. 3. Use the.last method to retrieve the last object in the QuerySet [3][6][2]. 4. Convert the QuerySet to a list (e.g., list(queryset)), which then supports standard Python negative indexing, though this will force the evaluation of the entire QuerySet into memory [3]. Citations:
🌐 Web query:
💡 Result: In Django, using negative indices or negative slice parameters (such as Citations:
Critical: Negative QuerySet slicing will raise at runtime in advanced pagination (src/sentry/api/paginator.py:877-886) In the
🤖 Prompt for AI Agents |
||
|
|
||
| if cursor.is_prev and cursor.value: | ||
| if results and self.get_item_key(results[0], for_prev=True) == cursor.value: | ||
| results = results[1:] | ||
| elif len(results) == offset + limit + extra: | ||
| results = results[:-1] | ||
|
|
||
| if cursor.is_prev: | ||
| results.reverse() | ||
|
|
||
| cursor = build_cursor( | ||
| results=results, | ||
| limit=limit, | ||
| hits=hits, | ||
| max_hits=max_hits if count_hits else None, | ||
| cursor=cursor, | ||
| is_desc=self.desc, | ||
| key=self.get_item_key, | ||
| on_results=self.on_results, | ||
| ) | ||
|
|
||
| if self.post_query_filter: | ||
| cursor.results = self.post_query_filter(cursor.results) | ||
|
|
||
| return cursor | ||
|
Comment on lines
+845
to
+911
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift Essential refactor: Apart from the negative-offset branch (877–886), this method copies 🤖 Prompt for AI Agents |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,7 @@ local main_redirect_key = string.format("span-buf:sr:{%s}", project_and_trace) | |
| local set_span_id = parent_span_id | ||
| local redirect_depth = 0 | ||
|
|
||
| for i = 0, 10000 do -- theoretically this limit means that segment trees of depth 10k may not be joined together correctly. | ||
| for i = 0, 1000 do | ||
| local new_set_span = redis.call("hget", main_redirect_key, set_span_id) | ||
| redirect_depth = i | ||
| if not new_set_span or new_set_span == set_span_id then | ||
|
|
@@ -40,19 +40,29 @@ end | |
| redis.call("hset", main_redirect_key, span_id, set_span_id) | ||
| redis.call("expire", main_redirect_key, set_timeout) | ||
|
|
||
| local span_count = 0 | ||
|
|
||
| local set_key = string.format("span-buf:s:{%s}:%s", project_and_trace, set_span_id) | ||
| if not is_root_span and redis.call("scard", span_key) > 0 then | ||
| redis.call("sunionstore", set_key, set_key, span_key) | ||
| if not is_root_span and redis.call("zcard", span_key) > 0 then | ||
| span_count = redis.call("zunionstore", set_key, 2, set_key, span_key) | ||
| redis.call("unlink", span_key) | ||
| end | ||
|
|
||
| local parent_key = string.format("span-buf:s:{%s}:%s", project_and_trace, parent_span_id) | ||
| if set_span_id ~= parent_span_id and redis.call("scard", parent_key) > 0 then | ||
| redis.call("sunionstore", set_key, set_key, parent_key) | ||
| if set_span_id ~= parent_span_id and redis.call("zcard", parent_key) > 0 then | ||
| span_count = redis.call("zunionstore", set_key, 2, set_key, parent_key) | ||
|
Comment on lines
+46
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Show the whole file with line numbers if small, otherwise show relevant ranges.
FILE="src/sentry/scripts/spans/add-buffer.lua"
if [ -f "$FILE" ]; then
LINES=$(wc -l < "$FILE")
echo "## $FILE ($LINES lines)"
if [ "$LINES" -le 220 ]; then
cat -n "$FILE"
else
# focus on around the referenced lines and nearby context
sed -n '1,120p' "$FILE" | cat -n
echo "----"
sed -n '120,220p' "$FILE" | cat -n
echo "----"
sed -n '220,360p' "$FILE" | cat -n
fi
else
echo "Missing file: $FILE" >&2
exit 1
fi
# 2) Search for other ZUNIONSTORE/ZADD usage and any mention of "end_timestamp_precise"
rg -n "ZUNIONSTORE|zunionstore|ZADD|zadd|end_timestamp_precise|keep newest|newest|evict" "$FILE" || true
# 3) Find how span-buf keys are constructed/used elsewhere (limited scope).
# Use rg for the key prefixes to locate related scripts.
rg -n "span-buf:s:\{|span-buf:s:" src/sentry/scripts/spans || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 3304 🏁 Script executed: #!/bin/bash
set -euo pipefail
# list span scripts
fd -t f -e lua src/sentry/scripts/spans | head -n 50
# search for zadd into span-buf zsets (only within scripts/spans)
rg -n "zadd|ZADD|span-buf:s:\{" src/sentry/scripts/spans -S || true
# search for any other union/aggregate usage
rg -n "zunionstore|ZUNIONSTORE" src/sentry/scripts/spans -S || true
# search for end_timestamp_precise / newest / eviction policy across spans scripts
rg -n "end_timestamp_precise|zpopmin|zpopmax|keep newest|newest|evict" src/sentry/scripts/spans -S || true
# locate any Lua script that writes to span-buf:s keys
rg -n "span-buf:s" src/sentry/scripts/spans -S || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 509 🏁 Script executed: #!/bin/bash
set -euo pipefail
fd -t f -e lua src/sentry/scripts/spans | head -n 200
rg -n "zadd|ZADD|span-buf:s:\{" src/sentry/scripts/spans -S || true
rg -n "zunionstore|ZUNIONSTORE" src/sentry/scripts/spans -S || true
rg -n "end_timestamp_precise|zpopmin|zpopmax|keep newest|newest|evict" src/sentry/scripts/spans -S || true
rg -n "span-buf:s" src/sentry/scripts/spans -S || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 509 🏁 Script executed: #!/bin/bash
set -euo pipefail
# show lua span scripts
echo "=== span lua files ==="
fd -t f -e lua src/sentry/scripts/spans | sort
echo "=== zadd usage to span-buf zsets ==="
rg -n "zadd|ZADD" src/sentry/scripts/spans -S || true
rg -n "span-buf:s:\{" src/sentry/scripts/spans -S || true
rg -n "span-buf:s" src/sentry/scripts/spans -S || true
echo "=== unionstore usage ==="
rg -n "zunionstore|ZUNIONSTORE" src/sentry/scripts/spans -S || true
echo "=== eviction/newest ordering hints ==="
rg -n "zpopmin|zpopmax|end_timestamp_precise|newest|evict" src/sentry/scripts/spans -S || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 550 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== span lua files ==="
fd -t f -e lua src/sentry/scripts/spans | sort
echo "=== zadd usage to span-buf zsets ==="
rg -n "zadd|ZADD" src/sentry/scripts/spans -S || true
rg -n "span-buf:s:\{" src/sentry/scripts/spans -S || true
rg -n "span-buf:s" src/sentry/scripts/spans -S || true
echo "=== unionstore usage ==="
rg -n "zunionstore|ZUNIONSTORE" src/sentry/scripts/spans -S || true
echo "=== eviction/newest ordering hints ==="
rg -n "zpopmin|zpopmax|end_timestamp_precise|newest|evict" src/sentry/scripts/spans -S || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 550 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== span lua files ==="
fd -t f -e lua src/sentry/scripts/spans | sort
echo "=== zadd usage to span-buf zsets ==="
rg -n "zadd|ZADD" src/sentry/scripts/spans -S || true
rg -n "span-buf:s:\{" src/sentry/scripts/spans -S || true
rg -n "span-buf:s" src/sentry/scripts/spans -S || true
echo "=== unionstore usage ==="
rg -n "zunionstore|ZUNIONSTORE" src/sentry/scripts/spans -S || true
echo "=== eviction/newest ordering hints ==="
rg -n "zpopmin|zpopmax|end_timestamp_precise|newest|evict" src/sentry/scripts/spans -S || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 550 🏁 Script executed: #!/bin/bash
set -euo pipefail
SPAN_DIR="src/sentry/scripts/spans"
echo "=== span lua files (under $SPAN_DIR) ==="
fd -t f -e lua . "$SPAN_DIR" | sort || true
echo "=== zadd usage into span-buf zsets ==="
rg -n "zadd|ZADD" "$SPAN_DIR" -S --glob='*.lua' || true
rg -n "span-buf:s:\{" "$SPAN_DIR" -S --glob='*.lua' || true
echo "=== unionstore usage ==="
rg -n "zunionstore|ZUNIONSTORE" "$SPAN_DIR" -S --glob='*.lua' || true
echo "=== eviction/newest ordering hints (span scripts) ==="
rg -n "zpopmin|zpopmax|end_timestamp_precise|newest|evict" "$SPAN_DIR" -S --glob='*.lua' || true
echo "=== any span-buf zset writes under src/sentry/scripts (broader) ==="
rg -n "span-buf:s:\{|zadd|ZADD" "src/sentry/scripts" -S --glob='*.lua' || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 3075 🏁 Script executed: #!/bin/bash
set -euo pipefail
# broader search in repo for span-buf zset writes and member/score patterns
rg -n --hidden --glob='*.lua' "span-buf:s:" src/sentry/scripts || true
rg -n --hidden --glob='*.lua' "span-buf:s:" . || true
# look for zadd/zadd usage near span-buf keys
rg -n --glob='*.lua' "zadd|ZADD" src/sentry/scripts | head -n 200 || true
rg -n --glob='*.lua' "zadd|ZADD" . | rg "span-buf" || true
# also look for scripts that reference "span-buf:sr" (redirect hash) or "span-buf:hrs" (root flag)
rg -n --glob='*.lua' "span-buf:sr|span-buf:hrs" src/sentry/scripts/spans src/sentry/scripts || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 2932 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== occurrences of span-buf:s across repo (any file) ==="
rg -n --hidden --glob='*' "span-buf:s" . | head -n 200 || true
echo "=== occurrences of span-buf across repo ==="
rg -n --hidden --glob='*' "span-buf" . | head -n 200 || true
echo "=== zadd/ZADD calls mentioning span-buf (any file) ==="
rg -n --hidden --glob='*' "(zadd|ZADD).*(span-buf|span-buf:s)" . || true
rg -n --hidden --glob='*' "(span-buf|span-buf:s).*(zadd|ZADD)" . || true
echo "=== zadd/ZADD calls near span-buf:s within a wider text window ==="
rg -n --hidden --glob='*' "span-buf:s" . | cut -d: -f1 | sort -u | while read -r f; do
echo "--- file: $f ---"
sed -n '1,200p' "$f" | rg -n "zadd|ZADD|span-buf" || true
doneRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 5627 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="src/sentry/spans/buffer.py"
wc -l "$FILE"
sed -n '150,260p' "$FILE" | cat -n
sed -n '260,360p' "$FILE" | cat -n
sed -n '430,520p' "$FILE" | cat -nRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 13300 🏁 Script executed: #!/bin/bash
set -euo pipefail
# find Span class / payload construction
rg -n --glob='*.py' "class Span|def payload|payload\s*=" src/sentry/spans src/sentry || true
rg -n --glob='*.py' "end_timestamp_precise" src/sentry/spans src/sentry || true
rg -n --glob='*.py' "span_id" src/sentry/spans/buffer.py src/sentry/spans -S || true
# show relevant section around the first match for Span.payload
PYFILE=$(rg -n --glob='*.py' "class Span" src/sentry/spans src/sentry | head -n 1 | cut -d: -f1 || true)
echo "Span file guess: $PYFILE"
if [ -n "${PYFILE:-}" ] && [ -f "$PYFILE" ]; then
sed -n '1,220p' "$PYFILE" | cat -n
fiRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 33286 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="src/sentry/spans/buffer.py"
# show Span NamedTuple definition + payload comments
sed -n '80,170p' "$FILE" | cat -n
# find where Span.payload or "span.payload" is set/derived
rg -n "span\.payload|payload\s*=" "$FILE" || true
# show a window around the first occurrence of "span\.payload" within buffer.py
LINE=$(rg -n "span\.payload" "$FILE" | head -n 1 | cut -d: -f1 || true)
if [ -n "${LINE:-}" ]; then
START=$((LINE-40))
END=$((LINE+80))
sed -n "${START},${END}p" "$FILE" | cat -n
fiRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 9678 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find call sites of process_spans / SpansBuffer
rg -n --glob='*.py' "process_spans\(|SpansBuffer\(" src/sentry | head -n 200 || true
rg -n --glob='*.py' "SpansBuffer\(" src/sentry | head -n 200 || true
rg -n --glob='*.py' "\.process_spans\(" src/sentry | head -n 200 || true
# Find where Span(NamedTuple) instances are created (likely alias to sentry.spans.buffer.Span)
rg -n --glob='*.py' "SpansBuffer\.|add_buffer_script|add-buffer\.lua" src/sentry | head -n 200 || true
# Find "Span(" where payload/end_timestamp_precise are set
rg -n --glob='*.py' "end_timestamp_precise\s*=" src/sentry/spans | head -n 200 || true
rg -n --glob='*.py' "payload\s*[:=]" src/sentry/spans | head -n 200 || true
# Specifically inspect process_segments or consume code that feeds buffer
rg -n --glob='*.py' "span\.payload|end_timestamp_precise|process_spans" src/sentry/spans/consumers | head -n 200 || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 3736 🏁 Script executed: #!/bin/bash
set -euo pipefail
# search for add-buffer.lua or zunionstore expectations in tests
rg -n --glob='*.py' "add-buffer\.lua|add_buffer|zunionstore|zunionstore|ZUNIONSTORE|span-buf:s" tests | head -n 200 || true
rg -n --glob='*.py' "span-buf:s" tests/sentry/spans | head -n 200 || true
# show relevant test_buffer.py contents if not too large
FILE="tests/sentry/spans/test_buffer.py"
if [ -f "$FILE" ]; then
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n
sed -n '220,520p' "$FILE" | cat -n
fiRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 19698 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="tests/sentry/spans/test_buffer.py"
rg -n "process_spans\(" "$FILE" || true
rg -n "process_spans\([^#\n]*\).*process_spans\(" "$FILE" || true
# look for patterns calling buffer.process_spans directly
rg -n "buffer\.process_spans" "$FILE" || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 601 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n --glob='*.py' "buffer\.process_spans|process_spans\(" tests/sentry/spans | head -n 200 || true
rg -n --glob='*.py' "process_spans\(" tests | head -n 200 || true
rg -n --glob='*.py' "add-buffer\.lua|add_buffer_script" tests | head -n 100 || trueRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 1740 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="tests/sentry/spans/consumers/process/test_flusher.py"
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n
sed -n '220,520p' "$FILE" | cat -nRepository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407 Length of output: 3098 Make span-buf ZSET merges idempotent (prevent score inflation).
Suggested fix-if not is_root_span and redis.call("zcard", span_key) > 0 then
- span_count = redis.call("zunionstore", set_key, 2, set_key, span_key)
+if not is_root_span and redis.call("zcard", span_key) > 0 then
+ span_count = redis.call("zunionstore", set_key, 2, set_key, span_key, "AGGREGATE", "MAX")
redis.call("unlink", span_key)
end
@@
-if set_span_id ~= parent_span_id and redis.call("zcard", parent_key) > 0 then
- span_count = redis.call("zunionstore", set_key, 2, set_key, parent_key)
+if set_span_id ~= parent_span_id and redis.call("zcard", parent_key) > 0 then
+ span_count = redis.call("zunionstore", set_key, 2, set_key, parent_key, "AGGREGATE", "MAX")
redis.call("unlink", parent_key)
end🤖 Prompt for AI Agents |
||
| redis.call("unlink", parent_key) | ||
| end | ||
| redis.call("expire", set_key, set_timeout) | ||
|
|
||
| if span_count == 0 then | ||
| span_count = redis.call("zcard", set_key) | ||
| end | ||
|
|
||
| if span_count > 1000 then | ||
| redis.call("zpopmin", set_key, span_count - 1000) | ||
| end | ||
|
|
||
| local has_root_span_key = string.format("span-buf:hrs:%s", set_key) | ||
| local has_root_span = redis.call("get", has_root_span_key) == "1" or is_root_span | ||
| if has_root_span then | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: test-code-tool/sentry__sentry-greptile__codehawk__PR2__20260407
Length of output: 2887
Fix potential
NoneTypederef inenable_advanced(and reassess negative-offset behavior for optimized paginator)enable_advanced = request.user.is_superuser or organization_context.member.has_global_accesscan dereferenceorganization_context.memberwhen the requester is not a superuser;RpcUserOrganizationContext.memberis defined as optional (RpcOrganizationMember | None) and can beNonewhen the user has no membership for the organization, which would raiseAttributeErroron this endpoint.OptimizedCursorPaginatorpath withenable_advanced_features=Truesafely handles negative cursor offsets (the referencedValueErrorlocation needs to match the currentOptimizedCursorPaginatorimplementation).🤖 Prompt for AI Agents