Source-level reservations
Opt-in, off by default.
Enable: cataloginventory/source_reservations/enabled, in Stores › Configuration › Catalog ›
Inventory › Source-Level Reservations.
In upstream MSI a reservation belongs to a stock. Two stocks that share a source each see the full physical quantity of that source and can both sell it. With source-level reservations, each sales reservation is split into one row per source, allocated across the enabled sources of the stock in priority order. Reservations then affect the salable quantity of every stock sharing the source, which closes the cross-stock oversell gap, and compensations (shipment, cancellation, credit memo) always land on the sources the demand was originally allocated to, whichever source ships.
Turning it on or off
Section titled “Turning it on or off”- Toggling the flag requires a full
bin/magento indexer:reindex inventory. - Orders placed while the flag was off are compensated stock-scoped, exactly as before; mixed states are safe.
inventory:reservation:create-compensationsstill creates stock-scoped compensations. Per-source residues of such orders are kept, not deleted by the cleanup cron, and remain visible until compensated per source.
Locks per source
Section titled “Locks per source”Concurrent orders that could draw from the same source are serialised against each other. The lock is
taken per (sku, source) rather than per (sku, stock), so two orders on different stocks sharing a
source can no longer race on it. Orders with no source in common run fully in parallel. This
upgrades the per-stock lock introduced upstream by ACP2E-4509.
The lock covers the SKUs the order writes reservations for: the child simples of a configurable or a
bundle, never the parent, so a standalone order and a composite order of the same simple exclude
each other. SKUs that get no reservation (manage_stock = 0, per item or globally, or
can_subtract = 0) take no lock, and neither does a reservation that gives stock back, such as the
rollback of a rejected asynchronous order.
It is held only while the reservation is written: the salability check and the insert, 8 to 27 ms measured, not the payment, emails and order save that follow. Because it wraps the reservation itself, every path that reserves at placement takes it: storefront and admin orders, REST, multishipping and the initial placement of asynchronous orders. A caller that places orders inside its own database transaction is not protected until it commits.
Shipping from the reserved sources
Section titled “Shipping from the reserved sources”An order reserves each item at specific sources when it is placed, and shipping reads that allocation.
-
The admin shipment form suggests the sources the order reserved at, with those quantities. The source selection only places the rest.
-
A shipment created without a source in a stock with several sources (REST
ShipOrderwithoutextension_attributes.source_code, or a programmatic shipment) resolves the source in this order:- the only source of the stock;
- the source the order reserved every item at;
- the default source selection, when it ships everything from one source.
When none applies, the shipment is rejected with a validation message asking for a source.
-
Shipping from a source the order did not reserve at still works. The source selection page shows a warning under the Source selector naming the SKU and its reserved sources with quantities, and explaining that it takes the stock from the selected source and releases the reservation at the other one.
A source chosen explicitly, in the admin or through extension_attributes.source_code, is used as
given.
Transfers respect reservations
Section titled “Transfers respect reservations”- Full transfer (
POST /V1/inventory/bulk-product-source-transfer) moves the reservations of open orders with the stock. For every open order it releases the origin’s pending balance and places the same quantity at the destination, under the same(sku, source)locks as checkout and in the same transaction as the stock. It is refused when the destination is not assigned to the stock of an order holding reservations at the origin; the error names the order. - Partial transfer (
POST /V1/inventory/bulk-partial-source-transfer) holds the same locks and refuses to move more than the origin quantity net of its source reservations. - The oversell guard on new reservations checks a batch’s net demand per stock and SKU, so a balanced move between sources asks the stock for nothing.
Reconciliation
Section titled “Reconciliation”Guards on the reservation path clamp and reject reservations that would break the ledger. When a ledger has already drifted, reconciliation repairs it by appending the compensations that terminal orders are missing.
| Setting | Default | Effect |
|---|---|---|
cataloginventory/source_reservations/reconcile_cancel_refund |
0 |
When an order reaches a final state, append any release its cancel or credit memo observer failed to write. Bounded so it can never over-release. |
cataloginventory/source_reservations/reconcile_sweep_enabled |
0 |
Periodically reconcile terminal orders whose reservations were never released, including drift from direct database edits or third-party state changes. |
cataloginventory/source_reservations/reconcile_sweep_cron |
0 * * * * |
Schedule of the sweep. |
The same pass runs on demand:
bin/magento inventory:reservation:reconcile --dry-run # report the residues, write nothingbin/magento inventory:reservation:reconcile --limit=500It processes at most --limit orders (500 by default), reindexes the stocks it touched and says when
the limit was reached, so it can be run again for the rest.
Abandoned placements
Section titled “Abandoned placements”A placement writes its reservation before it takes the payment and saves the order. When anything in between throws, the reservation is given back on the spot. When the process dies instead (out of memory, killed by the FPM timeout, a restarted container), nothing gives it back, and the stock stays reserved for an order that does not exist.
The same pass repairs those. An abandoned placement is an increment id that reserved stock at
placement, still holds it, and has no row in sales_order. Its reservations are compensated to zero
per stock, SKU and source, as an abandoned_placement event.
A placement that is still running looks the same for a few seconds, so the pass only touches reservations written before a mark that is at least 15 minutes old, by the database clock. Each run records the newest reservation id as the next mark once the current one is that old, and only the reservations below the mark are released: a retry of the same cart that is still running keeps its own. In practice:
- With the hourly sweep, an orphaned reservation is repaired between one and two hours after it was written.
- The first run after upgrading only records the mark and repairs nothing.
- Two manual runs a few seconds apart repair nothing new; the command says how many placements are too recent to repair yet.
--dry-runreports what would be repaired without writing and without moving the mark.- A mark above the newest reservation id, after
inventory_reservationwas truncated or rebuilt, is discarded and recorded again.
A retry of the same cart reuses its increment id. If a placement died and the customer then placed the order, the dead attempt’s reservation is added to the order’s own until the order reaches a final state, when the terminal-order pass releases it.
A placement whose order was saved and that then fails in a later step, such as an observer that throws after the save, still gives its reservation back. The order then exists with its reservation released, so the salable quantity is overstated while it stays open. This was already the case for exceptions and now also applies to PHP errors.
Oversell detection
Section titled “Oversell detection”Detection only: it never blocks a sale. Physical stock stays authoritative; an oversold position is logged and raised in the admin inbox for reconciliation.
| Setting | Default | Effect |
|---|---|---|
cataloginventory/source_reservations/oversell_detection_enabled |
0 |
Alert when a stock change leaves a source below the reservations already committed against it. |
cataloginventory/source_reservations/oversell_sweep_enabled |
0 |
Periodically scan every source for positions below their committed reservations, including drift from direct database edits that no stock write can observe. |
cataloginventory/source_reservations/oversell_sweep_cron |
0 * * * * |
Schedule of the sweep. |
On demand:
bin/magento inventory:reservation:detect-oversell [--source=<code>] [--sku=<sku>] [--limit=1000]It lists each oversold source position with its physical quantity, its committed reservations and the difference, and exits non-zero when it finds any.
Both sweeps and both commands work on per-source reservations, so they find nothing while the feature is off. The one exception is the abandoned-placement pass, which also repairs stock-scoped reservations.