fix(services): eliminate n+1 query in unit detail endpoint#378
Conversation
|
PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀 |
fa51d6e to
1fd6fe0
Compare
|
PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀 |
|
@sentry review |
There was a problem hiding this comment.
Pull request overview
This pull request fixes an N+1 query performance issue in the unit detail API endpoint (/v2/unit/{pk}/) that was causing up to 64+ database queries per request. The fix ensures that the retrieve() method uses the optimized queryset with prefetch operations, reducing queries by over 50% while maintaining backward compatibility with UnitAlias lookups.
Changes:
- Modified
UnitViewSet.retrieve()to callget_queryset()and pass the optimized queryset to_get_unit() - Updated
_get_unit()to accept an optionalquerysetparameter and use it for database operations - Added comprehensive test coverage to prevent regression of the N+1 query issue
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| services/api.py | Refactored _get_unit() and retrieve() methods to use prefetched queryset, eliminating N+1 queries while preserving UnitAlias functionality |
| services/tests/test_unit_view_set_api.py | Added four new test cases covering N+1 prevention, include parameters, alias access, and filter enforcement |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1fd6fe0 to
364f682
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀 |
Resolves an N+1 query performance issue affecting the /v2/unit/{pk}/
endpoint that was causing up to 64 repeated database queries for the
services_service table when retrieving a single unit.
Root cause: The UnitViewSet.retrieve() method bypassed the optimized
get_queryset() which contains prefetch_related calls, by directly
calling _get_unit(pk) which performed Unit.objects.get() without any
prefetching. This led to individual database queries during
serialization when the UnitSerializer accessed related fields like
service_nodes, connections, accessibility_properties, and keywords.
Solution: Refactored UnitViewSet.retrieve() to use get_queryset() to
obtain an optimized queryset with prefetching enabled. Updated
_get_unit() to accept an optional queryset parameter, allowing it to
use the prefetched queryset while maintaining backward compatibility
and fallback behavior for UnitAlias lookups.
Changes:
- Modified UnitViewSet.retrieve() to call get_queryset() before
fetching the unit
- Updated _get_unit() signature to accept optional queryset parameter
- Changed _get_unit() to use queryset.get() instead of
Unit.objects.get()
- Added comprehensive tests to verify proper prefetching and prevent
regression
Impact: Reduces database queries from 64+ to under 30 for typical unit
detail requests, improving API response time and reducing database
load.
Refs: PL-218
364f682 to
65ba0d8
Compare
|
|
PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀 |



Problem
Production users were experiencing performance degradation on the
/v2/unit/{pk}/API endpoint due to an N+1 query issue. The endpoint was executing up to 64 repeated database queries to theservices_servicetable when retrieving a single unit, causing slow response times and increased database load.Affected endpoint:
/v2/unit/{pk}/Root Cause
The
UnitViewSet.retrieve()method bypassed the optimizedget_queryset()method, which contains essentialprefetch_relatedcalls for related objects. Instead, it directly called_get_unit(pk), which performed a simpleUnit.objects.get()query without any prefetching.As a result, when the
UnitSerializeraccessed related fields during serialization (such asservice_nodes,connections,accessibility_properties, andkeywords), each access triggered a separate database query, creating the classic N+1 query problem.Technical details:
retrievemethod bypassedget_queryset's prefetching logic_get_unit()performed direct database queries:Unit.objects.get(pk=pk, public=True, is_active=True)UnitSerializer.to_representation()accessed related fields using.all()callsSolution
Refactored the
UnitViewSet.retrieve()method to utilize the optimized queryset fromget_queryset(), ensuring that allprefetch_relatedoptimizations are applied before fetching the unit.Implementation:
UnitViewSet.retrieve()to callget_queryset()and pass the optimized queryset to_get_unit()_get_unit()signature to accept an optionalquerysetparameter_get_unit()fromUnit.objects.get()toqueryset.get()UnitAliaslookupsThe solution preserves all existing functionality while leveraging Django ORM's prefetch capabilities that were already configured in
get_queryset().Performance Impact
Before
services_servicetable/v2/unit/{pk}/After
Measured Improvements
References
Impact Assessment
This fix will:
The change is backward compatible and does not affect API behavior or responses - only internal query optimization.
Additional Notes
get_queryset()method already contained all necessaryprefetch_relatedcalls