Skip to content

Benchmark: sentry PR 95633 - #11

Open
celmis-codereviewer wants to merge 8 commits into
cr-base-95633from
cr-pr-95633
Open

Benchmark: sentry PR 95633#11
celmis-codereviewer wants to merge 8 commits into
cr-base-95633from
cr-pr-95633

Conversation

@celmis-codereviewer

Copy link
Copy Markdown

Benchmark reproduction of getsentry#95633

wedamija added 8 commits July 16, 2025 12:42
One potential problem we have with batch processing is that any one slow item will clog up the whole batch. This pr implements a queueing method instead, where we keep N queues that each have their own workers. There's still a chance of individual items backlogging a queue, but we can try increased concurrency here to reduce the chances of that happening

@celmis-codereviewer celmis-codereviewer left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

💬 COMMENT — findings to consider

Full findings and scope are in the review summary comment on this pull request — one persistent comment, updated in place on every run.

celmis-codereviewer

This comment was marked as outdated.

celmis-codereviewer

This comment was marked as outdated.

celmis-codereviewer

This comment was marked as outdated.

celmis-codereviewer

This comment was marked as outdated.

@celmis-codereviewer celmis-codereviewer left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

CHANGES REQUESTED — blocking findings

Full findings and scope are in the review summary comment on this pull request — one persistent comment, updated in place on every run.

min_offset = min(all_offsets)
max_offset = max(all_offsets)

start = max(last_committed + 1, min_offset)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why: When min_offset exceeds last_committed + 1 on line 86, start skips unrecorded preceding offsets, causing get_committable_offsets to prematurely return and commit higher offsets while earlier messages are still in flight.

🔴 OffsetTracker skips missing intermediate offsets when calculating committable offsets

In OffsetTracker.get_committable_offsets, line 86 calculates start = max(last_committed + 1, min_offset). If all_offsets contains gaps (for instance, if message 101 is delayed in submission while message 102 has already been added to all_offsets), min_offset will be 102 while last_committed + 1 is 101.

Setting start to min_offset (102) skips checking whether offset 101 was processed or recorded, advancing highest_committable to 102 and committing past offset 101. If the consumer rebalances or restarts, unconsumed or in-flight messages at earlier offsets will be skipped.

Suggested change
start = max(last_committed + 1, min_offset)
start = last_committed + 1 if partition in self.last_committed else min_offset

agent: defect · rule: defect.off-by-one · confidence: 0.95

while not self.shutdown:
try:
work_item = self.work_queue.get()
except queue.ShutDown:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why: queue.ShutDown and Queue.shutdown are Python 3.13 features not available on Python 3.11/3.12, raising AttributeError when exception handlers run on line 132 or when shutdown() is called on line 237.

🔴 Usage of Python 3.13 queue.ShutDown and Queue.shutdown APIs

queue.ShutDown and queue.Queue.shutdown() were introduced in Python 3.13. On Python 3.11 and 3.12 (which Sentry runs on), referencing queue.ShutDown in except queue.ShutDown: raises AttributeError: module 'queue' has no attribute 'ShutDown'. Similarly, calling q.shutdown() on line 237 raises AttributeError: 'Queue' object has no attribute 'shutdown', causing worker thread shutdown to fail and worker threads waiting on work_queue.get() (line 131) without a timeout to block indefinitely.

Suggested change
except queue.ShutDown:
except Exception:
break

agent: defect · rule: defect.compatibility · confidence: 0.95

op="queue_worker.process",
name=f"monitors.{self.identifier}.worker_{self.worker_id}",
):
self.result_processor(self.identifier, work_item.result)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why: self.result_processor holds an instance of ResultProcessor passed from ResultsStrategyFactory, which does not implement call, raising a TypeError when invoked on line 140 during message processing.

🔴 Result processor object called directly as a function

In ResultsStrategyFactory.__init__, self.result_processor is initialized as an instance of ResultProcessor (self.result_processor_cls()) and passed to FixedQueuePool. In OrderedQueueWorker.run, line 140 executes self.result_processor(self.identifier, work_item.result). Because ResultProcessor instances do not implement __call__, processing any message will raise a TypeError: 'ResultProcessor' object is not callable.

Suggested change
self.result_processor(self.identifier, work_item.result)
self.result_processor.handle_result(self.identifier, work_item.result)

agent: defect · rule: defect.type-error · confidence: 0.95

self.multiprocessing_pool = MultiprocessingPool(num_processes)
if mode == "thread-queue-parallel":
self.thread_queue_parallel = True
self.queue_pool = FixedQueuePool(

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why: self.result_processor is passed to FixedQueuePool on line 133 of src/sentry/remote_subscriptions/consumers/result_consumer.py, but OrderedQueueWorker calls it with two arguments on line 140 of src/sentry/remote_subscriptions/consumers/queue_consumer.py, raising a TypeError at runtime.

🟠 Signature mismatch between ResultProcessor and OrderedQueueWorker invocation

In src/sentry/remote_subscriptions/consumers/result_consumer.py, ResultsStrategyFactory instantiates FixedQueuePool by passing self.result_processor directly:

self.queue_pool = FixedQueuePool(
    result_processor=self.result_processor,
    identifier=self.identifier,
    num_queues=max_workers or 20,  # Number of parallel queues
)

However, in src/sentry/remote_subscriptions/consumers/queue_consumer.py, OrderedQueueWorker.run invokes self.result_processor with two arguments (self.identifier and work_item.result):

self.result_processor(self.identifier, work_item.result)

Since ResultProcessor.__call__ only accepts a single argument (result), processing any queued work item will raise a TypeError at runtime.

Suggested change
self.queue_pool = FixedQueuePool(
self.queue_pool = FixedQueuePool(
result_processor=lambda _id, result: self.result_processor(result),
identifier=self.identifier,
num_queues=max_workers or 20, # Number of parallel queues
)

agent: contract · rule: contract.broken-caller · confidence: 0.95

For each partition, finds the highest contiguous offset that has been processed.
"""
committable = {}
for partition in list(self.all_offsets.keys()):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why: get_committable_offsets iterates over list(self.all_offsets.keys()) on line 74 without holding a lock on self.all_offsets, raising RuntimeError if add_offset inserts a new partition key concurrently from the strategy thread.

🟠 Unsynchronized dictionary key iteration during concurrent partition insertion

add_offset is called from SimpleQueueProcessingStrategy.submit on the Arroyo consumer thread and mutates self.all_offsets (a defaultdict) when a new partition is encountered. Concurrent execution of list(self.all_offsets.keys()) in get_committable_offsets on commit_thread without holding a global lock around self.all_offsets can raise RuntimeError: dictionary changed size during iteration.

Suggested change
for partition in list(self.all_offsets.keys()):
# Protect dict key access across threads
with threading.Lock():
partitions = list(self.all_offsets.keys())
for partition in partitions:

agent: defect · rule: defect.concurrency · confidence: 0.85

while not self.shutdown:
try:
work_item = self.work_queue.get()
except queue.ShutDown:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Bare except: catches everything including KeyboardInterrupt

Use except Exception: so SystemExit and KeyboardInterrupt still propagate as the user expects.

except queue.ShutDown: ⏎                 break

Also at line 142.

Suggested change
except queue.ShutDown:
except Exception:

agent: structural · rule: structural.py.bare-except · confidence: 1.00

finally:
try:
admin_client.delete_topics([test_topic])
except Exception:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Empty except clause silently swallows errors

Catching an exception with only pass hides bugs and makes debugging harder. At minimum log the exception or re-raise.

except Exception: ⏎                 pass
Suggested change
except Exception:
logger.exception('...') or raise

agent: structural · rule: structural.py.empty-except · confidence: 1.00

@celmis-codereviewer

Copy link
Copy Markdown
Author

🤖 Code Review for PR #11

CHANGES REQUESTED — blocking findings

Findings

  • 🔴 Critical: 3
  • 🟠 Error: 2
  • 🟡 Warning: 2

Scope

  • Files changed: 5
  • Lines: +1276 / -6

Performance

  • Analysis time: 1609.8s · agents: cve, structural, security, contract, defect · tokens: 52,507/42,710

Powered by Code Analyzer · context: tree-sitter graph + cve, structural, security, contract, defect

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.

2 participants