Skip to content

Speed up series API lists and make list ordering deterministic - #660

Merged
bpepple merged 4 commits into
masterfrom
dev
Oct 19, 2026
Merged

bpepple merged 4 commits into
masterfrom
dev

Conversation

@bpepple

@bpepple bpepple commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Profiling the API against a production-sized database found one slow query pattern and one pagination bug.

Faster series issue counts

/api/series/, series detail, and /api/publisher/<id>/series_list/ annotated num_issues with Count("issues", distinct=True). That joins and groups every issue row before sorting and paginating, and the pagination COUNT(*) repeats the join. It now uses a correlated subquery, which Postgres evaluates only for the rows on the requested page.

Endpoint (cache miss) Before After
/api/series/ ~470 ms ~20 ms
/api/publisher/1/series_list/ ~210 ms ~16 ms
/api/series/?name=spider-man ~80 ms ~30 ms

Counts are unchanged; a series with no issues still reports 0.

List ordering

Core metadata models were ordered by fields that aren't unique. For example, 506 series share (sort_name, year_began) with another series, and 129 characters share a name. Postgres can return tied rows in a different order on each query, so clients paginating a list could see a row twice and miss another.

id is now the final Meta.ordering key on Arc, Character, Creator, Imprint, Publisher, Team, Universe, Series, and Issue. This fixes the API and web views together. It also covers the issue_list actions, whose order_by("series") expands to Series' ordering. The series API views now rely on Meta.ordering instead of their own order_by.

Migration 0061_ordering_tiebreak_by_id only changes model options and runs no SQL. Adding id had no measurable effect on query times.

Testing

  • New tests: test_issue_count (series with and without issues, across list, series_list, and detail) and test_list_orders_ties_by_id.

The series list, series detail, and publisher series_list endpoints annotated num_issues with Count("issues", distinct=True). That joins and groups every issue row before sorting and paginating, and the pagination COUNT(*) repeats the join. On production-sized data (184k issues) this took ~450ms for /api/series/ and ~200ms for a large publisher's series_list on a cache miss.

Use a correlated subquery instead. Postgres evaluates it only for the rows on the requested page, and the pagination COUNT(*) leaves it out. On the same data /api/series/ dropped to ~5ms of SQL and series_list to ~4ms. Add a test covering issue_count for series with and without issues.
/api/series/ and publisher series_list ordered by (sort_name, year_began), which isn't unique: 506 series share a value with at least one other series. Postgres may return tied rows in a different order on each query, so a client paginating the list could see a series twice and miss another. Add id as a final tiebreaker. It has no measurable cost, since the existing (sort_name, year_began) index still drives the sort.
Arc, Character, Creator, Imprint, Publisher, Team, Universe, Series, and Issue all ordered by fields that aren't unique (e.g. 129 characters share a name with another character). Postgres may return tied rows in
a different order on each query, so paginated API and web lists could show a row twice and miss another. Add id as a final ordering key on each model.

This also covers the issue_list actions, whose order_by("series") expands to Series' Meta.ordering. The migration only changes model options and runs no SQL.
@bpepple bpepple self-assigned this Oct 19, 2026
@bpepple bpepple added enhancement New feature or request api An API bug/feature labels Oct 19, 2026
@bpepple
bpepple merged commit 15fce76 into master Oct 19, 2026
2 checks passed
@bpepple
bpepple deleted the dev branch October 19, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api An API bug/feature enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant