Tags: the tag list runs one COUNT query per tag #554

Closed
opened 2026-10-01 10:47:36 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the tag list view and template and counting the page's queries in an experiment.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at applications/views.py:1085-1088 (TagListView); the template line and IndustryListView have not moved.

The Tags page prints how many applications carry each tag by calling the related manager's count() in the template, and the view does not annotate that number. Every tag therefore costs its own COUNT(*) query. The industries list, which shows the same kind of column, already annotates it in one query.

  • Where: templates/applications/tag_list.html:31 ({{ tag.applications.count }}); applications/views.py:1045-1048 (TagListView, which inherits get_queryset from OwnedObjectMixin and adds nothing). Compare jobs/views.py:639 (IndustryListView.get_queryset, annotate(company_count=Count("companies", distinct=True))).
  • What happens: with 3 tags the page makes 12 queries; with 33 tags it makes 42. The difference is exactly one SELECT COUNT(*) ... applications_application_tags per added tag. Measured with the snapshot's test settings and an in-memory database.
  • Why it matters: the cost is small at the number of tags a person usually keeps, but it is linear in the rows a person has, which is the shape tests/test_query_budget.py exists to rule out (#231). That test does not catch it: its furnish always reuses the same two tags, and the Tags page is not in PAGES.
  • Fix: give TagListView a get_queryset that returns super().get_queryset().annotate(application_count=Count("applications")), and print {{ tag.application_count }} in the template. One many-to-many join, so distinct=True is not needed.

Done when

  • A test in tests/test_query_budget.py opens applications:tag_list with 3 tags and again with 30 more, and asserts the two query counts are equal; it fails today (12 against 42) and passes after the fix.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the tag list view and template and counting the page's queries in an experiment. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `applications/views.py:1085-1088` (`TagListView`); the template line and `IndustryListView` have not moved. The Tags page prints how many applications carry each tag by calling the related manager's `count()` in the template, and the view does not annotate that number. Every tag therefore costs its own `COUNT(*)` query. The industries list, which shows the same kind of column, already annotates it in one query. - **Where:** `templates/applications/tag_list.html:31` (`{{ tag.applications.count }}`); `applications/views.py:1045-1048` (`TagListView`, which inherits `get_queryset` from `OwnedObjectMixin` and adds nothing). Compare `jobs/views.py:639` (`IndustryListView.get_queryset`, `annotate(company_count=Count("companies", distinct=True))`). - **What happens:** with 3 tags the page makes 12 queries; with 33 tags it makes 42. The difference is exactly one `SELECT COUNT(*) ... applications_application_tags` per added tag. Measured with the snapshot's test settings and an in-memory database. - **Why it matters:** the cost is small at the number of tags a person usually keeps, but it is linear in the rows a person has, which is the shape `tests/test_query_budget.py` exists to rule out (#231). That test does not catch it: its `furnish` always reuses the same two tags, and the Tags page is not in `PAGES`. - **Fix:** give `TagListView` a `get_queryset` that returns `super().get_queryset().annotate(application_count=Count("applications"))`, and print `{{ tag.application_count }}` in the template. One many-to-many join, so `distinct=True` is not needed. ## Done when - A test in `tests/test_query_budget.py` opens `applications:tag_list` with 3 tags and again with 30 more, and asserts the two query counts are equal; it fails today (12 against 42) and passes after the fix.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:36 +00:00
Author
Owner

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#554).

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#554).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#554
No description provided.