CVs: every page, preview and download fetches each entry with its own query, and each skill group three more #559
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#559
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, by counting the queries of the CV pages, exports and API at two CV sizes.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atdocuments/rendering.py:133-134(build_sections),documents/models.py:257-259(CV.included_items) anddocuments/models.py:315-316(CVItem.__str__),resume/translating.py:269-272(named_by_classification),resume/translating.py:368(fallen_back) andresume/translating.py:202-208(skill_names); the views, the API router,resume/models.pyand the templates have not moved.A CV item points at its entry through a
GenericForeignKey, and every reader walks the items and readsitem.itemwithout prefetching it, so each entry costs a query. Skill groups then cost three more each when they are shown in a language. The cost grows with every entry on the CV, on the page somebody edits most and on every export.documents/rendering.py:159-160(build_sections:cv.included_items()thencv_item.itemper row), which feeds the preview, the text view, the.txtand.docxdownloads and every PDF renderdocuments/models.py:217-219(CV.included_items:select_related("content_type")only)documents/views.py:159(CVDetailView:self.object.items, read as{{ item.item }}attemplates/documents/cv_detail.html:130,133)resume/translating.py:353-356(named_by_classification) andresume/translating.py:476(fallen_back), which walk the same items again for the CV pageresume/translating.py:286-292(skill_nameson the translated entry):entry.skills.all(),overrides_by_entry(skills, …)andentry.owner, one query each per groupapi/routers/documents.py:40-46(_cv_out:str(item)per item, which readsitem.itemthroughCVItem.__str__,documents/models.py:275-276)templates/resume/overview.html:93-94,146withresume/models.py:135-137(SkillGroup.skill_names): the career overview reads each group's skills twice, with no prefetch.txt,.docx: 22 → 102; text view: 24 → 104GET /api/v1/cvs/{id}: 26 → 82Each PDF render runs the same
build_sections.cv.included_items().prefetch_related("item")(one query per content type), and inCVDetailViewand_cv_outlikewise.GenericPrefetch("item", [SkillGroup.objects.prefetch_related("skills"), …])(already used injobs/history.py:261).skill_names, pass the owner down (or useentry.owner_id) and look up overrides and classified names once for all the CV's skills rather than per group.prefetch_related("skills")for the skill-group section and readskill_namesfrom the prefetched list.Done when
tests/test_query_budget.pyrenders the CV page, the preview, the.docxdownload andGET /api/v1/cvs/{id}for a CV with 7 entries and with 63, and asserts the same number of queries for both; likewise the career overview with 1 and 9 skill groups.Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#559).