Magento 2 Performance Killer: Customer Email Update Bottleneck & Fix
Unmasking a Magento 2 Performance Killer: The Customer Email Update Bottleneck
Even in a robust platform like Magento 2, subtle code patterns can lead to significant performance bottlenecks. A recent GitHub issue (a re-report of a long-standing problem) has brought to light a critical performance drain related to a seemingly innocuous action: changing a customer's email address. For stores with a substantial customer base, particularly B2B or long-standing accounts, this operation can grind the system to a halt, causing timeouts and memory exhaustion.
The Problem: A Cascade of Unnecessary Operations
The core of the issue lies within Magento's Magento\Customer\Observer\UpgradeOrderCustomerEmailObserver, which is triggered whenever a customer's data object is saved (customer_save_after_data_object event). Instead of a direct, efficient update, this observer performs a highly inefficient sequence of operations:
- It loads every single order associated with the customer into memory.
- For each loaded order, it then calls
setShippingAssignments(),setOrderTaxDetails(), andsetPaymentAdditionalInfo(), each potentially running more database queries. - Finally, it attempts to "save" each order through the full resource model. This process is incredibly heavy, as it lazy-loads and re-saves all related entities for every order – items, payment details, status history entries, and addresses.
- As a final blow, each order's row in the
sales_order_gridis also refreshed indirectly.
This cascading effect means that a simple email change for a customer with 1000 orders can take over 33 seconds and consume more than 185 MB of extra memory, as demonstrated by the issue's author. This makes the functionality virtually unusable via the admin panel, REST API, GraphQL, or the storefront's "Account Information" page.
Reproducing the Issue
The issue provides a clear reproduction script, highlighting the diligent effort of the community. Developers can use a fixture profile to generate 1000 guest orders and then a PHP script to assign these orders to a new customer and measure the performance of changing their email. The commands are:
bin/magento setup:performance:generate-fixtures profile.xml
php repro-36081.php 1000
The results show a linear increase in time and memory with the number of orders, confirming the severity of the bottleneck.
The Elegant Solution: Direct Database Update
The proposed fix is a prime example of optimizing for denormalized data. Since customer_email is a flat column on both sales_order and sales_order_grid tables, it can be updated directly with simple SQL queries, bypassing the heavy object loading and saving. The suggested change involves replacing the inefficient loop with a direct database update:
$c>resourceConnection->getConnection('sales');
foreach (['sales_order', 'sales_order_grid'] as $table) {
$connection->update(
$this->resourceConnection->getTableName($table, 'sales'),
[OrderInterface::CUSTOMER_EMAIL => $customerEmail],
[
OrderInterface::CUSTOMER_ID . ' = ?' => (int) $customer->getId(),
OrderInterface::CUSTOMER_EMAIL . ' = ?' => (string) $originalCustomer->getEmail(),
]
);
}
This approach dramatically reduces execution time and memory usage to constant levels (around 0.2 seconds and 7.2 MB, regardless of order count). While this change means that per-order sales_order_save_* events and OrderRepository::save() plugins will not fire for this specific email update, it's generally considered an acceptable trade-off given that only a denormalized email field is being altered.
Key Takeaways for Magento Developers and Merchants
- Performance Awareness: Always be vigilant about operations that involve loading large collections of entities, especially if only a small, denormalized piece of data needs updating.
- Direct SQL for Denormalized Data: For flat, denormalized columns, direct database updates can offer massive performance gains over full object hydration and saving.
- Impact on B2B/High-Volume Stores: This issue particularly affects stores with many returning customers or B2B clients, where customer accounts accumulate numerous orders.
- Community Contributions: The detailed analysis and proposed solution from the community highlight the power of collaborative problem-solving in the Magento ecosystem.
This insight underscores the importance of continuous performance auditing, especially when dealing with core functionalities that can scale poorly with data growth. Implementing such fixes, whether through a core patch or a custom module, is crucial for maintaining a performant and stable Magento 2 environment.