.agents/skills/sentry-backend-bugs/references/database-integrity.md
31 issues (22 constraint violations + 9 duplicate object collisions), 2,972,784 events, 940 affected users. Race conditions between concurrent requests cause duplicate key violations, foreign key violations during cleanup, integer overflow on counter fields, and MultipleObjectsReturned from get() calls where uniqueness assumptions are violated.
Three main sub-patterns:
times_seen overflow the 32-bit integer range (~2.1 billion)get() assumes exactly one match, but data has duplicates1,753,743 events | 0 users
In-app frames:
# sentry/db/models/query.py -- update()
# SQL: UPDATE "sentry_groupedmessage" SET "times_seen" = ("sentry_groupedmessage"."times_seen" + 1)
# DataError: integer out of range
Called from:
# sentry/buffer/base.py -- process()
Group.objects.filter(id=group_id).update(times_seen=F("times_seen") + count)
Root cause: The times_seen field on Group is a standard 32-bit integer. For very active groups, the counter exceeds 2,147,483,647 and the Postgres UPDATE fails with DataError: integer out of range.
Fix:
# Cap the increment to prevent overflow
from django.db.models import F, Value
from django.db.models.functions import Least
Group.objects.filter(id=group_id).update(
times_seen=Least(F("times_seen") + count, Value(2_147_483_647))
)
Actual fix: Resolved -- either the field was migrated to BigInteger or the increment is now capped.
187,518 events | 0 users
In-app frames:
# sentry/utils/query.py -- bulk_delete_objects()
# IntegrityError: update or delete on table "sentry_monitorcheckin" violates
# foreign key constraint "sentry_monitorincide_..." on table "sentry_monitorincident"
Called from:
# sentry/runner/commands/cleanup.py -- multiprocess_worker()
bulk_delete_objects(MonitorCheckIn, ...)
Root cause: The cleanup task bulk-deletes old MonitorCheckIn records, but MonitorIncident records still reference them via a foreign key. The deletion does not check for or cascade child references.
Fix:
# Delete child references first, or use CASCADE
MonitorIncident.objects.filter(
checkin_id__in=checkin_ids_to_delete
).update(checkin_id=None) # Or delete incidents first
bulk_delete_objects(MonitorCheckIn, ...)
Actual fix: Resolved -- cleanup now handles FK constraints before deletion.
753,527 events | 0 users
In-app frames:
# sentry/models/groupopenperiod.py -- close_open_period()
# DataError: range lower bound must be less than or equal to range upper bound
# SQL: UPDATE "sentry_groupopenperiod" SET ...
Root cause: The GroupOpenPeriod model uses a date range field. When closing an open period, the end timestamp can be earlier than the start timestamp due to clock skew or race conditions in the event pipeline.
Fix:
def close_open_period(self, end_time):
if end_time < self.start_time:
end_time = self.start_time # Clamp to valid range
self.date_range = DateTimeTZRange(self.start_time, end_time)
self.save()
2,391 events | 3 users
In-app frames:
# sentry_plugins/heroku/plugin.py -- set_refs()
repo = Repository.objects.get(
name=repo_name, organization_id=org_id
) # Repository.MultipleObjectsReturned!
Root cause: The (name, organization_id) combination is not unique at the database level (or became non-unique through a migration gap). The code uses get() which raises when more than one match exists.
Fix:
repo = Repository.objects.filter(
name=repo_name, organization_id=org_id,
).order_by("-date_added").first()
if repo is None:
raise Repository.DoesNotExist()
2,554 events | 305 users
In-app frames:
# sentry/sentry_apps/services/app/impl.py -- find_installation_by_proxy_user()
installation = SentryAppInstallation.objects.get(
sentry_app=sentry_app, ...
) # MultipleObjectsReturned: get() returned more than one -- it returned 4!
Root cause: A SentryApp can have multiple installations (e.g., installed, uninstalled, re-installed) and the query does not filter by status.
Fix:
installation = SentryAppInstallation.objects.filter(
sentry_app=sentry_app,
status=SentryAppInstallationStatus.INSTALLED,
...
).first()
Actual fix: Resolved -- query now filters by status and uses .first().
2,250 events | 0 users
In-app frames:
# sentry/integrations/notifications.py -- _get_channel_and_integration_by_team()
actor = ExternalActor.objects.get(
team_id=team_id, integration_id=integration_id, ...
) # MultipleObjectsReturned!
Root cause: An ExternalActor (Slack channel mapping for a team) was duplicated, likely through a data migration or re-linking.
Fix:
actor = ExternalActor.objects.filter(
team_id=team_id, integration_id=integration_id, ...
).first()
Actual fix: Resolved -- uses .first() instead of .get().
| Pattern | Frequency | Typical Trigger |
|---|---|---|
| Integer field overflow on counters | Very High | times_seen incrementing past 2^31 |
| FK violation during bulk deletion | High | Cleanup tasks deleting parent without cascade |
| Date range bound inversion | High | Clock skew in distributed event pipeline |
| get() on non-unique data | High | Missing DB unique constraint, data duplicates |
| Concurrent insert on unique constraint | Medium | Consumers processing same message |
| get_or_create() race | Medium | Two threads call get_or_create simultaneously |
from django.db.models import F, Value
from django.db.models.functions import Least
Model.objects.filter(id=pk).update(
counter=Least(F("counter") + increment, Value(2_147_483_647))
)
# Delete or nullify child references first
ChildModel.objects.filter(parent_id__in=ids_to_delete).delete()
# Then delete parents
ParentModel.objects.filter(id__in=ids_to_delete).delete()
if end_time < start_time:
end_time = start_time # or raise ValueError
# Instead of:
obj = Model.objects.get(name=name, org=org)
# Use:
obj = Model.objects.filter(name=name, org=org).order_by("-date_added").first()
if obj is None:
raise Model.DoesNotExist()
from django.db import IntegrityError
try:
obj = Model.objects.create(**fields)
except IntegrityError:
obj = Model.objects.get(**unique_fields)
Scan the code for these patterns:
F("field") + increment on integer fields -- can the field overflow 2^31?bulk_delete_objects, queryset.delete()) -- are there FK constraints on child tables?.get() call -- is MultipleObjectsReturned possible? Check if the filter fields are actually unique at DB level.save() on a model with unique constraints -- is IntegrityError handled?get_or_create() in concurrent context -- wrapped in try/except IntegrityError?