Skip to content

Conversation

@evanpurkhiser
Copy link
Member

NEW-564: There needs to be some way to mute the entire cron detector

Stage 3 of the migration completes the transition to environment-based muting:

  • Removes the Monitor.is_muted database field
  • Converts Monitor.is_muted to a computed property that returns True only when ALL environments are muted
  • Removes dual-write synchronization logic from endpoints and tasks
  • Updates muting logic to propagate from monitor to environments
  • Updates sorting and filtering queries to use environment muting state
  • Updates all tests to use environment-based muting

Migration 0012 removes the is_muted field from the Monitor model.

The monitor is now considered muted if and only if all of its environments are muted. If a monitor has no environments, it is considered unmuted.

[NEW-564: There needs to be some way to mute the entire cron detector](https://linear.app/getsentry/issue/NEW-564/there-needs-to-be-some-way-to-mute-the-entire-cron-detector)

Stage 3 of the migration completes the transition to environment-based muting:

- Removes the `Monitor.is_muted` database field
- Converts `Monitor.is_muted` to a computed property that returns True only when ALL environments are muted
- Removes dual-write synchronization logic from endpoints and tasks
- Updates muting logic to propagate from monitor to environments
- Updates sorting and filtering queries to use environment muting state
- Updates all tests to use environment-based muting

Migration 0012 removes the is_muted field from the Monitor model.

The monitor is now considered muted if and only if all of its environments are muted. If a monitor has no environments, it is considered unmuted.
@evanpurkhiser evanpurkhiser requested review from a team as code owners November 18, 2025 22:31
@evanpurkhiser evanpurkhiser requested review from a team and removed request for a team November 18, 2025 22:31
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Nov 18, 2025
Comment on lines 444 to 454
)

# Only update if there are environments
if env_counts["total"] > 0:
all_muted = env_counts["total"] == env_counts["muted"]
if self.is_muted != all_muted:
self.update(is_muted=all_muted)
# If no environments exist, monitor is not muted
if env_counts["total"] == 0:
return False

# Monitor is muted only if ALL environments are muted
return env_counts["total"] == env_counts["muted"]


def check_organization_monitor_limit(organization_id: int) -> None:
Copy link

@sentry sentry bot Nov 18, 2025

Choose a reason for hiding this comment

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

Bug: N+1 query occurs when serializing a list of monitors because Monitor.is_muted property executes a database query for each monitor.
Severity: CRITICAL | Confidence: 1.00

🔍 Detailed Analysis

When serializing a list of monitors, accessing the Monitor.is_muted property for each monitor triggers a separate database query. The is_muted property, defined in src/sentry/monitors/models.py lines 436-451, executes MonitorEnvironment.objects.filter(...).aggregate(...). The MonitorSerializer.serialize() method, called for each monitor in a list (e.g., by the GET /organizations/{org}/monitors/ endpoint), directly accesses obj.is_muted. This results in an N+1 query pattern, where N additional database queries are performed for the is_muted state, significantly degrading performance.

💡 Suggested Fix

To resolve the N+1 query, either compute is_muted from pre-fetched MonitorEnvironment data within MonitorSerializer.get_attrs() and pass it via attrs, or use database-level annotations to compute the muted state before serialization.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry/monitors/models.py#L433-L454

Potential issue: When serializing a list of monitors, accessing the `Monitor.is_muted`
property for each monitor triggers a separate database query. The `is_muted` property,
defined in `src/sentry/monitors/models.py` lines 436-451, executes
`MonitorEnvironment.objects.filter(...).aggregate(...)`. The
`MonitorSerializer.serialize()` method, called for each monitor in a list (e.g., by the
`GET /organizations/{org}/monitors/` endpoint), directly accesses `obj.is_muted`. This
results in an N+1 query pattern, where N additional database queries are performed for
the `is_muted` state, significantly degrading performance.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference_id: 2785913

@github-actions
Copy link
Contributor

This PR has a migration; here is the generated SQL for src/sentry/monitors/migrations/0012_remove_monitor_is_muted_field.py

for 0012_remove_monitor_is_muted_field in monitors

--
-- Moved monitor.is_muted field to pending deletion state
--
-- (no-op)

Copy link
Member

@armenzg armenzg left a comment

Choose a reason for hiding this comment

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

I don't know enough about monitors to fully comprehend this, however, it seems a straightforward changes with lots reasonable coverage.

SafeRemoveField(
model_name="monitor",
name="is_muted",
deletion_action=DeletionAction.MOVE_TO_PENDING,
Copy link
Member

Choose a reason for hiding this comment

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

Good 👍🏻

Remove the column and in the generated migration use SafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING) to replace RemoveField(...). This only marks the state for the column as removed.

Comment on lines +436 to +437
@property
def is_muted(self) -> bool:
Copy link
Member

Choose a reason for hiding this comment

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

Do you want a cached property? Currently this will execute queries each time the property is accessed.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think for now I am OK with a brief N+1, but I'm going to get rid of this property after I think.

@evanpurkhiser evanpurkhiser merged commit 528639c into master Nov 19, 2025
67 of 68 checks passed
@evanpurkhiser evanpurkhiser deleted the evanpurkhiser/ref-crons-remove-monitor-is-muted-field-make-it-computed-stage-3- branch November 19, 2025 19:49
@sentry
Copy link

sentry bot commented Nov 19, 2025

Issues attributed to commits in this pull request

This pull request was merged and Sentry observed the following issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants