Skip to content

Conversation

@jacob-a-brown
Copy link
Contributor

@jacob-a-brown jacob-a-brown commented Dec 8, 2025

This task does not have a Jira ticket. @jirhiker and I discussed this refactor to improve API performance Friday, December 5.

Why

This PR addresses the following problem / context:

  • Eager loading in model definitions made it so that too much data was being loaded for /geospatial and other routers when it was not needed

How

Implementation summary - the following was changed / added / removed:

  • Removed eager loading from almost all models and defined the eager loading at the endpoints using selectinload. This gives us more granular control over when eager loading is utilized.
  • All the items in things in the ContactResponse only need the id and name of the thing. Because the ThingResponse requires a number of fields from related tables, and because eager loading is no longer the default as defined in the Thing model, every item in the things list just returns the id and name. If the full ThingResponse was used we'd run into an N+1 issue due to lazy loading.
    • The necessary fields were determined by looking at the frontend code. Please let me know if I am mistaken in this interpretation and I'll update the PR accordingly.
  • parameter remains eagerly loaded for each observation because that is a relationship that will always be loaded, used, and displayed.
  • All polymorphic relationships are still eagerly loaded.

Notes

Any special considerations, workarounds, or follow-up work to note?

  • A query, rather than select, is used for samples. Should I refactor to select since that's used elsewhere in the repo?

Because eager loading was defined in the models, it was being applied
every time those models were queried, leading to unnecessary data being
loaded in many cases. This change moves the eager loading logic to the
service layer, applying it only at the endpoints that require it. This
optimizes performance by reducing redundant data fetching and ensures
that only the necessary related data is loaded when needed.
If a full ThingResponse is used in ContactResponse, it can lead to
performance issues due to lazy loading of unneeded fields. To preclude
this from happening, a new ThingResponseForContact schema has been created
since all that is needed in this context are the id and name of the Things
related to a Contact.
moving forward all eager loading will be defined at endpoints,
not in the model definitions (unless they are in polymorphic models)
every observation is linked to a parameter, so eager loading
of parameters when loading observations will always be needed
@codecov-commenter
Copy link

codecov-commenter commented Dec 8, 2025

Codecov Report

❌ Patch coverage is 92.00000% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
services/thing_helper.py 88.88% 2 Missing ⚠️
Files with missing lines Coverage Δ
api/thing.py 83.56% <100.00%> (+0.22%) ⬆️
db/deployment.py 100.00% <100.00%> (ø)
db/location.py 88.63% <100.00%> (ø)
db/observation.py 100.00% <ø> (ø)
db/thing.py 97.95% <ø> (-0.02%) ⬇️
db/thing_aquifer_association.py 100.00% <ø> (ø)
db/thing_geologic_formation_association.py 100.00% <ø> (ø)
schemas/contact.py 100.00% <100.00%> (ø)
services/thing_helper.py 50.00% <88.88%> (+1.77%) ⬆️

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 201 to 208
class ThingResponseForContact(BaseModel):
"""
Response schema for thing details related to a contact. All that is needed
are the id and name
"""

id: int
name: str | None = None

Choose a reason for hiding this comment

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

P1 Badge Enable ORM parsing for contact things

Switching ContactResponse.things to ThingResponseForContact introduced a model that inherits BaseModel without model_config = ConfigDict(from_attributes=True). Contact responses are built directly from SQLAlchemy Contact objects, and the things association proxy yields Thing instances; without from_attributes the nested model cannot read their attributes and will throw validation errors whenever a contact has related things (affecting GET /contact* and sample responses). The previous ThingResponse worked because it inherited BaseResponseModel with from_attributes enabled.

Useful? React with 👍 / 👎.

@jirhiker jirhiker merged commit 567e764 into staging Dec 8, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants