Skip to content

feat: UEX API rate limiting and advisory lock service#210

Merged
GitAddRemote merged 2 commits into
mainfrom
feature/ISSUE-189
May 23, 2026
Merged

feat: UEX API rate limiting and advisory lock service#210
GitAddRemote merged 2 commits into
mainfrom
feature/ISSUE-189

Conversation

@GitAddRemote
Copy link
Copy Markdown
Owner

Summary

  • UexApiClient — shared Axios wrapper for all ETL step implementations (ETL: sync factions, jurisdictions, and companies into station_* tables #190ETL: sync commodities with self-referencing parent hierarchy #199). Applies a configurable inter-request delay (UEX_REQUEST_DELAY_MS, default 500ms) before every call. After each response, inspects x-ratelimit-remaining; if ≤ 5, doubles delayMs for the next request. On HTTP 429, reads Retry-After header and retries with exponential backoff up to 3 times before throwing RateLimitException. 5xx responses throw UEXServerException. Registered as a factory provider in UexSyncModule and exported for injection by upcoming ETL steps.
  • AdvisoryLockService — reusable withLock(key, fn) using a QueryRunner to pin both pg_try_advisory_lock and pg_advisory_unlock to the same physical PostgreSQL connection. Pool-based DataSource.query() cannot guarantee same-session execution and risks leaving locks stranded on recycled connections. Lock is released unconditionally in finally; throws ConflictException on contention.
  • CatalogEtlService — replaced inline advisory lock logic and DataSource injection with AdvisoryLockService.withLock().
  • .env.example — documents UEX_REQUEST_DELAY_MS=500.

Test plan

  • pnpm typecheck passes
  • npx jest "uex-api.client|advisory-lock|catalog-etl" --no-coverage — 18 tests pass
  • POST /admin/catalog-etl/run succeeds; a second concurrent call returns HTTP 409
  • UEX sync runs without 429 errors under normal load

Notes

The legacy per-endpoint clients (UEXCategoriesClient, UEXItemsClient, etc.) are intentionally not migrated to UexApiClient — they predate this issue. New ETL step implementations (#190#199) will inject UexApiClient directly.

Closes #189

- Add UexApiClient: shared Axios wrapper for ETL step implementations
  (#190#199) with configurable inter-request delay (UEX_REQUEST_DELAY_MS,
  default 500ms), reactive backoff when x-ratelimit-remaining ≤ 5 (doubles
  delayMs), 429 retry with Retry-After header and exponential backoff up to
  3 attempts, 5xx → UEXServerException. Legacy per-endpoint clients
  (UEXCategoriesClient etc.) are intentionally not migrated.
- Add AdvisoryLockService: reusable withLock(key, fn) using QueryRunner to
  pin both pg_try_advisory_lock and pg_advisory_unlock to the same physical
  connection — pool-based DataSource.query() cannot guarantee same-session
  execution and risks stranding locks on recycled connections.
- Wire AdvisoryLockService into CatalogEtlService; remove inline lock logic
  and DataSource injection from that service.
- Register UexApiClient as a factory provider in UexSyncModule; export it
  for injection by upcoming ETL step implementations.
- Document UEX_REQUEST_DELAY_MS in .env.example.
- 18 unit tests: UexApiClient (7), AdvisoryLockService (3), CatalogEtlService (8)
Copilot AI review requested due to automatic review settings May 23, 2026 06:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…ient

UEX returns HTTP 200 with body { status: 'error', message: 'requests_limit_reached' }
as a soft throttle signal. UexApiClient was only handling HTTP 429, so ETL
steps would receive null data and continue silently instead of backing off.

Now throws RateLimitException on that response shape, matching the behaviour
of the legacy per-endpoint clients. Added a unit test for this case.
@GitAddRemote GitAddRemote merged commit 59be2c0 into main May 23, 2026
12 of 13 checks passed
@GitAddRemote GitAddRemote deleted the feature/ISSUE-189 branch May 23, 2026 06:51
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.

UEX API rate limiting and concurrent sync prevention

2 participants