BookingCart::resolvePointer() reads the private tempstore every time it is called, and it is the way in to both accessors: current() and activeOrder() call it directly, and heldLines() calls current(). A surface that asks the cart more than one question therefore reads the same key/value row once per question.

PlaceSelectionController::act() asks several: current() to find the order, heldLines() to check the visitor's existing holds, current() again inside the branch that holds the place, and heldLines() once more to build the response. One place click on the graphical map reads tempstore.private.yoyaku_booking_cart four times, for a value that cannot change unless this same class writes it.

Measured

A performance test on the place map, following core's Gander pattern, puts a recurring click (one landing in a basket that already exists) at 25 queries. Four of them are that identical tempstore read:

  • 4 x SELECT name, value FROM key_value_expire ... collection = tempstore.private.yoyaku_booking_cart
  • 2 x SELECT ... FROM yoyaku_booking WHERE transaction = N, with no state filter
  • 2 x SELECT ... FROM yoyaku_booking WHERE transaction = N AND state IN (...)

Why core does not already cache this

PrivateTempStore::get() delegates to DatabaseStorageExpirable::get(), which runs a plain SELECT every call with no static or request cache. That is deliberate on core's part, because the tempstore is shared across requests and another request may have changed it. It argues for the memo rather than against it: four unsynchronised reads inside one request can in principle return four different values, and a request that acts on one basket id throughout is more correct than one that re-asks and may get a different answer halfway through.

Proposed

Memoize the resolved pointer id for the request in BookingCart, invalidated by adopt() and forget(). Those are the only writers of the key and both are on this class, so the memo cannot go stale behind its own back. Expected: 25 to 22 queries per click.

Deliberately out of scope

Memoizing the lines. lineBookings() also runs twice per click and the just-saved booking is reloaded afterwards, but a hold is written in the middle of that request and the response payload has to see it, so a lines memo needs write invalidation. That is a separate and riskier change, noted here rather than folded in.

Issue fork yoyaku-3615284

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

  • mably committed 7ebea45a on 1.x
    task: #3615284 The basket pointer is re-read from the tempstore on every...
mably’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mably’s picture

Merged to 1.x as 7ebea45a.

The basket pointer is now read at most once per request, with adopt() and forget() keeping the held value in step. Taking a place in an already-open basket went from 25 queries to 22.

Two performance test classes came with it, following core's Gander pattern: the per-slot booking page and the place map. They assert query counts exactly, and deliberately assert nothing about the measurements that answer to the environment rather than to this module, which is why there is no cold-cache scenario, no CacheGetCount, and no assertion on the opening click of a visit.

The rest of the remaining 22 is recorded in #3615297: Taking one place still costs twenty-two queries, four of them removable without changing a guarantee, postponed: four reductions that need no change of guarantee, and two that are decisions rather than cleanups.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.