From ea41405209a2b6aa4c89ee80c12b642f224d516a Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Mon, 16 Sep 2019 13:03:17 +0200 Subject: [PATCH] Index spree_orders on various columns 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 ``` --- ...0916105416_add_long_compound_index_on_spree_orders.rb | 9 +++++++++ ...0916110029_drop_completed_at_index_on_spree_orders.rb | 5 +++++ db/schema.rb | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20190916105416_add_long_compound_index_on_spree_orders.rb create mode 100644 db/migrate/20190916110029_drop_completed_at_index_on_spree_orders.rb diff --git a/db/migrate/20190916105416_add_long_compound_index_on_spree_orders.rb b/db/migrate/20190916105416_add_long_compound_index_on_spree_orders.rb new file mode 100644 index 0000000000..5ff04483ec --- /dev/null +++ b/db/migrate/20190916105416_add_long_compound_index_on_spree_orders.rb @@ -0,0 +1,9 @@ +class AddLongCompoundIndexOnSpreeOrders < ActiveRecord::Migration + def change + add_index( + :spree_orders, + [:completed_at, :user_id, :created_by_id, :created_at], + name: 'spree_orders_completed_at_user_id_created_by_id_created_at_idx' + ) + end +end diff --git a/db/migrate/20190916110029_drop_completed_at_index_on_spree_orders.rb b/db/migrate/20190916110029_drop_completed_at_index_on_spree_orders.rb new file mode 100644 index 0000000000..47b5517295 --- /dev/null +++ b/db/migrate/20190916110029_drop_completed_at_index_on_spree_orders.rb @@ -0,0 +1,5 @@ +class DropCompletedAtIndexOnSpreeOrders < ActiveRecord::Migration + def change + remove_index :spree_orders, :completed_at + end +end diff --git a/db/schema.rb b/db/schema.rb index 81684ade4c..99f98e7c59 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20190913023137) do +ActiveRecord::Schema.define(:version => 20190916110029) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -576,7 +576,7 @@ ActiveRecord::Schema.define(:version => 20190913023137) do t.integer "created_by_id" end - add_index "spree_orders", ["completed_at"], :name => "index_spree_orders_on_completed_at" + add_index "spree_orders", ["completed_at", "user_id", "created_by_id", "created_at"], :name => "spree_orders_completed_at_user_id_created_by_id_created_at_idx" add_index "spree_orders", ["customer_id"], :name => "index_spree_orders_on_customer_id" add_index "spree_orders", ["number"], :name => "index_orders_on_number"