Skip to content

perf: avoid duplicate IA loan API call in get_loan#13130

Draft
lokesh wants to merge 1 commit into
masterfrom
claude/perf/dedupe-ia-loan-calls
Draft

perf: avoid duplicate IA loan API call in get_loan#13130
lokesh wants to merge 1 commit into
masterfrom
claude/perf/dedupe-ia-loan-calls

Conversation

@lokesh

@lokesh lokesh commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #

Performance fix (with a correctness fix included): removes a redundant blocking HTTP POST to the archive.org loan API from every edition-page loan check.

Estimated performance gain

~30–150 ms off server render time (p50–p90) for every anonymous edition-page loan check, and worst-case stalls halved from 2× to 1× the API timeout. The loan check is a synchronous cross-service POST on the render path; this PR halves the number of calls, so the saving is one full loan-API round-trip. Estimates assume typical same-datacenter latency to the IA loan service; measure with the existing stats timing on get_loan callers to confirm.

xychart-beta
    title "Blocking loan-API calls per anonymous loan check"
    x-axis ["Before", "After"]
    y-axis "sequential HTTP POSTs" 0 --> 2
    bar [2, 1]
Loading

For user-scoped lookups the second call still happens, but only when it can matter (linked @itemname and the first lookup found nothing) — previously it ran unconditionally.

Technical

Problem. get_loan() in openlibrary/core/lending.py made two sequential, unconditional calls to _get_ia_loan(), each a blocking requests.post to the IA loan API, and the second call unconditionally overwrote the first's result:

try:
    _loan = _get_ia_loan(identifier, account and userkey2userid(account.username))
except Exception: ...
try:
    _loan = _get_ia_loan(identifier, account and account.itemname)  # clobbers!
except Exception: ...

This had three concrete consequences:

  1. Duplicate identical call for anonymous lookups. When user_key is None (the common case — get_edition_loans() in plugins/upstream/borrow.py calls get_loan(ocaid) with no user on edition-page renders, and is_loaned_out_on_ol() does the same), account is None, so both calls were byte-for-byte identical POSTs. Every loan-status check paid for two round-trips to the loan API where one suffices.
  2. A found loan could be clobbered. If the first (ol:username) lookup found a loan and the second (itemname) lookup returned None, the function returned None — losing a real loan.
  3. Unfiltered fallback for accounts without a linked IA item. When account.itemname is None, the second call was made with userid=None, i.e. an unfiltered query that returns any loan on the identifier — so a user-scoped get_loan(identifier, user_key) could return another patron's loan.

Fix. The second lookup now runs only when it can produce new, correctly-scoped information: if account and account.itemname and not _loan. Anonymous lookups make exactly one API call; user-scoped lookups fall back to the @itemname query only when the ol:username query found nothing.

Out of scope (pre-existing, noted for reviewers): the store-loan block just above (d = site.get().store.get("loan-" + identifier)) constructs a Loan(d) that is never returned when unexpired; that dead code predates this PR and is untouched.

Testing

New TestGetLoan in openlibrary/tests/core/test_lending.py (mocks lending.site and lending._get_ia_loan):

  • anonymous lookup → exactly one API call, result propagated
  • account lookup that finds a loan under ol:username → no second call, loan not clobbered
  • account lookup that finds nothing → falls back to exactly one @itemname call
  • account with no linked itemname → exactly one call

Reproduce: make test-py-uv PYTEST_ARGS="openlibrary/tests/core/test_lending.py" — 8 passed. pre-commit run --files openlibrary/core/lending.py openlibrary/tests/core/test_lending.py — all hooks pass (incl. mypy, ruff).

Screenshot

N/A — no UI change.

Stakeholders

Part of a performance-review series; see the review report branch claude/performance-review-top-changes-5wcs7g.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ky5Sc19MGPkDgbgTWWWET

get_loan() unconditionally made a second _get_ia_loan() call and
overwrote the first result. This caused:

- two identical blocking POSTs to the IA loan API for anonymous
  lookups (every edition-page loan check via get_edition_loans)
- a loan found under ol:<username> being clobbered by None when the
  itemname lookup found nothing
- an unfiltered (userid-less) lookup for accounts with no linked
  itemname, which could return another user's loan

Only make the second call when the account has a linked itemname and
the first lookup found nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ky5Sc19MGPkDgbgTWWWET
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