Repository navigation
Count discount usage once per request in limitation checks - #969
Open
KrzysztofPajak wants to merge 2 commits into
Open
KrzysztofPajak wants to merge 2 commits into
KrzysztofPajak wants to merge 2 commits into
Conversation
A listing validates every applicable discount for every product. For an NTimes or NTimesPerUser discount each check sent GetDiscountUsageHistoryQuery with PageSize = 1, which costs a sorted find plus a count: 48 queries on a page of 24 products. DiscountValidationService now counts the usage history with one CountAsync and keeps the count task for the rest of the scope, keyed by discount and customer. Product overviews are prepared concurrently, so concurrent checks share one query. ExistsCodeInDiscount uses AnyAsync instead of loading a list.
GetOrAdd could run the count factory once per racing thread, so a concurrent listing still sent duplicate queries; the count is now a Lazy task. A faulted count is removed from the scope instead of failing every later validation of that discount. The concurrency test now counts repository calls, and a new test covers the retry after a failed count.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A storefront listing validates every applicable discount for every product (
DiscountHandlerService.GetAllowedDiscounts). For a discount limited withNTimesorNTimesPerUser, each check sentGetDiscountUsageHistoryQuerywithPageSize = 1. That query goes throughPagedAsync, so each check cost a sorted find plus a count. A discount assigned to all products therefore cost 48 queries on a page of 24 products.Changes in
DiscountValidationService:CountAsyncinstead ofPagedAsync.GetProductOverviewHandlerprepares product overviews concurrently (Task.WhenAll), so the countTaskis stored in aConcurrentDictionaryand concurrent checks share one query.ExistsCodeInDiscountusesAnyAsyncinstead of loading the matching coupons into a list.IRepository<DiscountUsageHistory>instead ofIMediator. It already usedIRepository<DiscountCoupon>the same way.The limit semantics are unchanged: the count still includes canceled usages, and
NTimesPerUserstill filters by the customer.Nothing is cached between requests. Within one request the count is read once, so a usage written later in that same request is not seen. Order placement validates before it inserts the usage history, so it is not affected.
The
DiscountUsageHistoryindexes (DiscountId,CustomerId) are already created by the installer, so no migration is needed.Measured
Local MongoDB, 100,000 usage history documents, one limited discount with 1,000 usages, median of 7 runs, database queries only:
NTimesNTimesPerUserThese are localhost timings. With network latency to the database, the 47 round trips saved are worth more.
Breaking changes
DiscountValidationServiceconstructor takesIRepository<DiscountUsageHistory>instead ofIMediator. This only affects code that constructs or subclasses the service directly; DI resolves it as before.Testing
dotnet testonGrand.Business.Catalog.Tests(653 passed, 1 skipped) andGrand.Business.Checkout.Tests(292 passed). New tests cover:NTimeslimit, both reached and belowNTimesPerUsercounting only that customer's usagesExistsCodeInDiscountPluginDependencyCycleTests(3 passed). The tests needed a mock of the new repository dependency.dotnet build GrandNode.slnx: 0 errors.