From 5d8287f1208b8249c8fddf7c960d6eaeb0e9f356 Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:15:12 -0700 Subject: [PATCH] fix(benchmark): reuse seeded data in profile instead of reseeding _handle_profile unconditionally called seed_benchmark_dataset every run, colliding with an IntegrityError against data seeded by a prior seed/profile call. Look up the existing perf_target user instead, matching _handle_run's already-correct pattern, and raise a CommandError pointing at `seed` if no dataset exists. Scenario.run/queryset_for_explain now take a User directly instead of a SeededData, since only data.users[0] (perf_target) was ever used. profile no longer seeds, so it drops the --tier dependency: the printed line and history entry no longer include a tier/(tier=...) suffix, and --tier's help text now only mentions `seed`. --- .../management/commands/benchmark.py | 24 +++++++++++++------ src/paperless_benchmark/scenarios.py | 19 ++++++--------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/paperless_benchmark/management/commands/benchmark.py b/src/paperless_benchmark/management/commands/benchmark.py index a9caf6979..0131e9f6b 100644 --- a/src/paperless_benchmark/management/commands/benchmark.py +++ b/src/paperless_benchmark/management/commands/benchmark.py @@ -27,7 +27,7 @@ class Command(BaseCommand): "--tier", choices=["home", "medium", "large"], default="medium", - help="Dataset scale tier for `seed` and `profile` (default: medium).", + help="Dataset scale tier for `seed` (default: medium).", ) parser.add_argument( "--reset", @@ -131,11 +131,12 @@ class Command(BaseCommand): ) def _handle_profile(self, options: dict[str, Any]) -> None: + from django.contrib.auth import get_user_model + from paperless_benchmark.db import capture_explain from paperless_benchmark.harness import run_profile from paperless_benchmark.results import append_history from paperless_benchmark.scenarios import get as get_scenario - from paperless_benchmark.seeding import seed_benchmark_dataset if not options["scenario"]: raise CommandError( @@ -143,23 +144,32 @@ class Command(BaseCommand): ) scenario = get_scenario(options["scenario"]) - data = seed_benchmark_dataset(options["tier"], seed=options["seed"]) - profile = run_profile(lambda: scenario.run(data), repeat=options["repeat"]) + user_model = get_user_model() + try: + perf_target = user_model.objects.get(username="perf_target") + except user_model.DoesNotExist as e: + raise CommandError( + "No benchmark dataset found. Run `manage.py benchmark seed` first.", + ) from e + + profile = run_profile( + lambda: scenario.run(perf_target), + repeat=options["repeat"], + ) self.stdout.write( f"{scenario.name}: best={profile.best_seconds:.4f}s " - f"queries={profile.query_count} (tier={options['tier']})", + f"queries={profile.query_count}", ) if options["explain"] and scenario.queryset_for_explain is not None: - plan = capture_explain(scenario.queryset_for_explain(data)) + plan = capture_explain(scenario.queryset_for_explain(perf_target)) self.stdout.write(plan) append_history( { "mode": "profile", "scenario": scenario.name, - "tier": options["tier"], "best_seconds": profile.best_seconds, "query_count": profile.query_count, }, diff --git a/src/paperless_benchmark/scenarios.py b/src/paperless_benchmark/scenarios.py index 069a6da37..9e38fb20e 100644 --- a/src/paperless_benchmark/scenarios.py +++ b/src/paperless_benchmark/scenarios.py @@ -8,17 +8,16 @@ from typing import Any if TYPE_CHECKING: from collections.abc import Callable + from django.contrib.auth.models import User from django.db.models import QuerySet - from paperless_benchmark.seeding import SeededData - @dataclass(frozen=True, slots=True) class Scenario: name: str describe: str - run: Callable[[SeededData], Any] - queryset_for_explain: Callable[[SeededData], QuerySet] | None = None + run: Callable[[User], Any] + queryset_for_explain: Callable[[User], QuerySet] | None = None _SCENARIOS: dict[str, Scenario] = {} @@ -44,11 +43,10 @@ def all_scenarios() -> tuple[Scenario, ...]: return tuple(_SCENARIOS.values()) -def _guardian_visibility_query_run(data: SeededData) -> list[int]: +def _guardian_visibility_query_run(user: User) -> list[int]: from documents.models import Document from documents.permissions import get_objects_for_user_owner_aware - user = data.users[0] return list( get_objects_for_user_owner_aware( user, @@ -58,11 +56,10 @@ def _guardian_visibility_query_run(data: SeededData) -> list[int]: ) -def _guardian_visibility_query_queryset(data: SeededData) -> QuerySet: +def _guardian_visibility_query_queryset(user: User) -> QuerySet: from documents.models import Document from documents.permissions import get_objects_for_user_owner_aware - user = data.users[0] return get_objects_for_user_owner_aware(user, "documents.view_document", Document) @@ -80,11 +77,10 @@ register( ) -def _permitted_document_ids_run(data: SeededData) -> list[int]: +def _permitted_document_ids_run(user: User) -> list[int]: from documents.models import Document from documents.permissions import permitted_document_ids - user = data.users[0] return list( Document.objects.filter(id__in=permitted_document_ids(user)).values_list( "id", @@ -93,11 +89,10 @@ def _permitted_document_ids_run(data: SeededData) -> list[int]: ) -def _permitted_document_ids_queryset(data: SeededData) -> QuerySet: +def _permitted_document_ids_queryset(user: User) -> QuerySet: from documents.models import Document from documents.permissions import permitted_document_ids - user = data.users[0] return Document.objects.filter(id__in=permitted_document_ids(user))