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.
It's much clearer to read this way:
```
== OldMigrationsRemoved: migrating ===========================================
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
You haven't updated your dev environment in a long time!
Legacy migration files before 2019 have now been removed.
Run `rails db:schema:load` before running `rails db:migrate`.
```
A migration's `up` method is only run when the migration needs to be
applied. The only case we could have a higher version number is when a
migration with a higher version got merged before the current one. And
in that case, we still want this migration to fail, because it hasn't
been applied yet.
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
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
```