Skip to content

[DEV-15813] Spending explorer limit applied only after full result materialization - #4726

Open
james-at-kc wants to merge 8 commits into
qatfrom
fix/ops-3050-apply-early-limit
Open

[DEV-15813] Spending explorer limit applied only after full result materialization#4726
james-at-kc wants to merge 8 commits into
qatfrom
fix/ops-3050-apply-early-limit

Conversation

@james-at-kc

@james-at-kc james-at-kc commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description:

The /api/v2/spendingendpoint pulls more data from the DB than necessary when searching on a type of “award”. Django querysets won’t hit the DB until an action is taken on the queryset (e.g., converted to a list, retrieving a count, etc.). However, once that action is taken the query will pull all relevant data without a limit; unless a limit has been specified. The endpoint is currently putting more pressure on the DB than necessary by converting a queryset to a list prior to applying a limit via a slice: usaspending-api/usaspending_api/spending_explorer/v2/filters/type_filter.py at 055c58f48fb210568e1bf826356e75409faa9559 · fedspendingtransparency/usaspending-api

Technical Details:

The Explorer class supports a limit parameter which has a default value and is applied to the methods of the class

The default and maximum number of records that can be retrieved is currently defined in SPENDING_EXPLORER_LIMIT (see usaspending-api/usaspending_api/spending_explorer/v2/views/spending_explorer.py at 055c58f48fb210568e1bf826356e75409faa9559 · fedspendingtransparency/usaspending-api)

The filters/type_filter.py file continues to function as expected with the new limit parameter in place.

Rationale for changes:

The spending explorer's /api/v2/spending/ endpoint was asking the database for ALL matching award rows, loading them into the app, then keeping only the top 1000.

This change asks the database to consider at most 1000 rows (default limit) while keeping the same behavior for other explorer types (non award types).

The existing code only capped award types so this behavior was brought forward.

We moved the Spending Explorer award cap from "fetch everything then slice in Python" to "SQL limit on the explorer queryset".

We kept non-award and unreported behavior unchanged (per original Mythos finding).

We refactored type_filter.py to conform to ruff's complexity flag.

Requirements for PR Merge:

  1. Unit & integration tests updated
  2. N/A - API documentation updated (examples listed below)
    1. API Contracts
    2. API UI
    3. Comments
  3. N/A - Data validation completed (examples listed below)
    1. Does this work well with the current frontend? Or is the frontend aware of a needed change?
    2. Is performance impacted in the changes (e.g., API, pipeline, downloads, etc.)?
    3. Is the expected data returned with the expected format?
  4. N/A - Appropriate Operations ticket(s) created
  5. Jira Ticket(s)
    1. DEV-15813

Explain N/A in above checklist:

2 - there was no change to the API
3 - there was no change to the data returned so there was no impact on the frontend
4 - no ops involvement other than to deploy the merged branch

@james-at-kc james-at-kc changed the title [OPS-3050] apply early limit [DEV-15813] Spending explorer limit applied only after full result materialization Aug 10, 2026

@sethstoudenmier sethstoudenmier 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.

Since we have the limit hard coded on this endpoint do you mind creating a test that patches the limit to be lower (e.g, 2) and validate that the resulting endpoint call returns only 2 results? Other comments aren't worth blocking over, more just a suggestion and a question about performance.

exp = Explorer(alt_set, queryset)
if _type == "award":
# Cheap full-set total (single aggregate row) so response total matches prior behavior
actual_total = (

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.

Good catch! Not something that was picked up in refinement, but definitely some logic that needs to stay the same. Have you noticed any drop in performance with two queries? I would imagine not since it is now doing much less work overall.

) -> dict[str, Any]:
"""Process award, award_category, and recipient types.

Mythos - apply early limit - 8/2026

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.

Small and won't block over it, but I would remove the part about Mythos here since someone could find the ticket associated with the commit if they needed. The comment below tells me everything I need to know.

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