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`.
This commit is contained in:
stumpylog
2026-07-30 10:15:12 -07:00
parent acfdafbff9
commit 5d8287f120
2 changed files with 24 additions and 19 deletions
@@ -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,
},
+7 -12
View File
@@ -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))