Skip to content

perf(exchange): replace peerFamilyGroups allowlist + per-rec offering API with cached Cost Explorer recommendations #40

@cristim

Description

@cristim

Summary

The RI Exchange / reshape pipeline enumerates cross-family alternatives via a hand-curated peerFamilyGroups allowlist plus a per-recommendation DescribeReservedInstancesOfferings API call. CUDly already caches AWS Cost Explorer purchase recommendations in Postgres — the reshape page should pair underutilised convertible RIs against that cache instead.

Captured in: known_issues/24_exchange_offering_cache.md

Current behaviour

In pkg/exchange/reshape.go:

  1. ListConvertibleReservedInstances surfaces underutilised convertible RIs.
  2. peerFamilyGroups (hardcoded map) picks candidate target families.
  3. FindConvertibleOfferings (providers/aws/services/ec2/client.go) calls DescribeReservedInstancesOfferings per recommendation to enrich pricing.
  4. passesDollarUnitsCheck filters the surviving candidates.

Consequences today:

  • New EC2 families are invisible until someone updates peerFamilyGroups by hand.
  • Every reshape page load fans out N×M offering-enumeration API calls.
  • Specialty/legacy alternatives exist only because of hand-curated group lists.

Expected behaviour

The recommendations table already stores Cost Explorer's GetReservationPurchaseRecommendation output (providers/aws/recommendations/client.go). Pair underutilised convertibles against those cached recommendations — AWS's own advice is strictly more relevant than enumerating every offering in the family, and the data is already in Postgres.

Steps to reproduce

  1. Launch a reshape page load while tailing backend logs.
  2. Observe one DescribeReservedInstancesOfferings call per convertible RI, gated by the static peerFamilyGroups allowlist.
  3. Confirm the recommendations table already has cached Cost Explorer suggestions (SELECT provider,service,region,resource_type FROM recommendations WHERE provider='aws' AND service='ec2').

Proposed fix

  • Delete peerFamilyGroups, candidateFamilies, and alternativesForTarget from pkg/exchange/reshape.go.
  • Replace the OfferingLookup signature with a recommendation-driven PurchaseRecLookup(ctx, region, currencyCode) ([]OfferingOption, error) that reads store.ListStoredRecommendations(...) and maps each row to an OfferingOption (effective monthly = UpfrontCost/termMonths + MonthlyCost).
  • Rename AnalyzeReshapingWithOfferingsAnalyzeReshapingWithRecs; the existing passesDollarUnitsCheck gate is unchanged.
  • Update internal/api/handler_ri_exchange.go::convertToExchangeTypes and internal/server/handler_ri_exchange.go::convertForAutoExchange to thread the new adapter.
  • Delete providers/aws/services/ec2/client.go::FindConvertibleOfferings if no other callers (pre-flight grep).

No new schema, no new store API, no scheduler hook needed — the recommendations table is already populated on every scheduler tick.

Edge cases

  • Cache empty / cold start: no alternatives — same UX as a region with no AWS-recommended buys.
  • No Cost Explorer recs in region: correct zero-alternatives display; AWS hasn't recommended anything.
  • Term or currency mismatch: passesDollarUnitsCheck rejects the pair; filter by currency at SQL layer too.
  • Cross-account leak: RecommendationFilter must include source account ID — reshape is per-account.
  • auto.go path: uses base AnalyzeReshaping (no lookup) → AlternativeTargets=nil. Verify with grep -rn "AlternativeTargets" internal/ pkg/.

Test plan

Unit tests in pkg/exchange/:

  • TestAnalyzeReshapingWithRecs_RecommendationDrivenAlternatives — fake lookup spanning m5/c5/r5, asserts cross-family alternatives surface.
  • TestAnalyzeReshapingWithRecs_EmptyLookupReturnsNoAlternatives — cold-start UX.
  • TestAnalyzeReshapingWithRecs_AppliesDollarUnitsFilter — filter gate works.
  • Migrate existing TestAnalyzeReshapingWithOfferings_* to the new signature.
  • Delete TestCandidateFamilies_*.

Handler tests:

  • TestPurchaseRecLookupFromStore_RegionFilter / AccountFilter / NoRecsReturnsEmpty.

References

  • Commit cd440d9eafeat(exchange): cross-family RI alternatives for specialty + legacy (the hand-curated fix this replaces).
  • pkg/exchange/reshape.go, providers/aws/services/ec2/client.go::FindConvertibleOfferings.
  • internal/config/store_postgres_recommendations.go::ListStoredRecommendations.
  • Known-issue doc: known_issues/24_exchange_offering_cache.md.

Effort

Small — single refactor commit, no schema changes, no new failure surfaces. ~0.5–1 day including test updates.

Verification

go build ./...
go test -short -count=1 -race ./pkg/exchange/... ./internal/api/... ./internal/server/...

Post-deploy: reshape page surfaces alternatives for instance types launched after the last hand-curated allowlist update; zero DescribeReservedInstancesOfferings calls fire during a reshape load.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions