Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
uc_order_handler_field_order_actions() invokes in render() something like this:
uc_order_load()->uc_order_load_multiple()->entity_get_controller('uc_order')->load() so finally UcOrderController->attachLoad() is invoked for each order row.
class UcOrderController extends DrupalDefaultEntityController {
function attachLoad(&$orders, $revision_id = FALSE) {
foreach ($orders as &$order) {
/* ... */
$fields = array();
// Make sure the total still matches up...
if (($total = uc_order_get_total($order)) !== $order->order_total) {
$fields['order_total'] = $total;
$order->order_total = $total;
}
if (($count = uc_order_get_product_count($order)) !== $order->product_count) {
$fields['product_count'] = $count;
$order->product_count = $count;
}
// HERE IS THE PROBLEM:
if (count($fields)) {
$query = db_update('uc_orders')
->fields($fields)
->condition('order_id', $order->order_id)
->execute();
}
}
parent::attachLoad($orders, $revision_id);
}
On each rendered view row, where order actions field is used, a write query to db is made to update the total, like:
UPDATE uc_orders SET order_total='XXX.XX', product_count='XX'
// x [VIEW's NO OF ROWS]
As result, for the same view with actions field, the render time is 8 times slower than for the same view without actions field.
View render time (with actions field): <strong>5422.22 ms</strong>
View render time (without actions field): <strong>708.28 ms</strong>
The query time is not changed because query is not modified.
Comment | File | Size | Author |
---|---|---|---|
#11 | order_views_actions-2352957-11.patch | 1005 bytes | djdevin |
| |||
#7 | after.png | 34.61 KB | SilviuChingaru |
#7 | before.png | 35.79 KB | SilviuChingaru |
#6 | Issue-2352957-uc_order_actions-in-one-query.patch | 2.28 KB | SilviuChingaru |
#5 | Issue-2352957-2-fixed-typecasting-error.patch | 991 bytes | SilviuChingaru |
Comments
Comment #1
SilviuChingaru CreditAttribution: SilviuChingaru commentedI propose to remove the following logic from attachLoad():
I think that it should remain a read function and we should implement this in save() method.
Also we should not invoke more than one query / view where this is possible, so I suggest to pass to uc_order_actions(), an std object that emulates only order fields needed by uc_order_actions() like:
So, this way, we could query those fields as additional fields in handler::query().
I'm waiting for maintainers proposed fix before working on a patch for this major performance leak.
Comment #2
longwaveWhy is an update always performed? The code is designed to only run the update if the order total or product count have changed, that is why it is wrapped with
if (count($fields)) {
.Comment #3
SilviuChingaru CreditAttribution: SilviuChingaru commentedI'll dig into it today and let you know. Anyway, I thing we still have not to mix write methods with read methods and I think that uc_order should be fixed like in the attached patch.
With the attached patch I obtained the following timings on the same order views:
Before:
After:
Comment #4
SilviuChingaru CreditAttribution: SilviuChingaru commentedComment #5
SilviuChingaru CreditAttribution: SilviuChingaru commentedThe problem is from typecasting.
I've obtained the following output:
So
So we fix this typecasting error, and we ensure that we are comparing apple with apple and pears with pears.
Comment #6
SilviuChingaru CreditAttribution: SilviuChingaru commenteduc_order_handler_field_order_actions::render() loads an order for each displayed row, so an additional query is made to the database on each rendered row.
Because in uc_order_actions() we only need 3 order fields, order_id, order_status and uid I think a better approch would be to load those 2 additional fields order_status and uid in the views query, like core comment link does, and just mimic an order object when passing to uc_order_actions().
The patch also preserves backwards compatibility, but hook_uc_order_actions() and hook_uc_order_actions_alter() should be changed in future to get only $order_id as arg not the full $order object.
With all those 3 patches applied, I've got stunning results, attached screenshots speak for themselves.
I've tested with a view with 1000 orders / page for results to be more obvious.
Comment #7
SilviuChingaru CreditAttribution: SilviuChingaru commentedUps, I forgot to attach screenshots...
Comment #8
longwaveI am not sure it is safe to round order_total like that; there are edge cases with some taxes where order_total has more precision than uc_currency_prec specifies. Can we just use != instead of !== here?
The uc_order_actions patch looks good though. I wonder if we can just get away with sending only those three properties to all implementations; there are likely very few other implementations of this hook out there.
Comment #9
SilviuChingaru CreditAttribution: SilviuChingaru commentedI think no, we cannot use != here because we cannot rely on PHP calculating float value correctly. See the WARNING on PHP: Floating point numbers, exactly the part: "(...) So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. (...)
Se also user commens, including #113703:
So, i've executed the following in devel/php:
and got bool(false) which is definitely NOT TRUE.
So, you think that we should not preserve backward compatibility at all and just modify the hook and pass only those three params?
Comment #10
SilviuChingaru CreditAttribution: SilviuChingaru commentedI think this should be reviewed and commited as fast as possible because views that contains order action are very slow and very annoying.
Comment #11
djdevinOld issue but still an issue, it's more noticeable when you have a remote DB server. Loading a bunch of orders also fires a bunch of updates for them.
Regarding the precision I've found that it's safer to do a range comparison:
But I also agree that this should move into ::save()