From 436bf4f80e84b9c7b2025d9d070ad69f1f9667bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= <5421321+maarcingebala@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:24:25 +0200 Subject: [PATCH] Add missing locks for price recalculation (#15901) (#15905) * Add missing locks for price recalculation * Make sure to lock records in the same order Co-authored-by: Maciej Korycinski --- saleor/discount/utils.py | 29 ++++++++++++++++++++------ saleor/product/tasks.py | 25 ++++++++++++++++++---- saleor/product/utils/product.py | 14 ++++++++++--- saleor/product/utils/variant_prices.py | 12 ++++++++--- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/saleor/discount/utils.py b/saleor/discount/utils.py index 174042908fb..af11d126c30 100644 --- a/saleor/discount/utils.py +++ b/saleor/discount/utils.py @@ -1116,10 +1116,20 @@ def mark_active_catalogue_promotion_rules_as_dirty(channel_ids: Iterable[int]): rules = get_active_catalogue_promotion_rules() PromotionRuleChannel = PromotionRule.channels.through promotion_rules = PromotionRuleChannel.objects.filter(channel_id__in=channel_ids) - rules = rules.filter( + rule_ids = rules.filter( Exists(promotion_rules.filter(promotionrule_id=OuterRef("id"))) - ) - rules.update(variants_dirty=True) + ).values_list("id", flat=True) + + with transaction.atomic(): + rule_ids_to_update = list( + PromotionRule.objects.select_for_update(of=("self",)) + .filter(id__in=rule_ids, variants_dirty=False) + .order_by("pk") + .values_list("id", flat=True) + ) + PromotionRule.objects.filter(id__in=rule_ids_to_update).update( + variants_dirty=True + ) def mark_catalogue_promotion_rules_as_dirty(promotion_pks: Iterable[UUID]): @@ -1130,6 +1140,13 @@ def mark_catalogue_promotion_rules_as_dirty(promotion_pks: Iterable[UUID]): """ if not promotion_pks: return - PromotionRule.objects.filter(promotion_id__in=promotion_pks).update( - variants_dirty=True - ) + with transaction.atomic(): + rule_ids_to_update = list( + PromotionRule.objects.select_for_update(of=(["self"])) + .filter(promotion_id__in=promotion_pks, variants_dirty=False) + .order_by("pk") + .values_list("id", flat=True) + ) + PromotionRule.objects.filter(id__in=rule_ids_to_update).update( + variants_dirty=True + ) diff --git a/saleor/product/tasks.py b/saleor/product/tasks.py index 244bda08545..ed4fa458b87 100644 --- a/saleor/product/tasks.py +++ b/saleor/product/tasks.py @@ -7,6 +7,7 @@ from celery.utils.log import get_task_logger from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.db import transaction from django.db.models import Exists, OuterRef, Q, QuerySet from django.utils import timezone @@ -184,8 +185,17 @@ def update_variant_relations_for_active_promotion_rules_task(): channel_to_product_map = _get_channel_to_products_map( existing_variant_relation + new_rule_to_variant_list ) + with transaction.atomic(): + promotion_rule_ids = list( + PromotionRule.objects.select_for_update(of=("self",)) + .filter(pk__in=ids, variants_dirty=True) + .order_by("pk") + .values_list("id", flat=True) + ) + PromotionRule.objects.filter(pk__in=promotion_rule_ids).update( + variants_dirty=False + ) - PromotionRule.objects.filter(pk__in=ids).update(variants_dirty=False) mark_products_in_channels_as_dirty(channel_to_product_map, allow_replica=True) update_variant_relations_for_active_promotion_rules_task.delay() @@ -223,9 +233,16 @@ def recalculate_discounted_price_for_products_task(): settings.DATABASE_CONNECTION_REPLICA_NAME ).filter(id__in=products_ids) update_discounted_prices_for_promotion(products, only_dirty_products=True) - ProductChannelListing.objects.filter(id__in=listing_ids).update( - discounted_price_dirty=False - ) + with transaction.atomic(): + channel_listings_ids = list( + ProductChannelListing.objects.select_for_update(of=("self",)) + .filter(id__in=listing_ids, discounted_price_dirty=True) + .order_by("pk") + .values_list("id", flat=True) + ) + ProductChannelListing.objects.filter(id__in=channel_listings_ids).update( + discounted_price_dirty=False + ) recalculate_discounted_price_for_products_task.delay() diff --git a/saleor/product/utils/product.py b/saleor/product/utils/product.py index 3e37a0fb957..624b37b443e 100644 --- a/saleor/product/utils/product.py +++ b/saleor/product/utils/product.py @@ -1,6 +1,7 @@ from collections import defaultdict from django.conf import settings +from django.db import transaction from django.db.models import Exists, OuterRef, QuerySet from ...discount.models import PromotionRule @@ -121,6 +122,13 @@ def mark_products_in_channels_as_dirty( listing_ids_to_update.append(id) if listing_ids_to_update: - ProductChannelListing.objects.filter(id__in=listing_ids_to_update).update( - discounted_price_dirty=True - ) + with transaction.atomic(): + channel_listing_ids = list( + ProductChannelListing.objects.select_for_update(of=("self",)) + .filter(id__in=listing_ids_to_update, discounted_price_dirty=False) + .order_by("pk") + .values_list("id", flat=True) + ) + ProductChannelListing.objects.filter(id__in=channel_listing_ids).update( + discounted_price_dirty=True + ) diff --git a/saleor/product/utils/variant_prices.py b/saleor/product/utils/variant_prices.py index 23a970435a3..ed88f57b3e0 100644 --- a/saleor/product/utils/variant_prices.py +++ b/saleor/product/utils/variant_prices.py @@ -117,11 +117,13 @@ def _update_or_create_listings( ): if changed_products_listings_to_update: ProductChannelListing.objects.bulk_update( - changed_products_listings_to_update, ["discounted_price_amount"] + sorted(changed_products_listings_to_update, key=lambda listing: listing.id), + ["discounted_price_amount"], ) if changed_variants_listings_to_update: ProductVariantChannelListing.objects.bulk_update( - changed_variants_listings_to_update, ["discounted_price_amount"] + sorted(changed_variants_listings_to_update, key=lambda listing: listing.id), + ["discounted_price_amount"], ) if changed_variant_listing_promotion_rule_to_create: _create_variant_listing_promotion_rule( @@ -129,7 +131,11 @@ def _update_or_create_listings( ) if changed_variant_listing_promotion_rule_to_update: VariantChannelListingPromotionRule.objects.bulk_update( - changed_variant_listing_promotion_rule_to_update, ["discount_amount"] + sorted( + changed_variant_listing_promotion_rule_to_update, + key=lambda listing: listing.id, + ), + ["discount_amount"], )