Skip to content

perf: increase job concurrency, add channel routing and identity keys#151

Open
kneckinator wants to merge 4 commits into19.0from
worktree-perf+phase9-concurrency
Open

perf: increase job concurrency, add channel routing and identity keys#151
kneckinator wants to merge 4 commits into19.0from
worktree-perf+phase9-concurrency

Conversation

@kneckinator
Copy link
Copy Markdown
Contributor

Summary

  • Increase parallel-safe channel limits from 1 to 4 for cycle, eligibility_manager, and program_manager — now safe with INSERT ON CONFLICT
  • Add entitlement_approval channel (limit=1) for fund balance tracking that must remain serial
  • Add statistics_refresh channel (limit=1) to avoid concurrent refresh storms
  • Route all entitlement approval/validation jobs to the serial entitlement_approval channel
  • Route all completion handlers (mark_*_as_done) to the serial statistics_refresh channel
  • Add identity_key to all async dispatch methods to prevent duplicate job submission on double-click

Note: This PR is based on Phase 8 (#150) which is based on Phase 7 (#149). Will need rebasing after those merge.

Changes

  • queue_data.xml: Increase limits to 4 for parallel channels, add 2 new serial channels
  • entitlement_manager_base.py: Route _set_pending_validation, _validate, _cancel to entitlement_approval
  • entitlement_manager_cash.py: Route _validate_entitlements to entitlement_approval
  • entitlement_manager_inkind.py: Route _set_pending_validation, _validate to entitlement_approval
  • program_manager.py: Add identity_key, route completion to statistics_refresh
  • cycle_manager_base.py: Add identity_key, route completions to statistics_refresh
  • eligibility_manager.py: Add identity_key, route completion to statistics_refresh

Test plan

  • ./scripts/test_single_module.sh spp_programs — 600 tests, 0 failures
  • pre-commit run --files <changed_files> — all checks pass
  • For async operations: test via UI to verify jobs are dispatched to correct channels

Replace per-record ORM creates and Command.create() tuples with raw SQL
INSERT ... ON CONFLICT (unique_cols) DO NOTHING for bulk membership
creation. Duplicates are silently skipped and the inserted count is
returned via cursor.rowcount.

Updates _import_registrants and _add_beneficiaries to use the new
skip_duplicates path, with ORM cache invalidation after raw SQL inserts.
Set enrollment_date to current timestamp when state is 'enrolled' in
the program membership SQL insert (computed field not triggered by raw
SQL). Hoist fields.Date.today() outside the loop in cycle membership
to avoid repeated calls per record.
Add context flags (skip_registrant_statistics, skip_program_statistics)
that allow bulk operation callers to suppress expensive computed field
recomputation. Add refresh_beneficiary_counts() on spp.program and
refresh_statistics() on spp.cycle to recompute once at completion.

Also replace bool(rec.program_membership_ids) with SQL EXISTS in
_compute_has_members to avoid loading the full membership recordset.
Increase parallel-safe channel limits from 1 to 4 (cycle,
eligibility_manager, program_manager) now that INSERT ON CONFLICT
makes these operations safe for concurrent execution.

Add two serial channels:
- entitlement_approval (limit=1): fund balance tracking must be serial
- statistics_refresh (limit=1): avoid concurrent refresh storms

Route entitlement approval/validation jobs to entitlement_approval
channel. Route all completion handlers (mark_*_as_done) to
statistics_refresh channel.

Add identity_key to all async dispatch methods to prevent duplicate
job submission when users double-click action buttons.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements bulk membership creation for programs and cycles using raw SQL to improve performance and handle duplicates. It introduces context flags to skip expensive statistics recomputations during bulk operations, adds explicit refresh methods, refactors asynchronous job handling with specific channels and identity keys, and updates queue configurations. Feedback focuses on ensuring UTC timestamps in raw SQL queries and correcting a missing return value to align with method documentation.

values = []
params = []
for v in batch:
values.append("(%s, %s, %s, %s, %s, %s, now(), now())")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When using raw SQL in Odoo, it is recommended to explicitly use (now() at time zone 'utc') for create_date and write_date. This ensures that the timestamps are stored in UTC regardless of the database session's timezone configuration, adhering to Odoo's standard data storage practices.

Suggested change
values.append("(%s, %s, %s, %s, %s, %s, now(), now())")
values.append("(%s, %s, %s, %s, %s, %s, now() at time zone 'utc', now() at time zone 'utc')")

for v in batch:
state = v.get("state", "draft")
enrollment_date = now if state == "enrolled" else None
values.append("(%s, %s, %s, %s, %s, %s, now(), now())")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When using raw SQL in Odoo, it is recommended to explicitly use (now() at time zone 'utc') for create_date and write_date. This ensures that the timestamps are stored in UTC regardless of the database session's timezone configuration, adhering to Odoo's standard data storage practices.

Suggested change
values.append("(%s, %s, %s, %s, %s, %s, now(), now())")
values.append("(%s, %s, %s, %s, %s, %s, now() at time zone 'utc', now() at time zone 'utc')")

}
for partner_id in beneficiaries
]
self.env["spp.cycle.membership"].bulk_create_memberships(vals_list, skip_duplicates=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The method _add_beneficiaries is missing a return statement. According to its docstring, it should return an integer representing the count of inserted members. Returning the result of bulk_create_memberships will satisfy this requirement.

Suggested change
self.env["spp.cycle.membership"].bulk_create_memberships(vals_list, skip_duplicates=True)
return self.env["spp.cycle.membership"].bulk_create_memberships(vals_list, skip_duplicates=True)

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.12%. Comparing base (f738582) to head (38bc517).

Files with missing lines Patch % Lines
spp_programs/models/managers/cycle_manager_base.py 50.00% 4 Missing ⚠️
spp_programs/models/programs.py 76.92% 3 Missing ⚠️
...pp_programs/models/managers/eligibility_manager.py 71.42% 2 Missing ⚠️
...ograms/models/managers/entitlement_manager_base.py 0.00% 2 Missing ⚠️
...ograms/models/managers/entitlement_manager_cash.py 0.00% 1 Missing ⚠️
...rams/models/managers/entitlement_manager_inkind.py 0.00% 1 Missing ⚠️
spp_programs/models/managers/program_manager.py 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #151      +/-   ##
==========================================
+ Coverage   71.06%   71.12%   +0.05%     
==========================================
  Files         925      925              
  Lines       54704    54797      +93     
==========================================
+ Hits        38876    38973      +97     
+ Misses      15828    15824       -4     
Flag Coverage Δ
spp_api_v2 80.10% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_audit 64.44% <ø> (+0.60%) ⬆️
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_cel_event 85.11% <ø> (ø)
spp_claim_169 58.11% <ø> (ø)
spp_dci_client_dr 55.87% <ø> (ø)
spp_dci_client_ibr 60.17% <ø> (ø)
spp_programs 62.72% <85.71%> (+0.49%) ⬆️
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_programs/__manifest__.py 0.00% <ø> (ø)
spp_programs/models/cycle.py 64.92% <100.00%> (+0.24%) ⬆️
spp_programs/models/cycle_membership.py 75.00% <100.00%> (+14.13%) ⬆️
spp_programs/models/program_membership.py 54.23% <100.00%> (+12.67%) ⬆️
spp_programs/models/registrant.py 83.87% <100.00%> (+1.51%) ⬆️
...ograms/models/managers/entitlement_manager_cash.py 58.79% <0.00%> (ø)
...rams/models/managers/entitlement_manager_inkind.py 55.42% <0.00%> (ø)
spp_programs/models/managers/program_manager.py 78.33% <0.00%> (ø)
...pp_programs/models/managers/eligibility_manager.py 75.60% <71.42%> (+0.40%) ⬆️
...ograms/models/managers/entitlement_manager_base.py 64.25% <0.00%> (ø)
... and 2 more

... and 1 file with indirect coverage changes

🚀 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.

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.

1 participant