Skip to content

perf(api): improve performance on states and modules endpoints#9275

Open
p11o wants to merge 3 commits into
makeplane:previewfrom
p11o:preview
Open

perf(api): improve performance on states and modules endpoints#9275
p11o wants to merge 3 commits into
makeplane:previewfrom
p11o:preview

Conversation

@p11o

@p11o p11o commented Jun 19, 2026

Copy link
Copy Markdown

Description

These perf updates amount to a roughly 15s latency reduction as the api calls are sequential:

  Wave 1 (config):  ████████ 3s
  Wave 2 (issues):          ████████ 3s
  Wave 3 (details):                 ████████ 3s
  Wave 4 (...):                             ████████ 3s
  Wave 5 (...):                                     ████████ 3s
                                                             = ~15s

/api/projects/{id}/states/

The get_queryset filtered membership via a JOIN through project__project_projectmember, which multiplied rows and required .distinct() to deduplicate. Replaced with an Exists() correlated subquery on ProjectMember — same semantics, no row inflation.

/api/projects/{id}/modules/

Five of the six issue-count annotations used Count("issue_module__issue__state__group", distinct=True), traversing a 3-table join path per annotation. Replaced with Coalesce(Subquery(...annotate(count=Count("id")))) — the same pattern used in #8889 (2.6s -> 0.07s improvement on sub-issues).

Also extracted the three identical get_queryset bodies from ModuleListCreateAPIEndpoint, ModuleDetailAPIEndpoint, and ModuleArchiveUnarchiveAPIEndpoint into shared helpers.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Test Scenarios

Ran the full Docker test suite (docker compose -f docker-compose-test.yml up --build --abort-on-container-exit): 345 passed, 8 pre-existing failures in TestMagicSign*

References

#8889 — prior art for the Coalesce(Subquery) pattern in this codebase

Summary by CodeRabbit

  • Refactor
    • Improved module API query construction for better performance and code maintainability
    • Enhanced state API query filtering for more accurate project membership validation

p11o added 2 commits June 19, 2026 16:26
…yset

Filtering via project__project_projectmember caused a JOIN that
multiplied rows, requiring a DISTINCT to deduplicate. Replace with
an Exists() correlated subquery on ProjectMember, which avoids the
row inflation entirely and makes DISTINCT unnecessary.
…dule queryset

The five state-group counts used Count("issue_module__issue__state__group",
distinct=True) which traverses a 3-table join path per annotation and forces
the DB to deduplicate the inflated result set. Replace with Coalesce(Subquery)
per the pattern already established in sub_issue.py (PR makeplane#8889).

Also extract the duplicated get_queryset body from ModuleListCreateAPIEndpoint,
ModuleDetailAPIEndpoint, and ModuleArchiveUnarchiveAPIEndpoint into shared
helper functions to eliminate the copy-paste.
@CLAassistant

CLAassistant commented Jun 19, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors two API view files. In module.py, two shared helpers (_module_issue_count_subquery and _build_module_queryset) are extracted to centralize issue-count annotation and queryset construction, and three endpoint get_queryset methods delegate to them. In state.py, both State endpoint get_queryset methods replace join-based ProjectMember filters with Exists/OuterRef subqueries, removing distinct().

Changes

Module queryset centralization

Layer / File(s) Summary
Issue-count subquery and base queryset helpers
apps/api/plane/api/views/module.py
Expands ORM imports for IntegerField, OuterRef, Func, Subquery, and Coalesce, and introduces _module_issue_count_subquery(state_group=None) and _build_module_queryset(slug, project_id, order_by="-created_at") with select_related, prefetch_related, and six annotated issue-count fields.
Endpoint get_queryset delegation
apps/api/plane/api/views/module.py
ModuleListCreateAPIEndpoint, ModuleDetailAPIEndpoint, and ModuleArchiveUnarchiveAPIEndpoint each replace inline queryset construction with a call to _build_module_queryset; the archive endpoint appends archived_at__isnull=False.

State endpoint membership subquery

Layer / File(s) Summary
ProjectMember Exists subquery in State endpoints
apps/api/plane/api/views/state.py
Adds Exists, OuterRef (ORM), and ProjectMember (model) imports; rewrites membership filtering in both StateListCreateAPIEndpoint.get_queryset() and StateDetailAPIEndpoint.get_queryset() to use filter(Exists(member_check)) with OuterRef("project_id"), replacing the previous join-based filter and distinct().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hop, hop, helpers now share the load,
No more copy-paste down the module road.
A subquery leaps where join once sprawled,
distinct() retired, no longer called.
Clean queries bloom like clover in spring—
This rabbit approves of refactoring! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is comprehensive and well-structured, matching most requirements of the template.
Title check ✅ Passed The title 'perf(api): improve performance on states and modules endpoints' directly and accurately summarizes the main changes, which are performance optimizations to the states and modules API endpoints through query refactoring.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
apps/api/plane/api/views/state.py (1)

172-186: 💤 Low value

Consider extracting a shared helper to reduce duplication.

The get_queryset implementations in StateListCreateAPIEndpoint (lines 49-61) and StateDetailAPIEndpoint (lines 173-185) are identical. Following the same pattern applied in module.py where _build_module_queryset was extracted, a shared helper could consolidate this logic.

♻️ Optional helper extraction
def _build_state_queryset(slug, project_id, user):
    """Build the base State queryset with membership check."""
    member_check = ProjectMember.objects.filter(
        project_id=OuterRef("project_id"),
        member=user,
        is_active=True,
        deleted_at__isnull=True,
    )
    return (
        State.objects.filter(workspace__slug=slug)
        .filter(project_id=project_id)
        .filter(Exists(member_check))
        .filter(is_triage=False)
        .filter(project__archived_at__isnull=True)
        .select_related("project", "workspace")
    )

Then both endpoints can use:

def get_queryset(self):
    return _build_state_queryset(
        self.kwargs.get("slug"),
        self.kwargs.get("project_id"),
        self.request.user,
    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/plane/api/views/state.py` around lines 172 - 186, Extract the
duplicated queryset building logic from both StateListCreateAPIEndpoint and
StateDetailAPIEndpoint get_queryset methods into a shared helper function called
_build_state_queryset. This helper should accept slug, project_id, and user as
parameters and contain the ProjectMember membership check and all the filtering
logic that builds the State queryset. Then update both get_queryset methods in
StateListCreateAPIEndpoint and StateDetailAPIEndpoint to call this new helper
function instead of duplicating the entire queryset construction logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/plane/api/views/state.py`:
- Around line 172-186: Extract the duplicated queryset building logic from both
StateListCreateAPIEndpoint and StateDetailAPIEndpoint get_queryset methods into
a shared helper function called _build_state_queryset. This helper should accept
slug, project_id, and user as parameters and contain the ProjectMember
membership check and all the filtering logic that builds the State queryset.
Then update both get_queryset methods in StateListCreateAPIEndpoint and
StateDetailAPIEndpoint to call this new helper function instead of duplicating
the entire queryset construction logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 191d05e1-ca58-45fa-a3f1-de10936682ea

📥 Commits

Reviewing files that changed from the base of the PR and between 53a323d and 21ec42d.

📒 Files selected for processing (2)
  • apps/api/plane/api/views/module.py
  • apps/api/plane/api/views/state.py

CI requires 80% docstring coverage. Added one-line docstrings to all
functions introduced or modified by the perf fixes: _build_module_queryset,
and the get_queryset methods on ModuleListCreateAPIEndpoint,
ModuleDetailAPIEndpoint, ModuleArchiveUnarchiveAPIEndpoint, and both
State endpoint classes.
@p11o p11o changed the title perf(api): fix slow project config endpoints — states and modules perf(api): improve performance on states and modules endpoints Jun 19, 2026
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