Fix list_persons N+1 (the ~4s person-page load)
Opening any person page on a large tree took 4-5s on an idle server. Root cause: list_persons looped over every person calling privacy.person_visibility (which issues TWO get_membership_role queries per call) AND _attach_primary_name (one name query per person). On the reporter's 2,324-person tree that's ~7,000 serialized DB round-trips per page load — the person page fetches the full person list to build its name-lookup map. Fix: - Resolve the viewer's membership role ONCE. Members see the whole tree (full), so skip the per-person privacy engine entirely. - Add _attach_primary_names: one batched names query (person_id IN (...), ordered the same as the single-person query so it picks the same name) instead of one per person. - Apply the same batching to the non-member path, search_persons, the deleted- persons list, and public_view_service.list_public_persons. Member-path list_persons goes from ~3·N queries to ~3 total. Other tree-wide list endpoints (events/relationships/media/citations) were already flat selects. Adds a regression test that asserts list_persons issues a constant number of queries (not proportional to person count). Suite: 103 passing. Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
@@ -33,7 +33,11 @@ from app.models.source import Citation, Source
|
||||
from app.models.tree import Tree
|
||||
from app.services import privacy
|
||||
from app.services.exceptions import NotFound
|
||||
from app.services.person_service import _attach_primary_name, _redact
|
||||
from app.services.person_service import (
|
||||
_attach_primary_name,
|
||||
_attach_primary_names,
|
||||
_redact,
|
||||
)
|
||||
from app.services.privacy import Visibility
|
||||
|
||||
|
||||
@@ -78,6 +82,7 @@ async def list_public_persons(
|
||||
session: AsyncSession, *, viewer_id: uuid.UUID | None, tree: Tree
|
||||
) -> list[Person]:
|
||||
out: list[Person] = []
|
||||
full: list[Person] = []
|
||||
for p in await _persons(session, tree):
|
||||
vis = await privacy.person_visibility(session, user_id=viewer_id, tree=tree, person=p)
|
||||
if vis == Visibility.hidden:
|
||||
@@ -85,8 +90,9 @@ async def list_public_persons(
|
||||
if vis == Visibility.redacted:
|
||||
_redact(p)
|
||||
else:
|
||||
await _attach_primary_name(session, p)
|
||||
full.append(p)
|
||||
out.append(p)
|
||||
await _attach_primary_names(session, full) # one query, not one per person
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user