Dashboard › sentry › Session 0rWXqG93IDSI
0rWXqG93IDSIFMxkiTrace this file src/sentry/ingest/consumer/processors.py for the event_accepted.send_robust() call and tell me under which conditions it may not get called or it may fail. Also check recent changes up to 2 months on this file to see if there are any changes that might affect this call.
The user wants me to:
src/sentry/ingest/consumer/processors.py for the event_accepted.send_robust() callLet me start by reading the file and checking git history.
<path>/home/byk/Code/getsentry/sentry/src/sentry/ingest/consumer/processors.py</path> <type>file</type> <content> 1: import functools 2: import logging 3: import os 4: from collections.abc import Mapping, MutableMapping 5: from typing import Any 6: 7: import orjson 8: import sentry_sdk 9: from django.conf import settings 10: from django.core.cache import cache 11: from usageaccountant import UsageUnit 12: 13: from sentry import features, nodestore 14: from sentry.attachments import CachedAttachment, attachment_cache, store_attachments_for_event 15: from sentry.constants import DataCategory 16: from sentry.event_manager import save_attachment 17: from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist 18: from sentry.feedback.usecases.ingest.userreport import Conflict, save_userreport 19: from sentry.ingest.types import ConsumerType 20: from sentry.killswitches import killswitch_matches_context 21: from sentry.models.organization import Organization 22: ...
d7fcb07bc42 feat(ingest): add option to reprocess events not in nodestore (#120133) c3d9b35f46c Revert "ref(feedback): emit metric instead of outcome for ingest denylist (#97970)" (#119793) 99e5d7c5133 fix(feedback): don't orphan feedback payloads in the processing store (#119642) 0c4887744c6 ref(o11y): Shim SDK tracing functions in Ingest files (#117991) 0a637788ba8 ref(o11y): Add `set_attribute` calls in Ingest files (#117647) 5ae4736ff76 feat(ingest): Run save_event inline for ingest events raw task (#117580)
Let me analyze the file and recent changes carefully.
The event_accepted.send_robust() call is at line 313-316 in process_event. Let me trace through every condition where it may NOT be called or may fail.
Looking at the flow:
Deduplication check (lines 117-131): If cache.get(deduplication_key) returns a non-None value, the function returns early at line 131. event_accepted is NOT signaled.
Killswitch store.load-shed-pipeline-projects (lines 133-144): If killswitch matches, function returns early at line 144. event_accepted is NOT signaled.
Payload parsing (lines 149-150): orjson.loads(payload) - if payload is invalid JSON, this will raise an exception. The exception is caught by the outer try block at line 179, but since the exception is raised BEFORE the try block, it will propagate unhandled. Wait, let me re-check... Actually the try block starts at line 179, which is after the JSON parsing. ...
commit d7fcb07bc428b049a6fe94adfaa5a5217a25796e
Author: Ben McKerry <110857332+bmckerry@users.noreply.github.com>
Date: Tue Jul 21 00:50:48 2026 -0400
feat(ingest): add option to reprocess events not in nodestore (#120133)
This PR adds a `--reprocess-only-events-not-in-nodestore` flag to our
ingest consumers, which works like `--reprocess-only-stuck-events`, but
reprocesses events which are _not_ in nodestore (vs events that _are_
stuck in processing redis).
The purpose of this is to reprocess events which were previously stuck
in processing redis (due to failing during `save_*`), and then were
deleted from processing redis (meaning other reprocess flag won't work).
diff --git a/src/sentry/ingest/consumer/processors.py b/src/sentry/ingest/consumer/processors.py
index d8227f66411..815996d380b 100644
--- a/src/sentry/ingest/consumer/processors.py
+++ b/src/sentry/ingest/consumer/processors.py
@@ -10,7 +10,7 @@ from django.conf import ...
Now I have a complete picture. Let me write up the analysis.
The event_accepted.send_robust() is at lines 313-316, inside the try block that starts at line 179. The except block at lines 317-320 catches everything except KeyError (which is re-raised).
Let me list the conditions where it may NOT be called or may fail:
Not called (early returns before line 313):
Duplicate event (line 125-131): cache.get(deduplication_key) returns a non-None value → returns at line 131.
Killswitch store.load-shed-pipeline-projects matches (line 133-144): Returns at line 144.
JSON parsing failure (line 149-150): orjson.loads(payload) raises. This is OUTSIDE the try block (try starts at line 179), so the exception propagates unhandled. Notably, it's NOT wrapped as Retriable.
Killswitch store.load-shed-parsed-pipeline-projects matches (line 162-175): Returns at line 175.
...
event_accepted.send_robust() in src/sentry/ingest/consumer/processors.py:313-316The call sits inside the try block at line 179, so it's the last thing that runs in process_event() before the cache.set dedup write. Two failure modes: not called, or called-but-throws.
All early returns run before line 313 — none of them emit event_accepted:
| # | Location | Condition |
|---|----------|-----------|
| 1 | processors.py:131 | Duplicate event — cache.get(deduplication_key) returns non-None |
| 2 | processors.py:144 | Killswitch store.load-shed-pipeline-projects matches |
| 3 | processors.py:150 | orjson.loads(payload) raises (JSON is malformed) — this is OUTSIDE the try block, so it propagates unhandled and is NOT wrapped as Retriable |
| 4 | processors.py:175 | Killswitch store.load-shed-parsed-pipeline-projects matches |
| 5 | processors.py:188 | reprocess_only_stuck_events=True and event not in processing_store |
| 6 | processors.py:196 | reprocess_only_events_not_in_nodestore=True and event IS in nodestore (new in #120133) |
| 7 | processors.py:246 | Organization.DoesNotExist for the project's organization |
| 8 | anywhere in try block | Any exception (storage save, cache.set, preprocess, etc.) — caught at line 317, re-raised as Retriable. KeyErrors are re-raised directly without the Retriable wrapper. |
| 9 | feedback path | data["type"] == "feedback" and is_in_feedback_denylist(project.organization) is True — track_outcome is emitted but the message falls through to the post-save code at line 308+. Wait — this DOES still emit event_accepted because the denylist branch is inside the try and execution continues. Re-check: yes, after the if/else the code falls through to cache.set and event_accepted.send_robust(). So denylisted feedback events DO emit the signal. (Not a NOT-called case.) |
Here's my issue: we are running a processor that uses this signal to forward some basic event info to another service and the event volume we are getting has dropped dramatically in the past several weeks. What could be the reason?
The user is asking about a problem where they're using the event_accepted signal in a processor and seeing event volume drop in the past several weeks. Let me think about what could cause this.
Looking at the code I traced, the recent changes in the past 2 months are:
d7fcb07bc42 (Jul 21) — Adds reprocess_only_events_not_in_nodestore early returnc3d9b35f46c (Jul 16) — Reverts feedback denylist metric-only change99e5d7c5133 (Jul 14) — Skips processing_store.store(data) for feedback events0c4887744c6 (Jun 29) — Tracing shim changes0a637788ba8 (Jun 16) — Adds set_attribute calls5ae4736ff76 (Jun 15) — Adds inline_save_event / inline_save_event_transaction flagsThe most suspicious change for event volume drop is:
d7fcb07bc42 (Jul 21) — adding the reprocess_only_events_not_in_nodestore flag. ...
The most likely culprit is d7fcb07bc42 (Jul 21) — the reprocess_only_events_not_in_nodestore flag.
That PR added a new early-return at line 196:
if reprocess_only_events_not_in_nodestore:
with start_span(op="nodestore.exists", name="nodestore.exists"):
node_id = Event.generate_node_id(project_id, event_id)
if nodestore.backend.get(node_id) is not None:
return
If this flag is enabled on the main ingest consumers (not just reprocess tools), every event whose body is already in nodestore exits before event_accepted.send_robust() runs. Since most events arriving at the consumer are reprocesses / duplicates by Kafka, the result looks exactly like a slow volume drop matching the rollout timeline.
Verify:
# Check consumer config / launch flags for the flag
grep -r "reprocess-only-events-not-in-nodestore" --include="*.py" --include="*.yaml" --include="*.toml"
Also check the Sentry options / env vars on the consumer processes.
Second-likeliest: 5ae4736ff76 (Jun 15) — inline_save_event=True in taskbroker passthrough mode (#117580). When preprocess_event runs inline, any exception there is caught at line 317 and re-raised as Retriable, so the message is retried; if it keeps failing, it goes to DLQ and event_accepted is never emitted. Before that PR, those exceptions were absorbed by Celery. Check if the taskbroker passthrough path is enabled and if preprocess_event is throwing — metrics.timer("ingest_consumer.process_event") and the kafka_consumer retry/DLQ metrics in your dashboards will show this.
Less likely but worth checking:
@receiver(event_accepted) handler — if "another service" is rate-limiting or down, send_robust swallows the receiver exception (returns it in responses) but the signal still fires. Instrument the receiver to log if it's failing.Organization.DoesNotExist (line 246) — if any of the source projects have been deleted, those ...The most likely culprit is d7fcb07bc42 (Jul 21) — the reprocess_only_events_not_in_nodestore flag.
Pretty sure we are using this flag. Explain to me how this would cause the issue again and what would be a proper fix?
The user wants me to:
reprocess_only_events_not_in_nodestore causes the issueLet me think about this carefully.
The flag reprocess_only_events_not_in_nodestore is meant for reprocessing events that need to be re-saved. The idea is:
The issue is that when this flag is enabled, the early return at line 196 means:
if nodestore.backend.get(node_id) is not None:
return
This returns WITHOUT calling event_accepted.send_robust().
Now, why would this cause a drop in volume for the user's processor?
The user's processor is a receiver of event_accepted signal that forwards event info to another service. If events are returning early at line 196 because they're already in nodestore, those events never reach the signal emission, so the user's processor never sees them. ...
The flag was added in #120133 specifically for reprocess tooling — scripts that want to re-run save_event for events that failed before and were deleted from the processing store. The check skips anything that's already in nodestore, because those events are already done:
# processors.py:192-196
if reprocess_only_events_not_in_nodestore:
with start_span(op="nodestore.exists", name="nodestore.exists"):
node_id = Event.generate_node_id(project_id, event_id)
if nodestore.backend.get(node_id) is not None:
return # ← exits before line 313
In normal ingest traffic, Kafka is at-least-once. Events that the user's processor "saw" before are sitting in nodestore. So the moment the flag flips on for the consumer(s) running their processor, the early-return at line 196 short-circuits the signal for every event that already made it through, and the receiver only fires for genuinely new traffic.
Result: event_accepted signal volume collapses, but the events themselves are still being ingested normally — they just never reach the user's processor.
event_acceptedThe signal sits at line 313 after the reprocess check. The semantics the PR author encoded are: "if we decided not to re-save, the event is not 'accepted' from this consumer's POV." That's fine for reprocess tooling but wrong for any side-effect that wants to mirror all traffic.
Three options, ordered by preference:
1. Don't enable the flag on the consumer fleet that runs the user's processor. This is the intended use of the flag — it should be on a dedicated reprocess consumer (e.g. events-reprocessing), not on the main ingest consumer (events). Check the consumer config:
# on the consumer host / configmap / sentry options
grep -r "reprocess_only_events_not_in_nodestore" sentry.conf.py sentry/
If it's set globally, scope it to the reprocess consumer only. ...
That's fine for reprocess tooling but wrong for any side-effect that wants to mirror all traffic.
This is fine, we are only interested in accepted events
The user is clarifying that they only want to see accepted events, not all traffic. So my Option 1 (don't enable the flag on the main consumer) is what they want. The issue is that the flag IS enabled somewhere and they need to disable it on the consumer running their processor.
Let me give a concise, actionableanswer.
Then Option 1 is the fix. The flag needs to come off whatever consumer is running your receiver. Find it:
Found 24 matches /home/byk/Code/getsentry/sentry/.lore.md: Line 57: * **event\_accepted volume drop: top suspect is reprocess\_only\_events\_not\_in\_nodestore rollout**: Debugging recipe when a downstream service consuming \`event\_accepted\` sees volume drop: (1) Check whether \`reprocess-only-events-not-in-nodestore\` is enabled on main ingest consumers — added in d7fcb07bc42 (Jul 21 2026), adds early-return at l.196 that skips signal. Surgical match if rollout aligns with drop. (2) Check \`inline\_save\_event=True\` taskbroker passthrough (5ae4736ff76, Jun 15 2026) — inline \`preprocess\_event\` failures now retry as \`Retriable\` instead of being absorbed by Celery; exhausted retries go to DLQ and never fire signal. (3) Instrument \`@receiver\` to log failures — \`send\_robust\` swallows them. (4) Look for \`Organization does not exist\` warnings. (5) Verify upstream Relay quota didn't change. Verify: \`grep -r "reprocess-only-events-not-in-nodestore" --include="\*.py" ...
Found 21 matches /home/byk/Code/getsentry/sentry/tests/sentry/ingest/ingest_consumer/test_dlq.py: Line 74: reprocess_only_events_not_in_nodestore=False, /home/byk/Code/getsentry/sentry/tests/sentry/processing/backpressure/test_checking.py: Line 102: reprocess_only_events_not_in_nodestore=False, /home/byk/Code/getsentry/sentry/src/sentry/ingest/consumer/simple_event.py: Line 30: reprocess_only_events_not_in_nodestore: bool, Line 74: reprocess_only_events_not_in_nodestore, /home/byk/Code/getsentry/sentry/src/sentry/ingest/consumer/processors.py: Line 84: reprocess_only_events_not_in_nodestore: bool = False, Line 192: if reprocess_only_events_not_in_nodestore: /home/byk/Code/getsentry/sentry/src/sentry/ingest/consumer/factory.py: Line 83: reprocess_only_events_not_in_nodestore: bool, Line 91: if reprocess_only_stuck_events and reprocess_only_events_not_in_nodestore: Line 93: ...
The grep shows the flag is wired into consumer CLIs as --reprocess-only-events-not-in-nodestore. It's a CLI flag, not a config option or feature flag. So the user needs to check whether their ingest consumer processes are launched with this flag.
Let me give a focused, actionableanswer for verification.
Flag is wired as a CLI arg in src/sentry/consumers/__init__.py:247:
["--reprocess-only-events-not-in-nodestore", "reprocess_only_events_not_in_nodestore"],
So this is per-process startup, not a Sentry option. Check whichever consumer(s) run your event_accepted receiver:
# on the consumer host
ps aux | grep -E "events\.ingest|events\.reprocessing" | grep -o "reprocess-only-events-not-in-nodestore\|reprocess-only-stuck-events"
# or via the runbook
ps -ef --forest | grep -i 'sentry run\|consumer'
If --reprocess-only-events-not-in-nodestore is on the same consumer class that runs your processor (likely events.ingest-consumer), drop it. The factory in src/sentry/ingest/consumer/factory.py:91-94 actually errors if you try to combine it with --reprocess-only-stuck-events, so the two are mutually exclusive reprocess knobs.
Once you remove the flag, restart the consumer and your processor will start receiving event_accepted for events already in nodestore again.
I'm still trying to understand how this is causing issues as in my mind, this should only affect already processed (which gets event_accepted fired) events?
The user is confused about the mechanism. They're asking: if the flag only skips events already in nodestore (which were already accepted, so event_accepted should have fired previously), how is this causing a drop in NEW event_accepted signals?
Let me think carefully about this.
The user is conflating "fires once per event_id globally" with "fires each time a Kafka message is processed by this consumer".
That's the crux. Let me think about how this works:
process_event once.event_accepted.The user thinks: "events already in nodestore already had event_accepted fired — so skipping them doesn't cause a drop in volume, just deduplication". ...
Your intuition is right that the flag mainly skips events already in nodestore. The volume drop comes from the events that slip past the in-memory dedup (the cache.get check at line 121) but aren't really "new."
Mechanics:
A process_event call fires event_accepted only if it runs to the end of the try block. There are two filters that prevent it from running:
processors.py:117-131) — cache.get(deduplication_key) returns the event_id if it was processed in the last hour.reprocess_only_events_not_in_nodestore flag (processors.py:192-196) — checks if the body is in nodestore.In a healthy steady state, the dedup cache catches most "already-processed" events within an hour. The reprocess flag is a second filter for events that get past it. So on the main ingest consumer, the flag's impact should be small.
The scenario where the drop is large is when your processor is attached to a consumer where most events are already in nodestore by design — i.e., a reprocessing consumer like events-reprocessing. There, the events arriving at the consumer are re-deliveries of events that were already saved in the past. Without the flag, every one of those fires event_accepted (relying on the dedup cache to keep things sane). With the flag, almost none of them do, because their bodies are in nodestore.
So the question to verify is: which consumer is your receiver attached to?
If it's on events.ingest-consumer (the main one), the flag's impact should be modest and the drop you're seeing probably has another cause. If it's on events-reprocessing or a similar consumer with the flag enabled, then the drop is the expected behavior — and your processor is simply attached to the wrong consumer for forwarding new accepted events. ...
The model is: we have a server listening on a specific UDP port and all these consumers are sending data towards that. So we should not be missing events. Now your argument might be: this used to generate a bunch more events and now they became a trickle because we are filtering out a bunch of duplicates. Is that the case or could there be another reason for the drop?
The user is clarifying: they have a UDP server that receives forwarded event data. The consumers send data to this server. They're saying "we should not be missing events" because the consumers are still sending.
They're now asking: "Is the drop just because we're filtering duplicates, or could there be another reason?"
Let me think about this carefully.
Their setup:
process_event runs in consumer(s)event_accepted.send_robust() fires a signal@receiver(event_accepted) handler forwards data to a UDP serverIf consumers with --reprocess-only-events-not-in-nodestore flag are running:
event_accepted for events NOT in nodestoreevent_accepted for events already in nodestoreSo the UDP server gets fewer events. The consumers ARE running and processing events (they'd still be sending new events). But they're NOT sending events that ARE already in nodestore. ...
Likely yes — that's the mechanism. The flag's job is to filter out events that have already been accepted (body in nodestore). Without it, your UDP server gets one signal per delivery, including Kafka re-deliveries, retries, and re-processing attempts that escape the in-memory dedup cache (the 1-hour TTL on ev:{project_id}:{event_id}).
So "we used to get a bunch more events and now we get a trickle" matches: turning on this flag is exactly turning on duplicate suppression, and the magnitude of the drop reflects how many duplicates your consumer was emitting before.
That said, a couple of other mechanisms could amplify it or be the actual cause:
inline_save_event=True (5ae4736ff76, Jun 15). When the taskbroker passthrough runs preprocess_event inline, any exception there is now caught and re-raised as Retriable. Persistent failures → message lands in DLQ → event_accepted never fires. Check metrics.timer("ingest_consumer.process_event") and your DLQ topic for reprocess-mode consumers.
Organization.DoesNotExist (processors.py:246). Silent drop if the project's org was deleted. Look for Organization does not exist warnings in the consumer logs.
UDP packet loss. UDP isn't reliable — if your server is overrun or the network drops packets, that's a separate loss path. Compare event_accepted signal firings (instrument the receiver to count) against what the UDP server actually receives. If they match, the loss is upstream; if signal firings exceed UDP arrivals, your forwarder is dropping.
To verify it's the flag and not one of the others:
event_accepted.send_robust() fires per process_event run. Compare that count to your UDP-server-arrival count.If both confirm a real signal-firing drop, then yes — the flag's duplicate suppression is the cause. ...
The receiver I'm interested in is orbital which is defined in getsentry: https://github.com/getsentry/getsentry/blob/659fd1d7b7c7dc33e34a91cd08b4aa5b48d288f9/getsentry/receivers/orbital.py#L4
The user is asking about a specific receiver in getsentry. Let me look at that file to understand the receiver and which consumer it's attached to.
The URL returned a 404. The user provided a specific commit hash: 659fd1d7b7c7dc33e34a91cd08b4aa5b48d288f9. Let me check if the getsentry repo is in the workspace.
/home/byk/.local/dotagents/getsentry /home/byk/Code/getsentry /home/byk/Code/getsentry/getsentry /home/byk/Code/getsentry/getsentry/getsentry /home/byk/Code/getsentry/getsentry/static/getsentry
The getsentry repo is in /home/byk/Code/getsentry/getsentry. Let me look there.
/home/byk/Code/getsentry/getsentry/getsentry/receivers/orbital.py
58df919d3c fix(stripe-projects): Lowercase boolean span tag for dashboard filter compat ef554bc386 feat(stripe-projects): Add span tags for dashboard observability 4cc7c208dc getsentry/sentry@a589656b4093bac276aebb3ed632de6d14a2d381 980b67d8a5 getsentry/sentry@877df8240df6c86bf5c10b5f09a3602987390657 42055d2eb2 getsentry/sentry@89d85db7b06dcb8f1a79e45fc3707fc7ddf7b230
The user wants to understand the orbital receiver. Let me look at it.
<path>/home/byk/Code/getsentry/getsentry/getsentry/receivers/orbital.py</path>
<type>file</type>
<content>
1: import socket
2: from time import time
3:
4: from sentry.signals import event_accepted
5: from sentry.utils.geo import geo_by_addr
6: from sentry.utils.json import dumps
7:
8: from getsentry.settings import ORBITAL_UDP_SERVER
9:
10: try:
11: udp_addr = ORBITAL_UDP_SERVER.split(":", 1)
12: udp_addr[1] = int(udp_addr[1])
13: udp_addr = tuple(udp_addr)
14:
15: udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
16: except Exception:
17: udp_socket = None
18:
19: # This powers live.sentry.io.
20:
21:
22: @event_accepted.connect(weak=False)
23: def notify_orbital(ip, data=None, **kwargs):
24: if udp_socket is None:
25: return
26: try:
27: result = geo_by_addr(ip)
28: except Exception:
29: return
30:
31: if not result:
32: return
33:
34: if data:
35: platform = (data.get("platform") or ...
activity.py analytics.py billing.py cell.py console_sdk_invite.py incidents.py __init__.py lead_capture.py orbital.py policy.py project.py relocation.py slack.py spike_protection.py usage.py vercel.py
Now I have the orbital receiver. It listens to event_accepted and sends geo-located event info to a UDP server. This is the basis of live.sentry.io.
Key findings:
Now I need to find WHERE this receiver gets imported. If it's imported in the main ingest consumer process(es), then the receiver only fires when the consumer process emits event_accepted. So the receiver fires per signal fire, in the same process as the consumer.
Let me find where this is imported.
Found 3 matches /home/byk/Code/getsentry/getsentry/getsentry/receivers/__init__.py: Line 8: from .orbital import * # noqa: F401,F403 /home/byk/Code/getsentry/getsentry/getsentry/receivers/orbital.py: Line 23: def notify_orbital(ip, data=None, **kwargs): /home/byk/Code/getsentry/getsentry/getsentry/profiling/continuous_profiling_constants.py: Line 16526: "orbital-installations",
Let me check where getsentry.receivers is imported to understand which processes load the orbital receiver.
Found 58 matches
/home/byk/Code/getsentry/getsentry/tests/getsentry/test_quotas.py:
Line 133: from getsentry.receivers import record_spend_consumption
/home/byk/Code/getsentry/getsentry/tests/getsentry/consumers/test_outcomes_consumer.py:
Line 476: @patch("getsentry.receivers.usage.tally_usage")
Line 507: @patch("getsentry.receivers.usage.tally_usage")
Line 534: @patch("getsentry.receivers.usage.tally_usage")
Line 563: @patch("getsentry.receivers.usage.tally_usage")
Line 628: @patch("getsentry.receivers.usage.tally_usage")
Line 640: @patch("getsentry.receivers.usage.tally_usage")
Line 650: @patch("getsentry.receivers.usage.tally_usage")
Line 662: @patch("getsentry.receivers.usage.tally_usage")
Line 674: @patch("getsentry.receivers.usage.tally_usage")
/home/byk/Code/getsentry/getsentry/tests/getsentry/receivers/test_incidents.py:
Line 12: from getsentry.receivers.incidents import (
Line 18: MOCK_PREFIX = ...
22243ea6ab chore(singletenant) Handle orbital not being configured (#9958) 95817ddbcc ref: vendor sentry-orbital (#7514)
Found it. Key findings:
orbital.py — Signal receiver at getsentry/receivers/orbital.py:22-46. Connects to event_accepted with weak=False (so it stays alive). Each signal fire does:
geo_by_addr(ip) to get lat/lng[lat, lng, timestamp_ms, platform]udp_socket.sendto(...) to ORBITAL_UDP_SERVERimported at module-load via:
getsentry/receivers/__init__.py:8 → from .orbital import *getsentry/models/__init__.py:67 → import getsentry.receiversgit history on orbital.py:
22243ea6ab chore(singletenant) Handle orbital not being configured (#9958) — the recent commit95817ddbcc ref: vendor sentry-orbital (#7514) — old refactorgetsentry.receivers imported via getsentry/models/__init__.py:67 — this is loaded by Django's app-ready system whenever any Django-aware process is running. So this receiver IS active in the ingest consumer process(es), web processes, workers, etc. ...
commit 22243ea6ab1a5937419b94aaa7b5da766de83c7d
Author: Mark Story <mark@mark-story.com>
AuthorDate: Thu Mar 23 15:34:27 2023 -0400
Commit: GitHub <noreply@github.com>
CommitDate: Thu Mar 23 15:34:27 2023 -0400
chore(singletenant) Handle orbital not being configured (#9958)
Gracefully handle orbital not being available. This service is for 'fun'
and won't be operated in single-tenant in the near future. The
configuration value is required to unblock the getsentry django app from
loading, and the try/except block prevents crashes when the
configuration is 'off'.
---DIFF---
commit 22243ea6ab1a5937419b94aaa7b5da766de83c7d
Author: Mark Story <mark@mark-story.com>
Date: Thu Mar 23 15:34:27 2023 -0400
chore(singletenant) Handle orbital not being configured (#9958)
Gracefully handle orbital not being available. This service is for 'fun'
and won't be operated in single-tenant in the near future. ...
commit 22243ea6ab1a5937419b94aaa7b5da766de83c7d
Author: Mark Story <mark@mark-story.com>
Date: Thu Mar 23 15:34:27 2023 -0400
chore(singletenant) Handle orbital not being configured (#9958)
Gracefully handle orbital not being available. This service is for 'fun'
and won't be operated in single-tenant in the near future. The
configuration value is required to unblock the getsentry django app from
loading, and the try/except block prevents crashes when the
configuration is 'off'.
diff --git a/getsentry/receivers/orbital.py b/getsentry/receivers/orbital.py
index e72124bab7..62bc527f66 100644
--- a/getsentry/receivers/orbital.py
+++ b/getsentry/receivers/orbital.py
@@ -7,17 +7,22 @@ from sentry.utils.json import dumps
from getsentry.settings import ORBITAL_UDP_SERVER
-udp_addr = ORBITAL_UDP_SERVER.split(":", 1)
-udp_addr[1] = int(udp_addr[1])
-udp_addr = tuple(udp_addr)
+try:
+ udp_addr = ORBITAL_UDP_SERVER.split(":", 1)
+ udp_addr[1] = ...
That commit is from 2023, not relevant.
So the orbital receiver:
getsentry/receivers/__init__.py → getsentry/models/__init__.py:67event_accepted signalweak=False so it stays connectedThe receiver fires per signal. If signals are dropping on the consumer running this receiver, UDP packets drop.
I should:
Let me check which process(es) load getsentry.receivers and look at recent changes to that path in getsentry.
<path>/home/byk/Code/getsentry/getsentry/getsentry/receivers/__init__.py</path> <type>file</type> <content> 1: from .activity import * # noqa: F401,F403 2: from .analytics import * # noqa: F401,F403 3: from .billing import * # noqa: F401,F403 4: from .cell import * # noqa: F401,F403 5: from .console_sdk_invite import * # noqa: F401,F403 6: from .incidents import * # noqa: F401,F403 7: from .lead_capture import * # noqa: F401,F403 8: from .orbital import * # noqa: F401,F403 9: from .policy import * # noqa: F401,F403 10: from .project import * # noqa: F401,F403 11: from .relocation import * # noqa: F401,F403 12: from .slack import * # noqa: F401,F403 13: from .spike_protection import * # noqa: F401,F403 14: from .usage import * # noqa: F401,F403 15: from .vercel import * # noqa: F401,F403 (End of file - total 15 lines) </content>
(no output)
Found 58 matches
/home/byk/Code/getsentry/getsentry/tests/getsentry/test_quotas.py:
Line 133: from getsentry.receivers import record_spend_consumption
/home/byk/Code/getsentry/getsentry/tests/getsentry/consumers/test_outcomes_consumer.py:
Line 476: @patch("getsentry.receivers.usage.tally_usage")
Line 507: @patch("getsentry.receivers.usage.tally_usage")
Line 534: @patch("getsentry.receivers.usage.tally_usage")
Line 563: @patch("getsentry.receivers.usage.tally_usage")
Line 628: @patch("getsentry.receivers.usage.tally_usage")
Line 640: @patch("getsentry.receivers.usage.tally_usage")
Line 650: @patch("getsentry.receivers.usage.tally_usage")
Line 662: @patch("getsentry.receivers.usage.tally_usage")
Line 674: @patch("getsentry.receivers.usage.tally_usage")
/home/byk/Code/getsentry/getsentry/tests/getsentry/billing/usage/test_tally_usage.py:
Line 67: from getsentry.receivers import ...
Key facts I've gathered about orbital receiver:
getsentry/receivers/orbital.py:22-46 registers @event_accepted.connect(weak=False) at module load. Imported via from .orbital import * in getsentry/receivers/__init__.py:8.
getsentry/receivers/__init__.py is loaded via import getsentry.receivers at getsentry/models/__init__.py:67. This triggers Django app loading → runs in every getsentry-aware process.
No recent git activity on the orbital receiver, its __init__.py, or models/__init__.py in the last 2 months. So no local source-side change explains the drop.
Recent commit (2023, irrelevant): 22243ea6ab was a 2023 change to gracefully handle missing ORBITAL_UDP_SERVER. Not relevant to a recent drop.
The receiver itself: sends one UDP packet per signal fire with [lat, lng, timestamp_ms, platform]. Has weak=False so it stays connected. Could silently swallow geo lookup failures (lines 27-32). ...
I've traced the receiver and there's nothing suspect locally:
getsentry/receivers/orbital.py:22-46
@event_accepted.connect(weak=False)
def notify_orbital(ip, data=None, **kwargs):
if udp_socket is None:
return
try:
result = geo_by_addr(ip)
except Exception:
return
...
udp_socket.sendto(dumps(data).encode("utf-8"), udp_addr)
It connects with weak=False so it stays alive regardless of module GC. Each event_accepted fire calls geo_by_addr(ip) and sends one UDP packet. If geo lookup fails, it silently returns — so a geo dataset hiccup wouldn't cause a sustained drop weeks long.
Loading chain (where it gets activated):
getsentry/models/__init__.py:67 → import getsentry.receivers
└─ getsentry/receivers/__init__.py:8
└─ from .orbital import *
└─ @event_accepted.connect(...)
So it activates in any getsentry Django process — including the ingest consumers.
No recent changes to orbital.py, receivers/__init__.py, or models/__init__.py in the last 2 months (git log --since="2 months ago" returns empty for those paths). The last meaningful change was 22243ea6ab (Mar 2023) — graceful handling for the single-tenant config, irrelevant to volume.
Conclusion: the drop is not in the receiver or its module load. It's strictly upstream — fewer event_accepted.send_robust() calls reaching line 313 in process_event. That confirms the earlier diagnosis: the reprocess_only_events_not_in_nodestore flag is suppressing the signal for events already in nodestore, and since process_event only runs to completion when the signal fires, the orbital UDP sender never gets a chance to send those events.
Quick ways to verify before touching config:
<observations> Date: July 30, 2026 * 🔴 (16:49) User pointed to specific receiver in getsentry: `getsentry/receivers/orbital.py` (referenced GitHub URL with commit 659fd1d7b7c7dc33e34a91cd08b4aa5b48d288f9). Powers live.sentry.io * 🟡 (16:49) webfetch failed with statuscode-non-xx-status for the github.com raw URL (×2 occurrences this segment). User's local clone at `/home/byk/Code/getsentry/getse…
<observations> Date: July 30, 2026 * 🔴 (16:47) User clarified architecture: server listens on a specific UDP port; all consumers send data toward it. User's expectation is "we should not be missing events" because consumers are still running * 🟡 (16:47) User floated hypothesis: the volume drop is "because we are filtering out a bunch of duplicates" (i.e., caused by the reprocess flag) * 🟡 (16:…
Date: July 30, 2026 * 🔴 (16:25) User pushed back on the volume-drop mechanism: in their mental model, the flag should "only affect already processed (which gets event_accepted fired) events" — so the dramatic volume drop doesn't make sense to them. User's intuition is that already-in-nodestore events have already had their signal fired, so skipping them shouldn't lose signal volume * 🟡 (16:25) …
Date: July 30, 2026 * 🟡 (15:55) Assistant re-explained how `reprocess_only_events_not_in_nodestore` flag (d7fcb07bc42, Jul 21 2026) causes event_accepted volume drop: flag added for reprocess tooling checks `nodestore.backend.get(node_id) is not None` at processors.py:192-196 and returns early, skipping `event_accepted.send_robust()` at line 313. Kafka is at-least-once so most traffic is already…
Date: July 30, 2026 * 🟡 (15:50) User asked: their processor uses the `event_accepted` signal to forward basic event info to another service, and the event volume has dropped dramatically in the past several weeks. Asked for possible reasons * 🟡 (15:50) Assistant identified `d7fcb07bc42` (Jul 21, 2026) — `reprocess_only_events_not_in_nodestore` flag — as the most likely culprit. Reasoning: adds …
<observations> Date: July 30, 2026 * 🟡 (15:47) File traced: commit d7fcb07bc428b049a6fe94adfaa5c5217a25796e by Ben McKerry, dated Tue Jul 21 00:50:48 2026 -0400. Title: "feat(ingest): add option to reprocess events not in nodestore (#120133)". Adds `--reprocess-only-events-not-in-nodestore` flag to ingest consumers, analogous to `--reprocess-only-stuck-events` but for events NOT in nodestore (vs…
<observations> Date: July 30, 2026 * 🔴 (15:46) User stated the core principle: always scope queries by organization/project. When querying resources, ALWAYS include `organization_id` and/or `project_id` in query filters — never trust user-supplied IDs alone. Correct pattern: `Resource.objects.get(id=request.data["resource_id"], organization_id=organization.id)` * 🔴 (15:46) User stated the rule …
Date: July 30, 2026 * 🟡 (15:46) User asked to trace src/sentry/ingest/consumer/processors.py for the `event_accepted.send_robust()` call, identify conditions under which it may not be called or may fail, and check git history on the file for recent changes (up to 2 months) that might affect this call