Skip to content

fix: resolve slow DB query in org permission check (#8228) - #8230

Open
srijantrpth wants to merge 18 commits into
Flagsmith:mainfrom
srijantrpth:fix/8228-slow-org-permissions
Open

fix: resolve slow DB query in org permission check (#8228)#8230
srijantrpth wants to merge 18 commits into
Flagsmith:mainfrom
srijantrpth:fix/8228-slow-org-permissions

Conversation

@srijantrpth

Copy link
Copy Markdown
Contributor

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #8228

  • Rewrote user_has_organisation_permission to evaluate user, group, and role permissions using sequential EXISTS checks.
  • This prevents Django from building a massive LEFT OUTER JOIN cross-product of the entire RBAC graph, eliminating the ~2.6s loading delay on the /groups/ endpoint.

How did you test this code?

  • Ran the existing organisations and users test suites to guarantee functional parity (the rewritten ORM returns the exact same boolean logic as the old version).
  • Added test_user_has_organisation_permission_query_count in tests/unit/users/test_unit_users_models.py wrapped in django_assert_max_num_queries(4) to prevent future query-count regressions.

- Rewrote `user_has_organisation_permission` to evaluate user, group, and role permissions using sequential `EXISTS` checks.
- This prevents Django from building a massive `LEFT OUTER JOIN` cross-product of the entire RBAC graph, eliminating the ~2.6s loading delay on the `/groups/` endpoint.
- Added `test_user_has_organisation_permission_query_count` to prevent future regressions.
@srijantrpth
srijantrpth requested a review from a team as a code owner August 6, 2026 06:18
@srijantrpth
srijantrpth requested review from emyller and a lite review from Copilot and removed request for a team August 6, 2026 06:18
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

@srijantrpth is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

user_has_organisation_permission now checks organisation membership before direct, group, and optional RBAC role permissions. Each permission source uses a separate early-return query. A database test verifies that a member without explicit grants returns False within four queries.

Estimated code review effort: 2 (Simple) | ~10 minutes

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the api Issue related to the REST API label Aug 6, 2026
@srijantrpth

srijantrpth commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

To follow up on the Sentry report, this fix drastically reduces the database load for the /groups/ endpoint.

Before:
The permission check built a full LEFT OUTER JOIN cross-product of the entire RBAC graph (UserOrganisationPermission, UserPermissionGroup, Role, etc.) in a single massive query, causing the ~2.6s delay.

After:
The logic is now split into sequential EXISTS subqueries with early returns.

Best case scenario: If a user has direct permissions, the DB stops looking immediately (only 2 fast queries).

Worst case scenario: The DB executes a maximum of 4 isolated, indexed queries instead of one massive join. (This is now strictly enforced in the test suite via django_assert_max_num_queries(4)).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 26aeea1b-9216-4d14-a694-c8e66ce01c8f

📥 Commits

Reviewing files that changed from the base of the PR and between 377f203 and 5c5c4c0.

📒 Files selected for processing (2)
  • api/permissions/permission_service.py
  • api/tests/unit/users/test_unit_users_models.py

Comment thread api/tests/unit/users/test_unit_users_models.py Outdated

Copilot AI 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.

Pull request overview

This PR addresses the performance regression in organisation-level permission checks by replacing the single, join-heavy ORM query with a sequence of short-circuiting EXISTS checks, reducing the risk of RBAC join cross-products that were slowing /organisations/{id}/groups/.

Changes:

  • Reworked user_has_organisation_permission to perform sequential EXISTS checks for direct user, group, and (optionally) role-based permissions.
  • Added a unit test to cap the number of queries executed by the organisation permission check to help prevent query-count regressions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
api/permissions/permission_service.py Rewrites organisation permission checking logic to avoid a single large join and short-circuit via multiple EXISTS queries.
api/tests/unit/users/test_unit_users_models.py Adds a query-count regression test for user_has_organisation_permission.
Suppressed comments (2)

api/tests/unit/users/test_unit_users_models.py:282

  • There is trailing whitespace in the argument list, and the permission key should use the MANAGE_USER_GROUPS constant (to avoid typos and keep it aligned with the rest of the codebase).
    # (1 base check + up to 3 for user/group/role filters)
    # If a join explosion is reintroduced, this will fail because the query
    # complexity and count will change drastically.
    with django_assert_max_num_queries(4):
        has_permission = user_has_organisation_permission(

api/tests/unit/users/test_unit_users_models.py:8

  • The imports include two separate from users.models ... statements and the new test hard-codes the permission key string. This is likely to fail import-sorting checks and makes the test less robust than using the existing MANAGE_USER_GROUPS constant.

This issue also appears on line 278 of the same file.

from common.projects.permissions import VIEW_PROJECT
from django.db.utils import IntegrityError

from organisations.models import Organisation, OrganisationRole
from organisations.permissions.models import UserOrganisationPermission

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/tests/unit/users/test_unit_users_models.py Outdated
@matthewelwell

Copy link
Copy Markdown
Contributor

Hi @srijantrpth , thanks for the contribution. Could you take a look at the CI failures please? It looks like there are some new typing errors added here? You can ignore the 'API Documentation Artefacts' check - it looks like there's something we need to resolve there - and the Vercel Previews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 01df65b8-13dc-4a6d-8a87-e939cc873083

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5c4c0 and 6b72617.

📒 Files selected for processing (2)
  • api/permissions/permission_service.py
  • api/tests/unit/users/test_unit_users_models.py

Comment thread api/permissions/permission_service.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/permissions/permission_service.py (1)

220-226: ⚠️ Potential issue | 🟠 Major

Make the model-class typing exception explicit to Mypy.

Line 224 passes the Organisation model class, but the inline note states that get_role_permission_filter expects an instance. The note does not change the static type. If the helper annotation is still instance-only, Mypy will report an argument-type error.

Update the helper annotation to accept model classes, or add a precise # type: ignore[arg-type] with the runtime reason. Do not use an unqualified suppression.

Based on learnings, API typing workarounds should use a precise, documented, removable suppression or an accurate typing.cast(...).

#!/bin/bash
set -euo pipefail

rg -n -A16 -B3 'def get_role_permission_filter\(' api/permissions/permission_service.py
rg -n -C4 'get_role_permission_filter\(' api/permissions api/tests

if command -v mypy >/dev/null 2>&1; then
  mypy --show-error-codes api/permissions/permission_service.py
else
  echo "mypy is not installed"
fi

Source: Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5f8b0595-8df6-4a72-9fd7-46ff2d911a39

📥 Commits

Reviewing files that changed from the base of the PR and between fb0b063 and d129cda.

📒 Files selected for processing (2)
  • api/permissions/permission_service.py
  • api/tests/unit/users/test_unit_users_models.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
api/tests/unit/users/test_unit_users_models.py (1)

278-282: 🚀 Performance & Scalability | 🟡 Minor

Assert the query shape as well as the query count.

django_assert_max_num_queries(4) does not detect a regression to the RBAC LEFT OUTER JOIN cross-product. That regression could still execute as one query and remain below the limit. Replace this with exact per-configuration query assertions and add a SQL-shape or query-plan check that rejects the expensive join pattern. This is the same unresolved gap identified in the previous review.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 65c17559-e9e2-4cd5-83a3-1b5f77ee42e5

📥 Commits

Reviewing files that changed from the base of the PR and between d129cda and 9747b40.

📒 Files selected for processing (1)
  • api/tests/unit/users/test_unit_users_models.py

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.57%. Comparing base (accbd62) to head (26c8637).
⚠️ Report is 83 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8230      +/-   ##
==========================================
- Coverage   98.65%   98.57%   -0.09%     
==========================================
  Files        1514     1543      +29     
  Lines       60110    61699    +1589     
==========================================
+ Hits        59304    60819    +1515     
- Misses        806      880      +74     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@srijantrpth

Copy link
Copy Markdown
Contributor Author

Hi @matthewelwell , all fixed! I've resolved the mypy typing errors and the test linting issue. CI should be green now (ignoring the docs/Vercel checks as requested). Let me know if it looks good to go!

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

Labels

api Issue related to the REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slow DB Query: org permission check takes ~2.6s on GET /organisations/{id}/groups/

3 participants