Magento MSI Architecture: Source Selection, Reservations, and Operational Boundaries
Magento MSI replaces single-stock with Stock→Source→SourceItem hierarchy. Covers source selection algorithms, reservation compensation, ERP trust boundaries, operational repair commands, and failure modes that force redesign.
30 Sept 2025, 21:16 UTC

The Problem: Distributed Inventory Without a Single Source of Truth
Magento 2.4.x replaced the monolithic cataloginventory_stock model with Multi-Source Inventory (MSI), introducing a Stock→Source→SourceItem hierarchy. Each website maps to one Stock; each Stock aggregates multiple Sources (warehouses, retail stores, drop-shippers). The shift solves multi-location fulfillment but creates new failure surfaces: reservation drift, source-selection oversell, and eventual-consistency gaps between ERP/POS and Magento. This note covers the smallest suitable design, trust boundaries, operational checks, and the conditions that would force a redesign.
Requirements Driving the Design
- Multi-location fulfillment: Orders must draw from the nearest or highest-priority source with available quantity.
- External system ownership: ERP/POS owns authoritative quantity; Magento never writes back.
- Oversell prevention: Reservations must compensate at each state transition (order, shipment, credit memo).
- Operational visibility: Staff must detect and repair reservation drift without database surgery.
Smallest Suitable Design: Stock→Source→SourceItem
The hierarchy is deliberate:
- Stock: Logical pool bound to a website (one-to-one).
- Source: Physical or virtual location with coordinates, priority sort order, and
is_enabledflag. - SourceItem: Join row holding
qty,status(in/out of stock), linked to a product SKU.
No cross-Stock sharing. A product assigned to Stock A cannot pull from Sources in Stock B. This boundary simplifies SSA (Source Selection Algorithm) but forces explicit Stock assignment per website.
Source Selection Algorithm (SSA) at Checkout
SSA runs when the shopper proceeds to shipping. Three built-in strategies:
- Distance Priority: Uses source latitude/longitude vs. shipping address (requires geocoded sources).
- Source Priority: Admin-defined sort order; lowest numeric priority wins.
- Custom: Implement
Magento\InventorySourceSelectionApi\Model\SourceSelectionInterface.
Critical guard: Custom SSA must filter is_enabled = 1. The core algorithms do this; a custom implementation that skips the check will select disabled sources and cause oversell.
Reservation System: Event-Driven Compensation
Every quantity change writes a row to inventory_reservation:
| Event | Qty Delta | event_type |
|---|---|---|
| Order placed | -qty | order_placed |
| Shipment created | +qty | shipment_created |
| Credit memo | +qty | creditmemo_created |
| Cancellation | +qty | order_cancelled |
Salable quantity = SUM(SourceItem.qty) - SUM(uncompensated reservations). A reservation is "compensated" when a later event offsets it (shipment compensates order_placed). The cron inventory.reservations.cleanup removes fully compensated rows (default 24h TTL, configurable via inventory/reservations/cleanup_expired_reservations_interval).
Trust and Data Boundaries
ERP/POS writes only to SourceItem via REST/GraphQL inventorySourceItems endpoint. Magento never pushes quantity back. The integration path:
- ERP posts
POST /V1/inventory/source-itemswithsource_code,sku,qty,status. - Magento validates, writes
inventory_source_item, emits message to RabbitMQ topicinventory.source.items.update. - Consumers (reindex, notification) react asynchronously.
This is eventual consistency. If ERP and admin panel both adjust the same SourceItem without idempotency keys, split-brain occurs: last write wins, reservation math drifts.
Operational Checks and Repair Commands
Run these on the Magento application server (CLI, bin/magento user with file-system write access to var/ and generated/):
# 1. Detect reservation drift
bin/magento inventory:reservation:list-inconsistencies
# Expected: zero rows on healthy system. Non-zero = missing compensations or double-compensations.
# 2. Repair missing compensations (creates offsetting rows)
bin/magento inventory:reservation:create-compensations
# Risk: runs globally; test on staging first. Does not delete rows, only adds compensating entries.
# 3. Verify SSA choice (enable debug logging first)
bin/magento config:set dev/debug/debug_logging 1
# Place test order, then check var/log/source_selection_algorithm.log
For quantity sync health, monitor cron inventory.source.items.cleanup (removes stale SourceItems) and inventory.mass.update (bulk API updates without full reindex).
Concrete Diagnostic Scenario
Symptom: Salable quantity shows 5 but frontend shows out-of-stock. Check:
- Run
inventory:reservation:list-inconsistencies— returns 3 rows withevent_type=order_placeduncompensated. - Query
inventory_reservationfor those SKUs:SELECT * FROM inventory_reservation WHERE sku = 'SKU-123' AND event_type = 'order_placed' AND compensation_id IS NULL; - Find corresponding orders: shipments missing? Credit memos not issued?
- Run
create-compensations; re-run inconsistencies check — should return zero.
Root cause: Shipment API call failed silently; reservation never compensated. Fix: add idempotency key to shipment creation, retry queue consumer.
Failure Modes
| Mode | Trigger | Impact | Mitigation |
|---|---|---|---|
| Split-brain | ERP + admin adjust same SourceItem | Quantity drift, oversell | Single writer per SourceItem; idempotency keys on ERP writes |
| Reservation bloat | High order volume, cron lag | DB size, query slowdown | Tune cleanup interval; partition inventory_reservation by created_at |
| SSA picks disabled source | Custom algorithm omits is_enabled filter | Oversell from inactive warehouse | Code review gate; integration test with disabled source |
| Legacy observer breakage | Third-party uses cataloginventory_stock_item events | Silent qty mismatch | Migrate to Magento\InventoryApi service contracts |
Conditions That Change the Design
- Real-time ATP (Available to Promise) required: Move to event-sourced CQRS with separate read model; MSI's eventual consistency cannot guarantee millisecond accuracy.
- Sources exceed ~1,000: SSA performance degrades (iterates all sources per request). Consider regional Stock partitioning or cached source ranking.
- Regulatory data residency: MSI assumes single database. Cross-region sharding requires separate Magento instances per region with async sync — not a configuration change.
Verification Checklist (Run on Staging)
bin/magento module:status | grep Inventory— allMagento_Inventory*enabled.- Create two Sources, assign to Stock, assign Stock to Website; verify rows in
inventory_source_itemvia SQL. - Place order; check
inventory_reservationfor-qtywithevent_type=order_placed. - Ship order; verify
+qtycompensation row withcompensation_idlinking to order_placed row. - Run
inventory:reservation:list-inconsistencies— expect zero. - Load test 500 concurrent orders; monitor
inventory_reservationrow growth and cron cleanup latency (target < 5 min).
Limitations and Version Assumptions
This note assumes Magento 2.4.6+ with all Magento_Inventory* modules enabled. MSI migration tool runs once; rollback requires full database restore. GraphQL salable_quantity reflects MSI only when Inventory modules are active; REST /V1/stockItems returns legacy qty if MSI is disabled. Third-party extensions using cataloginventory_stock_item observers break silently — audit before enabling MSI in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.