GET /api/v1/documents reads every upload and every sent document, texts included, to answer one page #551
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#551
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 reading the documents router and timing one page at two sizes.Checked again on 2026-10-01 against
mainat0a5cad587: still present.list_documentsbuilds a Python list of everyUploadedDocumentand everyRenderedDocumentthe owner has, sorts it in Python and hands it to the paginator. The rendered rows are loaded whole, includingsource_textandplain_text, the full text of every CV and letter ever sent, althoughdocument_outuses none of it. So the cost of one page grows with every document sent, and walking the list withoffsetdoes quadratic work.api/routers/documents.py:106-143(list_documents:rows += [... for d in changed_since(owned(request, RenderedDocument.objects), updated_since)], thenrows.sort(...)andreturn rows);api/schemas.py:843-857(document_outreads only ids, kind, title, language, application, timestamps)limit=20page took 7 ms with 30 sent documents and 62 ms with 600; walking all 600 in pages of 100 read the whole table six times.uploads.values("pk", "created_at", "updated_at").annotate(source=Value("upload")).union(renders.values(...).annotate(source=Value("rendered")))ordered by-created_at, -pk(orupdated_at, pkwithupdated_since), slice it, then fetch that page's rows by id from each table. At the least,.only(...)the fieldsdocument_outreads, or.defer("source_text", "plain_text")on the rendered rows.Related to #230 (closed), whose item 1 moved the other lists to shaping only the page and left this one reading every row.
Done when
tests/test_api_lists.pyasksGET /api/v1/documents?limit=20for an owner with 5 and with 50 rendered documents and asserts that no query selectssource_textand that the rows fetched are bounded by the page size, not the total.Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#551).