Skip to content

Comments

fix(services): eliminate n+1 query in unit detail endpoint#378

Merged
mhieta merged 1 commit intomainfrom
fix/sentry-pl-218-unit-retrieve-n-plus-1
Feb 9, 2026
Merged

fix(services): eliminate n+1 query in unit detail endpoint#378
mhieta merged 1 commit intomainfrom
fix/sentry-pl-218-unit-retrieve-n-plus-1

Conversation

@mhieta
Copy link
Contributor

@mhieta mhieta commented Jan 29, 2026

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 the services_service table 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 optimized get_queryset() method, which contains essential prefetch_related calls for related objects. Instead, it directly called _get_unit(pk), which performed a simple Unit.objects.get() query without any prefetching.

As a result, when the UnitSerializer accessed related fields during serialization (such as service_nodes, connections, accessibility_properties, and keywords), each access triggered a separate database query, creating the classic N+1 query problem.

Technical details:

  • The retrieve method bypassed get_queryset's prefetching logic
  • _get_unit() performed direct database queries: Unit.objects.get(pk=pk, public=True, is_active=True)
  • During serialization, UnitSerializer.to_representation() accessed related fields using .all() calls
  • Each related object access resulted in a new query to the database

Solution

Refactored the UnitViewSet.retrieve() method to utilize the optimized queryset from get_queryset(), ensuring that all prefetch_related optimizations are applied before fetching the unit.

Implementation:

  1. Modified UnitViewSet.retrieve() to call get_queryset() and pass the optimized queryset to _get_unit()
  2. Updated _get_unit() signature to accept an optional queryset parameter
  3. Changed query execution in _get_unit() from Unit.objects.get() to queryset.get()
  4. Maintained backward compatibility with fallback logic for UnitAlias lookups

The solution preserves all existing functionality while leveraging Django ORM's prefetch capabilities that were already configured in get_queryset().

Performance Impact

Before

  • Database Queries: 64+ repeated queries to services_service table
  • Affected Endpoint: /v2/unit/{pk}/

After

  • Database Queries: < 30 total queries (>53% reduction)
  • Response Time: Significantly improved
  • Database Load: Reduced by eliminating redundant queries
  • Scalability: Better performance under high load

Measured Improvements

  • Query count for unit detail requests: 64 → <30 (>50% reduction)
  • All related objects prefetched in single pass
  • No additional queries during serialization

References

  • Jira Ticket: Refs: PL-218
  • Issue Type: Performance - N+1 Database Queries

Impact Assessment

This fix will:

  1. Improve API response times for unit detail requests
  2. Reduce database server load by eliminating redundant queries
  3. Enhance user experience with faster page loads
  4. Improve system scalability under high traffic

The change is backward compatible and does not affect API behavior or responses - only internal query optimization.

Additional Notes

  • The get_queryset() method already contained all necessary prefetch_related calls
  • This fix simply ensures those optimizations are actually used during single-unit retrieval
  • The solution follows Django best practices for ORM optimization
  • No changes required to serializers or models
  • Fix is minimal and focused on the root cause

@mhieta mhieta marked this pull request as ready for review January 29, 2026 13:30
@mhieta mhieta requested a review from a team as a code owner January 29, 2026 13:30
@azure-pipelines
Copy link

PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀

@mhieta mhieta force-pushed the fix/sentry-pl-218-unit-retrieve-n-plus-1 branch from fa51d6e to 1fd6fe0 Compare January 29, 2026 13:53
@azure-pipelines
Copy link

PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀

@mhieta
Copy link
Contributor Author

mhieta commented Jan 30, 2026

@sentry review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 call get_queryset() and pass the optimized queryset to _get_unit()
  • Updated _get_unit() to accept an optional queryset parameter 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.

@mhieta mhieta force-pushed the fix/sentry-pl-218-unit-retrieve-n-plus-1 branch from 1fd6fe0 to 364f682 Compare January 30, 2026 09:10
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

@azure-pipelines
Copy link

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
@mhieta mhieta force-pushed the fix/sentry-pl-218-unit-retrieve-n-plus-1 branch from 364f682 to 65ba0d8 Compare February 9, 2026 12:36
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 9, 2026

@azure-pipelines
Copy link

PALVELUKARTTA-API branch is deployed to platta: https://palvelukartta-api-pr378.dev.hel.ninja 🚀🚀🚀

@mhieta mhieta merged commit 6bb0d8f into main Feb 9, 2026
7 checks passed
@mhieta mhieta deleted the fix/sentry-pl-218-unit-retrieve-n-plus-1 branch February 9, 2026 13:59
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.

2 participants