fix(#1962): resolve N+1 query fixes breaking tests#1975
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request fixes N+1 query issues introduced by previous optimizations to the server service. The changes address the problem where __dict__.copy() returns an empty dictionary when using SQLAlchemy's scalars().all() eager loading pattern, and ensures consistent team name loading across all server operations.
Key Changes
- Refactored
convert_server_to_readto explicitly build server dictionaries from attributes instead of using__dict__.copy() - Added new
email_teamrelationship andteamproperty to theServermodel for efficient team name loading - Added
joinedload(DbServer.email_team)to query methods (list_servers,list_servers_for_user,get_server,update_server) - Removed the
_get_team_namehelper method that was causing N+1 queries
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| mcpgateway/db.py | Added email_team relationship and team property to Server model for efficient team name loading via joinedload |
| mcpgateway/services/server_service.py | Refactored convert_server_to_read to build dict explicitly; added joinedload(DbServer.email_team) to list/get/update operations; removed _get_team_name method |
| tests/unit/mcpgateway/services/test_server_service.py | Updated mock_server fixture with explicit None values; updated test mocks to accept options parameter; added regression test for team name loading |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test_db.get.assert_called_once_with(DbServer, 1) | ||
| # commit called twice: once for status change, once in _get_team_name to release transaction | ||
| assert test_db.commit.call_count == 2 | ||
| # commit called once for status change (team name loaded via email_team relationship property) | ||
| assert test_db.commit.call_count == 1 |
There was a problem hiding this comment.
The comment on line 818-819 correctly notes that team name is loaded via the email_team relationship property, but the assertion on line 817 still expects test_db.get.assert_called_once_with(DbServer, 1) without an options parameter. Since toggle_server_status accesses server.team property (which uses the email_team relationship), the service method should use eager loading with joinedload(DbServer.email_team) to avoid N+1 queries. The test should either be updated to expect the options parameter (like line 560) or the service method needs to be updated to include eager loading.
mcpgateway/db.py
Outdated
| """Return the team name from the eagerly-loaded email_team relationship. | ||
|
|
There was a problem hiding this comment.
The docstring states "Return the team name from the eagerly-loaded email_team relationship," but the relationship uses default lazy loading according to the comment on lines 4243-4245. The docstring should clarify that the relationship may be lazily loaded unless explicitly eager-loaded with joinedload(DbServer.email_team). Consider updating to: "Return the team name from the email_team relationship. The relationship uses lazy loading by default and should be eagerly loaded with joinedload for optimal performance."
| """Return the team name from the eagerly-loaded email_team relationship. | |
| """Return the team name from the email_team relationship. | |
| The email_team relationship uses lazy loading by default and may trigger | |
| an additional query when accessed. For list or admin views, it should be | |
| eagerly loaded with joinedload(Server.email_team) for optimal performance. |
b19b4da to
44a36c8
Compare
8403e6a to
a9e4203
Compare
…N+1 queries (#1957) Add email_team relationship and team property to Server model, replacing the _get_team_name helper method. Use joinedload for list operations to fetch team names in a single query instead of per-server lookups. Signed-off-by: Jonathan Springer <jps@s390x.com>
- Fix convert_server_to_read to explicitly build server dict from attributes instead of using __dict__.copy(), which can return empty dict with SQLAlchemy's scalars().all() eager loading pattern - Add missing joinedload(DbServer.email_team) to get_server and update_server methods for consistent team name loading - Update test mocks to accept new options parameter for db.get() calls - Update mock_server fixture with explicit None values for optional tracking fields to prevent MagicMock auto-creation Closes #1962 Signed-off-by: Jonathan Springer <jps@s390x.com>
Add selectinload/joinedload options to db.get() call in toggle_server_status to prevent N+1 queries when accessing server relationships (tools, resources, prompts, a2a_agents, email_team). This completes the N+1 fix for issue #1962 which listed toggle_server_status as an affected function with medium impact. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
a9e4203 to
cf95ec6
Compare
- admin.py: Remove manual team assignment loop that would fail since Server.team is now a read-only @Property. Add joinedload for email_team relationship instead. - export_service.py: Add selectinload for tools relationship in _export_selected_servers to prevent N+1 queries when accessing db_server.tools in a loop. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Additional Fixes Applied During ReviewThis PR was rebased onto main and extended with additional fixes discovered during code review. Commits Added
Changes Summarymcpgateway/services/server_service.py
mcpgateway/admin.py
mcpgateway/services/export_service.py
tests/unit/mcpgateway/services/test_server_service.py
Out of ScopeGateway N+1 queries ( Test Results
|
Add email_team relationship and team property to Gateway model, mirroring the optimization applied to Server model in PR #1962 and #1975. Changes: - Add email_team relationship to Gateway model (db.py) - Add team property for convenient access to team name - Update gateway_service.py to use joinedload(DbGateway.email_team) - Remove _get_team_name helper method (no longer needed) - Update tests to mock db.execute instead of db.get This reduces database queries by ~50% for gateway operations that need team information, eliminating the N+1 query pattern. Closes #1994 Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Add email_team relationship and team property to Gateway model, mirroring the optimization applied to Server model in PR #1962 and #1975. Changes: - Add email_team relationship to Gateway model (db.py) - Add team property for convenient access to team name - Update gateway_service.py to use joinedload(DbGateway.email_team) - Remove _get_team_name helper method (no longer needed) - Update tests to mock db.execute instead of db.get This reduces database queries by ~50% for gateway operations that need team information, eliminating the N+1 query pattern. Closes #1994 Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
* perf: eager load EmailTeam relationship in Server model to eliminate N+1 queries (IBM#1957) Add email_team relationship and team property to Server model, replacing the _get_team_name helper method. Use joinedload for list operations to fetch team names in a single query instead of per-server lookups. Signed-off-by: Jonathan Springer <jps@s390x.com> * fix(IBM#1962): resolve N+1 query fixes breaking tests - Fix convert_server_to_read to explicitly build server dict from attributes instead of using __dict__.copy(), which can return empty dict with SQLAlchemy's scalars().all() eager loading pattern - Add missing joinedload(DbServer.email_team) to get_server and update_server methods for consistent team name loading - Update test mocks to accept new options parameter for db.get() calls - Update mock_server fixture with explicit None values for optional tracking fields to prevent MagicMock auto-creation Closes IBM#1962 Signed-off-by: Jonathan Springer <jps@s390x.com> * perf(IBM#1962): add eager loading to toggle_server_status Add selectinload/joinedload options to db.get() call in toggle_server_status to prevent N+1 queries when accessing server relationships (tools, resources, prompts, a2a_agents, email_team). This completes the N+1 fix for issue IBM#1962 which listed toggle_server_status as an affected function with medium impact. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(IBM#1962): fix team property assignment and export N+1 query - admin.py: Remove manual team assignment loop that would fail since Server.team is now a read-only @Property. Add joinedload for email_team relationship instead. - export_service.py: Add selectinload for tools relationship in _export_selected_servers to prevent N+1 queries when accessing db_server.tools in a loop. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Add email_team relationship and team property to Gateway model, mirroring the optimization applied to Server model in PR IBM#1962 and IBM#1975. Changes: - Add email_team relationship to Gateway model (db.py) - Add team property for convenient access to team name - Update gateway_service.py to use joinedload(DbGateway.email_team) - Remove _get_team_name helper method (no longer needed) - Update tests to mock db.execute instead of db.get This reduces database queries by ~50% for gateway operations that need team information, eliminating the N+1 query pattern. Closes IBM#1994 Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Summary
convert_server_to_readto explicitly build server dict from attributes instead of using__dict__.copy(), which can return empty dict with SQLAlchemy'sscalars().all()eager loading patternjoinedload(DbServer.email_team)toget_serverandupdate_servermethods for consistent team name loadingoptionsparameter fordb.get()callsmock_serverfixture with explicitNonevalues for optional tracking fields to prevent MagicMock auto-creationCloses #1962