Class: Edi::Amazon::FeedSubmissionBudget

Inherits:
Object
  • Object
show all
Defined in:
app/services/edi/amazon/feed_submission_budget.rb

Overview

Paces SP-API feed submissions per SELLER ACCOUNT.

SP-API meters createFeed per account, not per marketplace, and our twelve
marketplaces share exactly two: :amazon_sc_seller_api (US/CA/MX) and
:amazon_sc_seller_eu_api (the nine EU marketplaces). A flow that walks
partners in order therefore spends one account's whole allowance on the first
few marketplaces and 429s the rest — which is precisely what happened from
2026-06-16: fr/be/de/es/it submitted every run and nl/pl/se never did.

This is the guard the schedule's wall-clock offsets (:00 inventory, :30
price, :45 listing in config/sidekiq_production_schedule.yml) were standing
in for. Those space the FLOWS apart but do nothing about nine marketplaces
inside one flow.

Not a substitute for sending less: the dominant fix is
Edi::Amazon::FeedMessageSender#supersede_stale, which stops the sender
replaying a backlog of stale full snapshots. After it, EU submits ~18 feeds
an hour (9 inventory + 9 price) where it previously attempted ~279.

THIS IS A BURST CEILING, NOT QUOTA ENFORCEMENT — and it deliberately sits
ABOVE the documented quota (config/sidekiq_production_schedule.yml records
JSON_LISTINGS_FEED as 1 submission per account per 5 minutes; in practice we
observed five clear in ~50s before the sixth 429'd). Sizing LIMIT down to
the quota would REBUILD the bug this class exists to fix: nine EU partners
run inside one serial flow, so a limit below nine deterministically defers
the same trailing partners every single run — exactly the nl/pl/se
starvation, just enforced by us instead of Amazon. Keep LIMIT at or above
the largest account's partner count.

Amazon's own 429 remains the real quota enforcer, and now records itself
(see Edi::Amazon::FeedMessageSender#defer). What this stops is the
pathological burst — a backlog replay firing dozens of submissions in
seconds. Making the flow respect the quota properly means spreading partners
across the hour rather than walking them in one pass; that is the fan-out
work in doc/troubleshooting/MENARD_PLAYWRIGHT_OUTAGE_2026_07.md, not a
smarter limiter.

ponytail: fixed window, not a sliding bucket. A boundary crossing can pass
LIMIT in a short interval — acceptable precisely because this is a burst
ceiling above the quota, so the marginal submissions were already Amazon's
call to refuse. A sliding window costs a Redis sorted set and expiry
bookkeeping to tighten a bound that is deliberately loose.

Constant Summary collapse

PERIOD =

Window length, and the most an account may submit inside it. LIMIT must
stay >= the partner count of the largest account (EU: 9) — see above.

5.minutes
LIMIT =
10

Class Method Summary collapse

Class Method Details

.claim!(account_key) ⇒ Boolean

Claims one submission slot for the account.

Parameters:

  • account_key (Symbol, String)

    the orchestrator's transporter_profile

Returns:

  • (Boolean)

    false when the account has spent this window's budget



57
58
59
60
61
62
63
64
# File 'app/services/edi/amazon/feed_submission_budget.rb', line 57

def claim!()
  spent = Rails.cache.increment(window_key(), 1, expires_in: PERIOD * 2)
  # A cache that can't count must not block feeds — the 429 handler is
  # still there to catch what this would have prevented.
  return true if spent.nil?

  spent <= LIMIT
end