ref(workflow_engine): Convert OwnerModel.owner_team FK to BigInt#109938
ref(workflow_engine): Convert OwnerModel.owner_team FK to BigInt#109938
Conversation
Remove the database-level FK constraint on owner_team by converting it from FlexibleForeignKey to BoundedBigIntegerField. This affects Detector and Workflow models (both in workflow_engine app). The migration uses SeparateDatabaseAndState to drop the FK constraint at the database level while updating Django state to use a plain integer field. Also adds nullification of owner_team_id on Detector and Workflow when a Team is deleted. Co-Authored-By: Claude <noreply@anthropic.com>
72c285b to
71e3699
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Migration removes non-existent constraint from Workflow state
- Removed the Workflow
RemoveConstraintand correspondingAddConstraintstate operations in migration 0110 because that constraint never existed in workflow migration state or model metadata.
- Removed the Workflow
Or push these changes by commenting:
@cursor push 8372276a40
Preview (8372276a40)
diff --git a/src/sentry/workflow_engine/migrations/0110_owner_team_break_fk.py b/src/sentry/workflow_engine/migrations/0110_owner_team_break_fk.py
--- a/src/sentry/workflow_engine/migrations/0110_owner_team_break_fk.py
+++ b/src/sentry/workflow_engine/migrations/0110_owner_team_break_fk.py
@@ -72,10 +72,6 @@
),
],
state_operations=[
- migrations.RemoveConstraint(
- model_name="workflow",
- name="workflow_engine_workflow_owner_constraints",
- ),
migrations.RemoveField(
model_name="workflow",
name="owner_team",
@@ -87,17 +83,6 @@
blank=True, db_index=True, null=True
),
),
- migrations.AddConstraint(
- model_name="workflow",
- constraint=models.CheckConstraint(
- condition=(
- models.Q(owner_user_id__isnull=True, owner_team_id__isnull=False)
- | models.Q(owner_user_id__isnull=False, owner_team_id__isnull=True)
- | models.Q(owner_user_id__isnull=True, owner_team_id__isnull=True)
- ),
- name="workflow_engine_workflow_owner_constraints",
- ),
- ),
],
),
]|
This PR has a migration; here is the generated SQL for for --
-- Custom state/database change combination
--
SET CONSTRAINTS "workflow_engine_dete_owner_team_id_177670f3_fk_sentry_te" IMMEDIATE; ALTER TABLE "workflow_engine_detector" DROP CONSTRAINT "workflow_engine_dete_owner_team_id_177670f3_fk_sentry_te";
--
-- Custom state/database change combination
--
SET CONSTRAINTS "workflow_engine_work_owner_team_id_c401e7b8_fk_sentry_te" IMMEDIATE; ALTER TABLE "workflow_engine_workflow" DROP CONSTRAINT "workflow_engine_work_owner_team_id_c401e7b8_fk_sentry_te"; |
Backend Test FailuresFailures on
|
Workflow.Meta overrides OwnerModel.Meta.constraints with its own list, so the CheckConstraint was never applied to Workflow. Remove the RemoveConstraint/AddConstraint operations from the migration for Workflow. Also fix test using owner_team= kwarg. Co-Authored-By: Claude <noreply@anthropic.com>
markstory
left a comment
There was a problem hiding this comment.
Looks good to me. The constraint drops will leave indexes behind so query performance should not be impacted.
| Detector.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) | ||
| Workflow.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) |
There was a problem hiding this comment.
It looks like most team deletions are done through scheduled deletions so we shouldn't have dangling references in detectors/workflows.
There was a problem hiding this comment.
Agreed that scheduled deletions handle the cascading cleanup when the FK exists. However, this PR converts owner_team from a ForeignKey (with on_delete=SET_NULL) to a plain BoundedBigIntegerField, so the DB will no longer automatically null these out on team deletion. The manual cleanup here ensures we don't end up with dangling team IDs pointing to deleted teams.
— Claude Code
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| Rule.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) | ||
| Monitor.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) | ||
| Detector.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) | ||
| Workflow.objects.filter(owner_team_id=instance.id).update(owner_team_id=None) |
There was a problem hiding this comment.
Custom managers skip nullifying deletion-state objects' team references
Low Severity
Detector.objects and Workflow.objects use custom managers (DetectorManager, WorkflowManager) that exclude rows with PENDING_DELETION or DELETION_IN_PROGRESS status. These nullification queries will silently skip any detectors or workflows in those states, leaving dangling owner_team_id values. Previously, the database-level ON DELETE SET_NULL from the FK constraint would have nullified all rows regardless of Django manager filtering. This is unlikely to cause real problems since those objects are mid-deletion, but it's a subtle behavioral change from the FK-based cascade.



The
OwnerModelabstract base class definesowner_teamas aFlexibleForeignKeyto
sentry.Team. This FK creates a database-level constraint that assumes bothtables reside in the same database, which is not desirable in our sharded
environment where tables may be placed on different database instances.
This converts
owner_teamto aBoundedBigIntegerField(owner_team_id) forthe two concrete models that inherit from
OwnerModel:DetectorandWorkflow(both in
workflow_engineapp). TheRulemodel defines its ownowner_teamFKseparately and is not in scope.
The migration uses
SeparateDatabaseAndStateto drop the FK constraint at thedatabase level while updating Django state to use a plain integer field. Also adds
nullification of
owner_team_idonDetectorandWorkflowin the team deletionhandler, matching the existing pattern for
RuleandMonitor.