Skip to content

High Database Load for Sales Rule Validation #19117

Closed
@rauberdaniel

Description

@rauberdaniel

Preconditions (*)

  1. Magento 2.2.5
  2. PHP 7.1 / MySQL 5.6.41

Steps to reproduce (*)

  1. Create multiple Sales Rules with multiple coupons each
  2. Get a lot of customers to redeem codes

Expected result (*)

  1. High Performance, as finding sales rules is fast using indexes on Database

Actual result (*)

  1. Incredibly Low Performance, as SQL query is badly constructed so it cannot use indexes

Description

We’re currently having about 400 sales rules (most of them are inactive or expired) as well as about 600.000 coupons. The query, that is currently being used by the sales-rule module is extremely inefficient on the database and therefore results in massive database CPU consumption when having several orders with coupons per minute. However, this query can greatly be optimized by splitting it into two queries.

The query that is generated by the setValidationFilter method in magento/vendor/magento/module-sales-rule/Model/ResourceModel/Rule/Collection.php currently looks like this:

SELECT `main_table`.*, `rule_coupons`.`code`
FROM `salesrule` AS `main_table`
INNER JOIN `salesrule_website` AS `website`
ON website.website_id IN ('1') AND main_table.rule_id = website.rule_id
INNER JOIN `salesrule_customer_group` AS `customer_group_ids`
ON main_table.rule_id = customer_group_ids.rule_id AND customer_group_ids.customer_group_id = 0
LEFT JOIN `salesrule_coupon` AS `rule_coupons` ON main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != 1

WHERE (from_date is null or from_date <= '2018-11-01')
AND (to_date is null or to_date >= '2018-11-01')
AND (`is_active` = '1')
AND
(
    main_table.coupon_type = 1
    OR
    (
        (
            (main_table.coupon_type = 3 AND rule_coupons.type = 1)
            OR
            (main_table.coupon_type = 2 AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)
            OR
            (main_table.coupon_type = 2 AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)
        )
        AND
        rule_coupons.code = 'COUPONCODE'
        AND
        (rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= '2018-11-01')
    )
)
AND (`is_active` = '1') ORDER BY sort_order ASC;

The problematic part in this query is line 14 main_table.coupon_type = 1 OR. This part forces the database to filter the joined tables based on coupon_type. By simply removing that line, the rule_coupons.code can be used as an index to find exactly the one matching sales rule without having to join and filter a massive amount of rows. The line 14 is a completely separate case than the rest in that scope (line 13 to 29) and therefore should be treated separately e.g. by a second query in a UNION operation.

Some details using the MySQL EXPLAIN function:

Original Query:
image

Query without line 14/15 (not added in a second query in this example, therefore ignoring sales rules without coupons):
image

This is a major performance improvement especially for larger amounts of sales rules and coupons and absolutely critical.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Component: SalesRuleFixed in 2.2.xThe issue has been fixed in 2.2 release lineFixed in 2.3.xThe issue has been fixed in 2.3 release lineIssue: Clear DescriptionGate 2 Passed. Manual verification of the issue description passedIssue: ConfirmedGate 3 Passed. Manual verification of the issue completed. Issue is confirmedIssue: Format is validGate 1 Passed. Automatic verification of issue format passedIssue: Ready for WorkGate 4. Acknowledged. Issue is added to backlog and ready for developmentReproduced on 2.2.xThe issue has been reproduced on latest 2.2 releaseReproduced on 2.3.xThe issue has been reproduced on latest 2.3 releasehelp wanted

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions