Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions src/sentry/integrations/services/repository/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.db import IntegrityError, router, transaction
from django.utils import timezone

from sentry import features
from sentry.api.serializers import serialize
from sentry.constants import ObjectStatus
from sentry.db.postgres.transactions import enforce_constraints
Expand All @@ -18,7 +17,6 @@
from sentry.models.code_review_event import CodeReviewEvent
from sentry.models.commit import Commit
from sentry.models.options.project_option import ProjectOption
from sentry.models.organization import Organization
from sentry.models.projectcodeowners import ProjectCodeOwners
from sentry.models.projectrepository import ProjectRepository
from sentry.models.pullrequest import PullRequest
Expand Down Expand Up @@ -244,17 +242,10 @@ def disassociate_organization_integration(
Repository.objects.filter(id__in=repo_ids).update(integration_id=None)

# Delete Seer preferences for this repository
org = Organization.objects.get(id=organization_id)
if features.has("organizations:project-repository-fk-reads", org):
SeerProjectRepository.objects.filter(
project_repository__repository_id__in=repo_ids,
project_repository__project__organization_id=organization_id,
).delete()
else:
SeerProjectRepository.objects.filter(
repository_id__in=repo_ids,
project__organization_id=organization_id,
).delete()
SeerProjectRepository.objects.filter(
project_repository__repository_id__in=repo_ids,
project_repository__project__organization_id=organization_id,
).delete()

# Delete Code Owners with a Code Mapping using the OrganizationIntegration
ProjectCodeOwners.objects.filter(
Expand Down
10 changes: 2 additions & 8 deletions src/sentry/seer/autofix/autofix.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ def _get_serialized_event(
def _pre_resolve_stacktrace_frames(
serialized_event: dict[str, Any],
code_mappings: list[RepositoryProjectPathConfig],
use_project_repository_fk: bool = False,
) -> None:
"""
Pre-resolve stacktrace frame repo_name and filename using Sentry's code mappings
Expand All @@ -204,7 +203,7 @@ def _pre_resolve_stacktrace_frames(
# Build ordered list of (repo_full_name, code_mapping) preserving global priority
ordered_mappings: list[tuple[str, RepositoryProjectPathConfig]] = []
for cm in code_mappings:
repo = cm.project_repository.repository if use_project_repository_fk else cm.repository
repo = cm.project_repository.repository
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

N+1 queries from unprefetched project_repository FK access

Medium Severity

These callers now unconditionally access code_mapping.project_repository.repository, but get_sorted_code_mapping_configs still branches on the organizations:project-repository-fk-reads flag. When that flag is off, it only calls select_related("repository") — not project_repository or project_repository__repository. Each code mapping access then triggers two extra DB queries, creating an N+1 problem on the autofix hot path.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2dcb64e. Configure here.

repo_name_sections = repo.name.split("/")
if len(repo_name_sections) > 1 and repo.provider:
ordered_mappings.append((repo.name, cm))
Expand Down Expand Up @@ -730,12 +729,7 @@ def trigger_legacy_autofix(
# Pre-resolve stacktrace frame paths using code mappings so Seer can skip
# expensive git tree fetches for large repos.
try:
use_fk = features.has(
"organizations:project-repository-fk-reads", group.project.organization
)
_pre_resolve_stacktrace_frames(
serialized_event, code_mappings, use_project_repository_fk=use_fk
)
_pre_resolve_stacktrace_frames(serialized_event, code_mappings)
except Exception:
logger.exception("Failed to pre-resolve stacktrace frames")

Expand Down
130 changes: 41 additions & 89 deletions src/sentry/seer/autofix/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,18 +463,10 @@ def _write_preferences_to_sentry_db(
list(Project.objects.select_for_update().filter(id__in=project_ids).order_by("id"))

# Only delete SeerProjectRepository for active repos.
if features.has(
"organizations:project-repository-fk-reads",
project_preferences[0][0].organization,
):
SeerProjectRepository.objects.filter(
project_repository__project_id__in=project_ids,
project_repository__repository__status=ObjectStatus.ACTIVE,
).delete()
else:
SeerProjectRepository.objects.filter(
project_id__in=project_ids, repository__status=ObjectStatus.ACTIVE
).delete()
SeerProjectRepository.objects.filter(
project_repository__project_id__in=project_ids,
project_repository__repository__status=ObjectStatus.ACTIVE,
).delete()

all_repo_ids = {
repo_def.repository_id
Expand All @@ -493,7 +485,7 @@ def _write_preferences_to_sentry_db(
)

# Collect project repos to create.
project_repos_to_create: list[SeerProjectRepository] = []
project_repos_to_create: list[tuple[Project, int, SeerProjectRepository]] = []
overrides_by_key: dict[tuple[int, int], list[BranchOverride]] = {}
for project, pref in project_preferences:
for repo_def in pref.repositories:
Expand All @@ -514,11 +506,13 @@ def _write_preferences_to_sentry_db(
continue

project_repos_to_create.append(
SeerProjectRepository(
project=project,
repository_id=repo_def.repository_id,
branch_name=repo_def.branch_name,
instructions=repo_def.instructions,
(
project,
repo_def.repository_id,
SeerProjectRepository(
branch_name=repo_def.branch_name,
instructions=repo_def.instructions,
),
)
)

Expand All @@ -528,27 +522,25 @@ def _write_preferences_to_sentry_db(
)

if project_repos_to_create:
for spr in project_repos_to_create:
for project, repository_id, spr in project_repos_to_create:
project_repo, _ = ProjectRepository.objects.get_or_create(
project=spr.project,
repository_id=spr.repository_id,
project=project,
repository_id=repository_id,
defaults={"source": ProjectRepositorySource.SEER_PREFERENCE},
)
spr.project = project
spr.repository_id = repository_id
spr.project_repository = project_repo

created_project_repos = SeerProjectRepository.objects.bulk_create(
project_repos_to_create
[spr for _, _, spr in project_repos_to_create]
)

# Create branch overrides using the created project repos.
overrides_to_create: list[SeerProjectRepositoryBranchOverride] = []
for seer_project_repo in created_project_repos:
pr = seer_project_repo.project_repository
key = (
(pr.project_id, pr.repository_id)
if pr is not None
else (seer_project_repo.project_id, seer_project_repo.repository_id)
)
key = (pr.project_id, pr.repository_id)
for override in overrides_by_key.get(key, []):
overrides_to_create.append(
SeerProjectRepositoryBranchOverride(
Comment thread
wedamija marked this conversation as resolved.
Expand Down Expand Up @@ -608,16 +600,11 @@ def clear_preference_automation_handoff(project: Project) -> None:

def build_repo_definition_from_project_repo(
seer_project_repo: SeerProjectRepository,
use_project_repository_fk: bool = False,
) -> SeerRepoDefinition | None:
"""Build a SeerRepoDefinition from a SeerProjectRepository with its joined Repository.

Returns None if Repository name is invalid."""
if use_project_repository_fk:
pr = seer_project_repo.project_repository
repo = pr.repository if pr is not None else seer_project_repo.repository
else:
repo = seer_project_repo.repository
repo = seer_project_repo.project_repository.repository
repo_name_sections = repo.name.split("/")
if len(repo_name_sections) < 2:
sentry_sdk.capture_exception(ValueError(f"Invalid repository name format: {repo.name}"))
Expand Down Expand Up @@ -665,33 +652,18 @@ def build_automation_handoff(

def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference:
"""Read a single project's Seer preferences from Sentry DB."""
use_fk = features.has("organizations:project-repository-fk-reads", project.organization)
if use_fk:
seer_project_repo_qs = (
SeerProjectRepository.objects.filter(
project_repository__project=project,
project_repository__repository__status=ObjectStatus.ACTIVE,
)
.select_related("project_repository", "project_repository__repository")
.prefetch_related("branch_overrides")
)
else:
seer_project_repo_qs = (
SeerProjectRepository.objects.filter(
project=project, repository__status=ObjectStatus.ACTIVE
)
.select_related("repository", "project_repository", "project_repository__repository")
.prefetch_related("branch_overrides")
seer_project_repo_qs = (
SeerProjectRepository.objects.filter(
project_repository__project=project,
project_repository__repository__status=ObjectStatus.ACTIVE,
)
.select_related("project_repository", "project_repository__repository")
.prefetch_related("branch_overrides")
)
repo_definitions = [
repo_def
for project_repo in seer_project_repo_qs
if (
repo_def := build_repo_definition_from_project_repo(
project_repo, use_project_repository_fk=use_fk
)
)
is not None
if (repo_def := build_repo_definition_from_project_repo(project_repo)) is not None
]

return SeerProjectPreference(
Expand All @@ -713,29 +685,19 @@ def bulk_read_preferences_from_sentry_db(

projects = list(Project.objects.filter(id__in=project_ids, organization_id=organization_id))

org = Organization.objects.get(id=organization_id)
repo_definitions_by_project: defaultdict[int, list[SeerRepoDefinition]] = defaultdict(list)
use_fk = features.has("organizations:project-repository-fk-reads", org)
if use_fk:
seer_repo_qs = SeerProjectRepository.objects.filter(
seer_repo_qs = (
SeerProjectRepository.objects.filter(
project_repository__project_id__in=project_ids,
project_repository__repository__status=ObjectStatus.ACTIVE,
).select_related("project_repository", "project_repository__repository")
else:
seer_repo_qs = SeerProjectRepository.objects.filter(
project_id__in=project_ids, repository__status=ObjectStatus.ACTIVE
).select_related("repository", "project_repository", "project_repository__repository")
for seer_repo in seer_repo_qs.prefetch_related("branch_overrides"):
repo_def = build_repo_definition_from_project_repo(
seer_repo, use_project_repository_fk=use_fk
)
.select_related("project_repository", "project_repository__repository")
.prefetch_related("branch_overrides")
)
for seer_repo in seer_repo_qs:
repo_def = build_repo_definition_from_project_repo(seer_repo)
if repo_def is not None:
if use_fk:
pr = seer_repo.project_repository
pid = pr.project_id if pr is not None else seer_repo.project_id
else:
pid = seer_repo.project_id
repo_definitions_by_project[pid].append(repo_def)
repo_definitions_by_project[seer_repo.project_repository.project_id].append(repo_def)

# get_value_bulk_id returns None for missing options, unlike project.get_option
# which automatically falls back to the registered well-known key default.
Expand Down Expand Up @@ -844,18 +806,11 @@ def _set_if_not_default(key: str, value: Any, default: Any) -> None:

def has_project_connected_repos(organization: Organization, project: Project) -> bool:
"""Check if a project has connected repositories for Seer automation."""
if features.has("organizations:project-repository-fk-reads", organization):
return SeerProjectRepository.objects.filter(
project_repository__project=project,
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()
return SeerProjectRepository.objects.filter(
project=project,
project__organization_id=organization.id,
project__status=ObjectStatus.ACTIVE,
repository__status=ObjectStatus.ACTIVE,
project_repository__project=project,
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()


Expand All @@ -870,12 +825,9 @@ def get_autofix_repos_from_project_code_mappings(
if code_mappings is None:
code_mappings = get_sorted_code_mapping_configs(project)

use_fk = features.has("organizations:project-repository-fk-reads", project.organization)
repos: dict[tuple, dict] = {}
for code_mapping in code_mappings:
repo: Repository = (
code_mapping.project_repository.repository if use_fk else code_mapping.repository
)
repo: Repository = code_mapping.project_repository.repository
Comment thread
sentry[bot] marked this conversation as resolved.
Comment thread
wedamija marked this conversation as resolved.
repo_name_sections = repo.name.split("/")

if (
Expand Down
15 changes: 4 additions & 11 deletions src/sentry/seer/code_review/contributor_seats.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,11 @@ def _is_autofix_enabled_for_repo(organization: Organization, repository_id: int)
this repository, ie, if any project has this repository configured
in Seer preferences.
"""
if features.has("organizations:project-repository-fk-reads", organization):
return SeerProjectRepository.objects.filter(
project_repository__repository_id=repository_id,
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()
return SeerProjectRepository.objects.filter(
repository_id=repository_id,
project__organization_id=organization.id,
project__status=ObjectStatus.ACTIVE,
repository__status=ObjectStatus.ACTIVE,
project_repository__repository_id=repository_id,
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()


Expand Down
7 changes: 4 additions & 3 deletions src/sentry/seer/endpoints/group_ai_autofix.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,8 +490,9 @@ def _get_legacy(self, request: Request, group: Group) -> Response:
if project:
code_mappings = get_sorted_code_mapping_configs(project=project)
for mapping in code_mappings:
if mapping.repository.external_id:
repo_code_mappings[mapping.repository.external_id] = mapping
repo = mapping.project_repository.repository
if repo.external_id:
Comment thread
sentry-warden[bot] marked this conversation as resolved.
repo_code_mappings[repo.external_id] = mapping

for repo_external_id, repo_state in autofix_codebase_state.items():
retrieved_mapping: RepositoryProjectPathConfig | None = repo_code_mappings.get(
Expand All @@ -501,7 +502,7 @@ def _get_legacy(self, request: Request, group: Group) -> Response:
if not retrieved_mapping:
continue

mapping_repo: Repository = retrieved_mapping.repository
mapping_repo: Repository = retrieved_mapping.project_repository.repository

repositories.append(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,10 @@ def is_autofix_enabled(organization: Organization) -> bool:
Check if autofix/RCA is enabled for any active project in the organization,
ie, if any project has repositories configured in Seer preferences.
"""
if features.has("organizations:project-repository-fk-reads", organization):
return SeerProjectRepository.objects.filter(
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()
return SeerProjectRepository.objects.filter(
project__organization_id=organization.id,
project__status=ObjectStatus.ACTIVE,
repository__status=ObjectStatus.ACTIVE,
project_repository__project__organization_id=organization.id,
project_repository__project__status=ObjectStatus.ACTIVE,
project_repository__repository__status=ObjectStatus.ACTIVE,
).exists()


Expand Down
23 changes: 5 additions & 18 deletions src/sentry/seer/fetch_issues/by_function_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
from snuba_sdk import BooleanCondition, BooleanOp, Column, Condition, Entity, Function, Op, Query
from snuba_sdk import Request as SnubaRequest

from sentry import features
from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig
from sentry.models.group import Group, GroupStatus
from sentry.models.organization import Organization
from sentry.models.project import Project
from sentry.seer.constants import SeerSCMProvider
from sentry.seer.fetch_issues import utils
Expand Down Expand Up @@ -208,25 +206,14 @@ def _get_projects_and_filenames_from_source_file(
org_id: int, repo_id: int, pr_filename: str, max_num_left_truncated_paths: int = 2
) -> tuple[set[Project], set[str]]:
# Fetch the code mappings in which the source_root is a substring at the start of pr_filename
org = Organization.objects.get(id=org_id)
use_fk = features.has("organizations:project-repository-fk-reads", org)
if use_fk:
base_qs = RepositoryProjectPathConfig.objects.filter(
organization_id=org_id,
project_repository__repository_id=repo_id,
).select_related("project", "project_repository__project")
else:
base_qs = RepositoryProjectPathConfig.objects.filter(
organization_id=org_id,
repository_id=repo_id,
).select_related("project")
base_qs = RepositoryProjectPathConfig.objects.filter(
organization_id=org_id,
project_repository__repository_id=repo_id,
).select_related("project_repository__project")
code_mappings = base_qs.annotate(
substring_match=StrIndex(Value(pr_filename), "source_root")
).filter(substring_match=1)
if use_fk:
projects_set = {cm.project_repository.project for cm in code_mappings}
else:
projects_set = {cm.project for cm in code_mappings}
projects_set = {cm.project_repository.project for cm in code_mappings}
sentry_filenames = {
pr_filename.replace(code_mapping.source_root, code_mapping.stack_root, 1)
for code_mapping in code_mappings
Expand Down
Loading
Loading