This speeds up a great deal one of the most awful queries our DB servers
execute. It's not rare to see traces above 20s in Datadog 😱.
In staging, with no traffic, we go from
```
EXPLAIN ANALYZE SELECT COUNT ( * )
FROM spree_state_changes
WHERE spree_state_changes . stateful_id = 2024
AND spree_state_changes . stateful_type = 'Spree::Order';
Planning time: 0.142 ms
Execution time: 9.073 ms
```
to
```
EXPLAIN ANALYZE SELECT COUNT ( * )
FROM spree_state_changes
WHERE spree_state_changes . stateful_id = 2024
AND spree_state_changes . stateful_type = 'Spree::Order';
Planning time: 0.284 ms
Execution time: 0.202 ms
```
We realized in Spree v2.1 they follow this format instead and this is
what's causing issues to Katuma production.
This will remove the duplicate ones and convert the current preferences
to the new thus, keeping the values.
This is critical to debug bugs related to subscriptions.
Essentially, `has_and_belongs_to_many` doesn't give us the option for
any other column that the foreign keys themselves:
> A has_and_belongs_to_many association creates a direct many-to-many
> connection with another model, with no intervening model.
Source: https://guides.rubyonrails.org/v3.2/association_basics.html#the-has_and_belongs_to_many-association
Note however, that there's no way to update an order_cycle_schedule,
that I can think of but `updated_at` doesn't do any harm.
Activate paper_trail in order_cycles and schedules and track each others ids
An alternative way of doing this would be to use a gem for paper_trail associations but this way we avoid adding a new dependency to the app
It looks like this was probably changed whilst resolving a merge conflict somewhere. The number doesn't match the last migration file, and it's breaking the ofn-install CI build (as well as migrations on fresh servers).
The following query
```sql
SELECT spree_orders . *
FROM spree_orders
WHERE spree_orders . user_id = ?
AND spree_orders . completed_at IS ?
AND spree_orders . created_by_id = ?
ORDER BY created_at DESC LIMIT ?
```
performs quite badly even though LIMIT is always 1 because:
* ORDER BY requires sorting by a column which is not indexed therefore
a sequential scan is performed.
* Although `completed_at` is indexed, `user_id` and `created_by_id` are
not causing a sequential scan.
To make it worse this query is executed very often in the following
controllers among others also related to checkout:
* CartController#populate
* EnterprisesController#Shop
* LineItemsController#bought
* ShopController#products
* ShopController#order_cycle
In some cases this query alone accounts for 66.8% of the total time
of the endpoint.
Results
See by yourself. We move from 56.643ms to 0.077ms. Pretty neat.
```
openfoodnetwork=> explain analyze SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."user_id" = 1 AND "spree_orders"."completed_at" IS NULL AND "spree_orders"."created_by_id" = 1 ORDER BY created_at DESC LIMIT 1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Limit (cost=11753.03..11753.04 rows=1 width=195) (actual time=56.580..56.580 rows=0 loops=1)
-> Sort (cost=11753.03..11753.04 rows=1 width=195) (actual time=56.578..56.578 rows=0 loops=1)
Sort Key: created_at DESC
Sort Method: quicksort Memory: 25kB
-> Seq Scan on spree_orders (cost=0.00..11753.02 rows=1 width=195) (actual time=56.571..56.571 rows=0 loops=1)
Filter: ((completed_at IS NULL) AND (user_id = 1) AND (created_by_id = 1))
Rows Removed by Filter: 256135
Planning time: 0.252 ms
Execution time: 56.643 ms
(9 rows)
openfoodnetwork=> CREATE INDEX ON spree_orders (completed_at, user_id, created_by_id, created_at);
CREATE INDEX
openfoodnetwork=> explain analyze SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."user_id" = 1 AND "spree_orders"."completed_at" IS NULL AND "spree_orders"."created_by_id" = 1 ORDER BY created_at DESC LIMIT 1;
mit (cost=8.45..8.46 rows=1 width=195) (actual time=0.030..0.030 rows=0 loops=1)
-> Sort (cost=8.45..8.46 rows=1 width=195) (actual time=0.029..0.029 rows=0 loops=1)
Sort Key: created_at DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using spree_orders_completed_at_user_id_created_by_id_created_at_idx on spree_orders (cost=0.42..8.44 rows=1 width=195) (actual time=0.021..0.021 rows=0 loops=1)
Index Cond: ((completed_at IS NULL) AND (user_id = 1) AND (created_by_id = 1))
Planning time: 0.199 ms
Execution time: 0.077 ms
```
This is required because when the default stock location is created, the backorderable_default column doesnt exist and when this column is created, the initial default is true. This is why we need to force it to false here. This column is the default value for on_demand which must be false.