Skip to content

feat(taxon-tiles): improve performance and scope to collection - #8584

Open
grantfitzsimmons wants to merge 7 commits into
mainfrom
issue-303
Open

grantfitzsimmons wants to merge 7 commits into
mainfrom
issue-303

Conversation

@grantfitzsimmons

@grantfitzsimmons grantfitzsimmons commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Fixes #303

Warning

This PR affects database migrations. See migration testing instructions.

This PR makes taxon_bar collection-scoped and improves the performance of current-determination counts.

For some reason, taxon rows were filtered by collection discipline, but determination counts were not restricted to the active collection. The count query also lacked an index matching its collection-scoped predicates (so it took sooo long to complete). In the future, we should consider using Materialized Views in PostgreSQL once we migrate since this should be cached for general cases (like statistics visualizations in most contexts).

performance_tiles.mp4

Changes

  • Count only current determinations whose collectionmemberid matches the active collection.
  • Replace the previous annotation path with a correlated count lookup.
  • Add DetColMemCurrentTaxonIDX on (collectionmemberid, iscurrent, taxon).
  • Add regression coverage for determinations belonging to another collection.

User-visible changes

  • Taxon tile counts now reflect the selected collection.
  • Taxon tiles load substantially faster on large determination tables.

Performance

On the KU entomology database with 1,786,726 determinations and 42,520 taxa:

  • taxon_bar with the index: approximately 0.29–0.39 seconds.
  • taxon_bar without the index: approximately 4.10–4.22 seconds.
  • Index size: approximately 37.6 MiB at this database size, extrapolating to roughly 1 GiB at 50 million determinations.

Checklist

  • Added automated regression coverage.
  • Added the required migration.
  • Verified the migration locally by rolling it back and applying it again.
  • Reviewed the final diff for unrelated changes and debug code.
  • Link the relevant issue and add it to the release milestone.
  • Add the PR to the documentation list when appropriate.

Testing

  • Use a database that has over 1 million objects in it
  • Compare the same database on both main and issue-303 (noting that this PR introduces a migration)
  • Verify that loading taxon tiles is much faster (or at least justifiably faster)

Summary by CodeRabbit

  • Bug Fixes
    • Taxon bar counts now reflect distinct collection objects with current determinations in the selected collection. Noncurrent determinations and records from other collections are excluded, and taxa without matching objects show a count of zero.
  • Performance
    • Taxon bar count lookups are optimized, which may improve loading times for collections with many determination records.

@coderabbitai

This comment was marked as outdated.

@grantfitzsimmons grantfitzsimmons changed the title Issue 303 feat(taxon-tiles): improve performance and scope to collection Sep 26, 2026
coderabbitai[bot]

This comment was marked as outdated.

@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Sep 26, 2026
@grantfitzsimmons
grantfitzsimmons marked this pull request as ready for review September 26, 2026 15:37
@grantfitzsimmons

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@grantfitzsimmons

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@grantfitzsimmons
grantfitzsimmons requested review from a team September 30, 2026 00:45

@melton-jason melton-jason left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yayyy, more performance stuff! 🚀

For some reason, taxon rows were filtered by collection discipline, but determination counts were not restricted to the active collection.

I don't think it was ever a requirement to only return Determinations in the current collection? 🤷
I can see there being some benefit in getting a birds-eye view of how large the default Taxon tree is as a whole (across all Collections within the Discipline), though it is probably more useful to each individual Collection Manager to see results scoped to just one Collection so I'm in favor of the change.

Perhaps CollectionMemberID was considered too unreliable (though MariaDB should still be able to fairly cheaply do the Collection scoping through the CollectionObject?
On a side note, we should probably look into changing these CollectionMemberID columns across tables to dedicated Foreign Keys).


I just very briskly looked through the non-test code and did not do any testing, profiling, etc.

Few notes here: mainly for gathering better profiling information and ways to enhance this in the future.

Though I would like to see us remove the existing index which contains just CollectionMemberID. The new composite index already covers the old single-column index. Can't think of a good reason to keep the old single column index. If there is one, we can keep it though.

Comment on lines +25 to +35
taxons = Taxon.objects.annotate(
current_determination_count=Coalesce(
Subquery(
current_determination_counts,
output_field=IntegerField(),
),
0,
)
).values_list(
'id', 'rankid', 'parent_id', 'name', 'current_determination_count'
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(future enhancement notes!)
Just quick note on scalability here. None of these would be strictly required, but something to consider for future enhancements!
These QuerySets can still suffer from #7864.
That is, if there is a large number of Taxon objects returned from the database to this QuerySet, Django will hold them all in memory at once (values_list is very nice, but a few million Taxon records could still hold a lot of memory and converting it all to a list might use the CPU for a time).

To make this even more performant, I would recommend a streaming/pagination approach (specifically using a keyset/cursor method) to the frontend, where the frontend fetches pages of Taxon records at a time.
That would limit the amount of work the frontend*, backend, and database has to do for each individual fetch.
Alongside the memory and performance benefits, this would also perform much better for #7880 a Gunicorn worker being caught with a long-running task can not respond to other requests.
This is especially true for CPU intensive operations: in a high-thread low-process setup like we have in production environments, a CPU hungry operation can theoretically impact how long it holds the python GIL, impacting other threads in the same process.

Barring with or in addition a streaming approach, it would be nice to move these large and memory intensive operations to a separate process, so the memory can be immediately reclaimed when they're finished (or so that the OOMKiller doesn't have to kill the entire worker process, if it is invoked).

).values_list(
'id', 'rankid', 'parent_id', 'name', 'current_determination_count'
)
return list(filter_by_collection(taxons, collection))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional / note to self)

When I have a moment, I will do so, but if someone is up for it can they run this Query through a MariaDB ANALYZE statement? 😅

Something like ANALYZE FORMAT=JSON to see the MariaDB execution/optimizer plan.

Any MariaDB version, buffer pool size, or page size would do. Would be interesting to see how it compares across different databases and from the previous version 👀

Comment on lines +11 to +17
migrations.AddIndex(
model_name='determination',
index=models.Index(
fields=['collectionmemberid', 'iscurrent', 'taxon'],
name='DetColMemCurrentTaxonIDX',
),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do keep this index as-is we should remove the DetMemIDX Index on Determination. That index is just CollectionMemberID.

It is (almost*) completely redundant (taking up storage space, slowing down writes, taking up room in the Buffer Pool, etc.) if we have a composite index with CollectionMemberID as the first column.

Comment on lines +2891 to +2894
models.Index(
fields=['collectionmemberid', 'iscurrent', 'taxon'],
name='DetColMemCurrentTaxonIDX',
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note / optional)

Are there any other Queries we can modify to utilize this index? 👀
Would be nice if there were.

If I have the time, I'll do some investigation into this index (how it plays with other index pages in the buffer pool-- specifically whether it can significantly impact future buffer pool hits when the buffer is already otherwise full), increased write times for updating Determinations, cases where InnoDB will usually fall back to a full table scan (e.g., when using a single column of an index is estimated to return ~20-30% of objects in the table), verify the index size assuming default page size, see if there's another optimal ordering of the index, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Taxon tiles are not filtered by collection.

4 participants