Problem/Motivation

Currently commerce_line_item_delete_references() will remove the line item from all entities that reference it. The problem is most of the time if you delete more than one line item on the same order you will call entity_save() multiple times.

Proposed resolution

Instead of saving the order over and over again for each order, return a list of entities needing saving after and loop through that list.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

joelpittet created an issue. See original summary.

joelpittet’s picture

Status: Active » Needs review
Issue tags: +commerce-sprint
FileSize
2.78 KB

I've defaulted this to save for BC, yet there seems to be only one call to this function.

joelpittet’s picture

Issue summary: View changes
FileSize
268.6 KB

Adding profiling to show the savings.

joelpittet’s picture

Issue summary: View changes
joelpittet’s picture

Issue tags: +Performance

For the profiling, I had 40 order discounts on an order and that was just the ajax block loading the order summary firing discount refresh. It's not the whole solution but this provides some huge benefits to a solution.

mglaman’s picture

Okay, I originally went from "woah this breaks expectations" to "oh, now I get it!" Essentially the function's operation is unchanged for existing implementations. The controller sets the new flag parameter to FALSE. This allows it to only run the save operation on entities at a minimal amount. Otherwise we could run the save a whole bunch of times.

mglaman’s picture

  1. +++ b/modules/line_item/commerce_line_item.module
    @@ -657,8 +657,17 @@ function commerce_line_item_delete_multiple($line_item_ids) {
    + * @param bool $immediate_save
    ...
    +function commerce_line_item_delete_references($line_item, $immediate_save = TRUE) {
    
    @@ -676,12 +685,20 @@ function commerce_line_item_delete_references($line_item) {
    +          if ($immediate_save) {
    +            entity_save($entity_type, $entity);
    +          }
    

    $skip_save = FALSE ? and if false save, matches how core does order status updates.

  2. +++ b/modules/line_item/commerce_line_item.module
    @@ -657,8 +657,17 @@ function commerce_line_item_delete_multiple($line_item_ids) {
    + *   Weather to save immediately or defer till a call outside.
    

    Whether* (:

mglaman’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
74.55 KB

Here's some more data. Order with 9 order discounts.

Without patch: https://blackfire.io/profiles/2b13e5f8-d28f-46fd-b6fc-951fea90a1a8/graph

With patch: https://blackfire.io/profiles/30190eb3-1a36-48cb-8d7e-3fe99c2e3bcc/graph

commerce_discount_order_refresh was 200ms faster.
commerce_cart_refresh was 1.3s to 894ms

There's obviously issues in Discount, but this makes it a lot better.

cart in question

joelpittet’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs tests
FileSize
2.91 KB
2.5 KB

Thanks for testing @mglman and reviewing. I think I thought of a scenario where this could be bad and need to test it. But because the order entities aren't passed by reference each time and got with entity_load(), this could lead to maybe stale references. I think we'll need some tests to make sure we aren't leaving them behind.

And if that is the case, I think creating a new function instead of shoehorning the logic in this one may be worth while.

mglaman’s picture

But because the order entities aren't passed by reference each time and got with entity_load(),

But wouldn't this always be a risk? The loaded entities will still have hit the controller's internal static cache. The only issue is if someone edited the order and didn't commit their changes, it seems. I'm not sure how what's happening here is any different than what's existing, except we're providing a way to reduce the number of saves.

joelpittet’s picture

@mglaman I may be wrong but look at this piece of the code.

+++ b/modules/line_item/commerce_line_item.module
@@ -676,12 +686,20 @@ function commerce_line_item_delete_references($line_item) {
           commerce_entity_reference_delete($entity, $field_name, 'line_item_id', $line_item->line_item_id);
           entity_save($entity_type, $entity);

Specifically inside of commerce_entity_reference_delete(), note that all it does is remove the deltas from the field names on the passed in $entity reference. Because the $entity reference is being loaded in commerce_line_item_delete_references and not the controller that we moved the save to, we lose that change outside the function... right?

joelpittet’s picture

Ok this is inlined to avoid lost of changes of each entity when it get's clobbered by the new loaded entities.

Likely needs some refactoring.

joelpittet’s picture

+++ b/modules/line_item/includes/commerce_line_item.controller.inc
@@ -139,9 +139,52 @@ class CommerceLineItemEntityController extends DrupalCommerceEntityController {
+      $reference_fields = commerce_info_fields('commerce_line_item_reference');

Moving this outside the nested loops should provide some CPU performance improvements as well.

mglaman’s picture

+++ b/modules/line_item/includes/commerce_line_item.controller.inc
@@ -139,9 +139,52 @@ class CommerceLineItemEntityController extends DrupalCommerceEntityController {
+            $entities = entity_load($entity_type, array_keys($data));
...
+              if (!isset($entities_to_save[$entity_type . ':' . $entity_id])) {

Do we even need to load? We have the entity type and the entity ID.

If the type and entity ID are not set, add. This way we can actually stop loading duplicates. I get it shouldn't be that big of a deal because of static caches, but it could streamline the process some.

mglaman’s picture

+++ b/modules/line_item/includes/commerce_line_item.controller.inc
@@ -139,9 +139,52 @@ class CommerceLineItemEntityController extends DrupalCommerceEntityController {
+            $entities = entity_load($entity_type, array_keys($data));
...
+            foreach ($entities as $entity_id => $entity) {
...
+                  'entity' => $entity,
...
+                $entity = $entities_to_save[$entity_type . ':' . $entity_id];
...
+              commerce_entity_reference_delete($entity, $field_name, 'line_item_id', $line_item->line_item_id);
...
+      foreach ($entities_to_save as $entity_data) {
+        entity_save($entity_data['entity_type'], $entity_data['entity']);

Nevermind my last comment. This is done to ensure proper object references.

joelpittet’s picture

commerce_field_info() saved only maybe 2ms, but still this patch is still saving me ~5s load time which is about 90% improvement from what was inside commerce_line_item_delete_references()

And I'm quite confident we aren't missing any references removal saves with the latest patch.

smccabe’s picture

Refactored the patch slightly to remove unnecessary entity loads, otherwise I think it's good to go! Does this mean I've made it so I can't RTBC? hmm...

@joelpittet, if you still have your profiling environment up, mind running it through again?

joelpittet’s picture

Could you post an interdiff on that change please?

I can likely get a profiling scenario close, just want to read over the changes first.

Thanks for the review @smccabe

smccabe’s picture

FileSize
19 bytes
smccabe’s picture

FileSize
2.23 KB

Last one didn't upload right, here's the proper one.

joelpittet’s picture

@smccabe thank you for the interdiff. I couldn't follow the change. Maybe you can run me through the change at midcamp?

There are some coding standard nits to pick in there too but rather grasp the change before I look at those

smccabe’s picture

Talked w/ @joelpittet, turns out to be a really really edge case for my change, so more likely to cause performance issues than fix it. Putting this to RTBC with #12

smccabe’s picture

Status: Needs review » Reviewed & tested by the community
rszrama’s picture

Gonna commit this light reroll with the else hunk removed from the $entities_to_save array. Added some comments further explaining the rationale for the patch at the top of the try block.

rszrama’s picture

Status: Reviewed & tested by the community » Fixed

Committed.

  • rszrama committed 25ec640 on 7.x-1.x authored by joelpittet
    Issue #2653904 by joelpittet, smccabe, rszrama: Improve...

Status: Fixed » Closed (fixed)

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