Skip to content

Commit 4f93bf6

Browse files
authored
fix(Code References): Very slow data migration (#7555)
1 parent dc60398 commit 4f93bf6

1 file changed

Lines changed: 37 additions & 14 deletions

File tree

api/projects/code_references/migrations/0003_introduce_per_feature_scanned_references.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
from itertools import groupby
44
from operator import attrgetter
5-
from typing import TypedDict
5+
from typing import NamedTuple, TypedDict
66

77
import django.db.models.deletion
88
from django.apps.registry import Apps
@@ -42,18 +42,33 @@ def migrate_scans_forward(apps: Apps, _: object) -> None:
4242
"vcs_provider",
4343
).annotate(last_scanned_at=Max("created_at"))
4444

45-
repositories = {
46-
(summary["project_id"], summary["repository_url"]): Repository.objects.create(
45+
repositories_to_create = [
46+
Repository(
4747
project_id=summary["project_id"],
4848
url=summary["repository_url"],
4949
vcs_provider=summary["vcs_provider"],
5050
last_scanned_at=summary["last_scanned_at"],
5151
)
5252
for summary in legacy_scans_summaries
53+
]
54+
Repository.objects.bulk_create(repositories_to_create)
55+
repositories = {
56+
(repository.project_id, repository.url): repository
57+
for repository in repositories_to_create
5358
}
5459

55-
# Oldest-first per project so the newest scan wins on hash collisions
56-
legacy_scans = LegacyScan.objects.order_by("project_id", "created_at").iterator()
60+
class PerFeatureScanKey(NamedTuple):
61+
feature_id: int
62+
repository_id: int
63+
code_references_hash: str
64+
65+
# Iteration is oldest-first, so overwriting on key collision means the
66+
# newest legacy scan wins for a given (feature, repository, hash).
67+
per_feature_rows: dict[PerFeatureScanKey, models.Model] = {}
68+
69+
legacy_scans = LegacyScan.objects.order_by("project_id", "created_at").iterator(
70+
chunk_size=200,
71+
)
5772
grouped_scans = groupby(legacy_scans, key=attrgetter("project_id"))
5873
for project_id, project_scans in grouped_scans:
5974
features = {
@@ -64,8 +79,7 @@ def migrate_scans_forward(apps: Apps, _: object) -> None:
6479
)
6580
}
6681
for legacy_scan in project_scans:
67-
repository_url = legacy_scan.repository_url
68-
repository = repositories[project_id, repository_url]
82+
repository = repositories[project_id, legacy_scan.repository_url]
6983

7084
references_by_feature: dict[str, list[StoredCodeReference]] = {}
7185
for reference in legacy_scan.code_references:
@@ -80,17 +94,26 @@ def migrate_scans_forward(apps: Apps, _: object) -> None:
8094
for feature_name, references in references_by_feature.items():
8195
if not (feature := features.get((project_id, feature_name))):
8296
continue
83-
PerFeatureScan.objects.update_or_create(
97+
references_hash = _hash_references(references)
98+
key = PerFeatureScanKey(
99+
feature_id=feature.id,
100+
repository_id=repository.id,
101+
code_references_hash=references_hash,
102+
)
103+
per_feature_rows[key] = PerFeatureScan(
84104
feature=feature,
85105
repository=repository,
86-
code_references_hash=_hash_references(references),
87-
defaults={
88-
"revision": legacy_scan.revision,
89-
"code_references": references,
90-
"created_at": legacy_scan.created_at,
91-
},
106+
code_references_hash=references_hash,
107+
revision=legacy_scan.revision,
108+
code_references=references,
109+
created_at=legacy_scan.created_at,
92110
)
93111

112+
PerFeatureScan.objects.bulk_create(
113+
per_feature_rows.values(),
114+
batch_size=1000,
115+
)
116+
94117

95118
def migrate_scans_backward(apps: Apps, _: object) -> None:
96119
"""Mirror each per-feature row back into the legacy single-table layout."""

0 commit comments

Comments
 (0)