-
-
Notifications
You must be signed in to change notification settings - Fork 729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix customer balance n+1 #6366
Fix customer balance n+1 #6366
Conversation
54ba5cf
to
8bc0f6b
Compare
There's another issue on this page, which is lack of pagination. The "load more" buttons on the page don't make new requests, all the data for all customers is loaded in Angular when a hub is selected. This can be 800+ records for a large hub. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you consider this PR?
https://github.com/openfoodfoundation/openfoodnetwork/pull/5031/files
It must include payments.sum(amount). order.payment_total is not the same. I recommend you look at prod data and see the difference for completed orders and for cancelled orders.
Yes, I looked at it and I didn't see why. Is it because |
I believe because an order can have multiple payments but spree_orders.payments only records the first payment on an order |
yes, I checked the code and the DBs at the time I reviewed the PR and then afterwards. It is my opinion that the algorithm is 100% correct in the pending PR and we have to follow it in this new query. |
or maybe I should say: please prove me wrong here, I may be wrong, this is a tricky space. But I think you need to do that with examples from prod data, ok? This is for the inclusion of spree_payments vs spree_orders.payment_total in the query. anyway, I am 100% sure you need to include payment_total (wherever it is coming) from cancelled orders but not those orders total, following this logic: |
Thanks everyone! It was clear that had to validate my ideas early on with you all 💪 Yes, I think that logic is right @luisramos0 . For the record, I also found out that openfoodnetwork/app/models/spree/order.rb Lines 502 to 517 in 8c99608
|
c0196b0
to
2e49286
Compare
This last commit should implement that same logic but in SQL @luisramos0 |
I think you are missing the fact that the join is by email not customerid. Guest orders will not have customer id. |
Red build... looks like it needs a few tweaks. |
Yes, that's why it is still draft. It's not yet done. I need to adapt things now to this new query. |
that's right, but then, there's no customer for whom to show the balance, right? oh wait, we do have a "phantom" customer created every time they buy. Got it. |
I was thinking just about customer_id null in spree_orders, guest users,
have a look in prod:
select o.email, o.customer_id, o.user_id from spree_orders o where o.state
= 'complete' and o.customer_id is null and exists (select 1 from customers
where email = o.email);
But now that you mention, multiple customer records with same email:
select email, count(*) from customers group by email having count(*) > 1;
I think this should all be grouped by email so the balance is real.
…On Sat, 21 Nov 2020 at 09:03, Pau Pérez Fabregat ***@***.***> wrote:
I think you are missing the fact that the join is by email not customerid.
Guest orders will not have customer id.
that's right, but then, there's no customer for whom to show the balance,
right? oh wait, we do have a "phantom" customer created every time they
buy. Got it.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6366 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMQPOSSUVGTWLS7MKR22U3SQ564HANCNFSM4TSGCPDA>
.
|
That's exactly what I thought. Since they are identified by email grouping by that will do the trick. |
2e49286
to
32bd88b
Compare
For the record: in a guest order, there's no customer_id provided by the request. In It's also worth noting that the customers DB table was introduced back on June 4th, 2015, and data was never migrated to the new schema. Hence, there are 4209 non-cart orders in AU that have no associated customer although they do have an order email. |
448dd2a
to
d5b1da4
Compare
tag_rule_mapping: tag_rule_mapping, | ||
customer_tags: customer_tags_by_id | ||
render json: @collection, | ||
each_serializer: ::Api::Admin::CustomerWithBalanceSerializer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, the serializer's class name is a clear indication that we should extract an API endpoint out of this, together with the if branch of #collection
#butNotToday.
80ab1ca
to
a1fbd27
Compare
As is, `payment_total` is only increased after successfully processing a payment and never updated. This inconsistency breaks `CustomerWithBalance` which relies on it. Needless to say that if we keep this denormalized column, we better make it consistent. I investigated current Spree's master branch (709e686cc0) and they also realized it was broken. Now `Payment` runs the following from the `after_save` `update_order` callback. ```rb order.updater.update_payment_total if completed? || void? ``` I also took the chance to rearrange tests a bit.
We only care about non-cart orders and skipping carts, saves PostgreSQL query planner to go through thousands of records in production use cases (my food hub). We go from ```sql -> Index Scan using index_spree_orders_on_customer_id on spree_orders (cost=0.42..12049.45 rows=152002 width=15) (actual time=0.015..11.703 rows=13867 loops=1) ``` to ```sql -> Index Scan using index_spree_orders_on_customer_id on spree_orders (cost=0.42..12429.46 rows=10802 width=15) (actual time=0.025..17.705 rows=9954 loops=1) ```
This query object is meant to be reusable but those includes are context-specific and will likely not be needed when reusing the query elsewhere. If we keep them there, chances are next dev might not notice it and will introduce a performance regression.
This makes it possible to deploy it without releasing it to users since the toggle is not enabled for anyone. It aims to make the balance calculation consistent across pages.
These order states are already taken care of because they fall in the `WHEN state IS NOT NULL` case.
These states are reached when the order is complete and shipped. An admin can create a new return authorization, which will set the order in `awaiting_return` state. It's only after, when we call `return_authorization#receive` that the return authorization moves to `received` state and the order to `returned`. You can do so from the UI by editing the return authorization and clicking on Receive. However, we didn't find any order in such state in UK, FR and AU. The UX is quite obfuscated. That must be why no users clicked on it. The `returned` state cannot count for the balance as is, since some of the products are returned to the shop. That's enough for now.
71e6978
to
33c72ec
Compare
This was lacking a rebase to get the various beta_testers pieces that got merged. 33c72ec should fix it. |
Allright @sauloperez ! As discussed and as mentioned on the testing notes, I tested this in two steps. Step 1) testing feature is toggle Significantly better performance in the To verify this, I logged onto the server and tailed the log file by running a) Before staging and toggling the feature (this was ran several times, so these can be seen as average or representative values): [2021-01-12 13:56:36#30737] INFO -- : Completed 200 OK in 267ms (Views: 212.3ms | ActiveRecord: 23.4ms) b) After staging the PR (feature not toggled) [2021-01-12 14:47:34#15577] INFO -- : Completed 200 OK in 240ms (Views: 183.3ms | ActiveRecord: 26.0ms) c) After staging the PR and toggling the feature - by adding the user as a beta tester [2021-01-12 15:03:12#19935] INFO -- : Completed 200 OK in 270ms (Views: 211.8ms | ActiveRecord: 25.1ms) The conclusion is: toggling the feature works!! And it works only after staging and after enabling: we can see a significant reduction in c) only, both on the ActiveRecord and the actual request time. |
Step 2) Now, to the actual test cases. I added another user as a beta tester, a hub manager - and placed orders this hub as both logged in and as a guest customer. Customer balance in the i) In all cases, it was verified that incomplete orders resulted in customer balance = 0; the customer is added to the ii) after placing an order with cash:
iii) after placing an order with StripeSCA/Paypal:
iv) editing orders
In all the test cases i) ii) iii) and iv) the customer balance as seen from the Hub matched the values seen on the customer's account. Reports seem to work as before - payments report |
Yup, after staging master, the report blows up as well, for dates not related with this PR. So, this looks good to me. If you feel there is that should be tested @sauloperez please let me know - moving to ready to go! |
that's an amazing work @filipefurtad0 ! thanks a lot for the patience 😅 . I agree this is more than enough to ship it 🚢 . What I'll do now is make sure the automated tests cover what you checked manually so that the test suite gives us the same confidence as your thorough manual tests. Don't hesitate to suggest any improvements on that regard.
Let me know if I can help you find the root cause. I'd take the chance to improve that report now that I'll be touching it. |
|
For the record, this is the monitoring dashboard we should keep an eye on. Toggling it on should have a deep impact on those response times. |
What? Why?
This solves v3.3.1's performance regression when calculating the customer balance.
It's simpler and many orders of magnitude more efficient to ask the DB to aggregate the customer balance based on their orders. It removes a nasty N+1. To do that I created a query object based on the work started in #5031.
This is a first attempt at solving the negative performance impact of our current approach but the best solution in the long term is to either offload the balance calculation to a background job to be performed overnight so we don't go through all customer orders every time. No doubt it'll be slow for hubs that have thousands of customers with thousands of orders.
This doesn't
close
#4732. That needs small and fast iterations using this new approach to each of the pages displaying the customer balance.What should we test?
This involves two sets of tests.
On one hand, we need to smoke test customer balance in /admin/customers. Just to confirm that, as the automated tests tell, we haven't changed a bit of its behavior.
On the other, we need to check how the new implementation works. For that, the user you test it with needs to have the feature enabled for them. To do that, we need to add the account's email address to the list of beta users. Then, with a shopper account create some orders and payments and check:
The figures should match with all the changes you make. It'd be good to go through all of the states of the order. If there's one broken, please, don't let this pass without a test covering that case.
Release notes
Remove N+1 when fetching customers' balance from /admin/customers and fix
payment_total
for void payments.Changelog Category: Technical changes