#ruby-on-rails #query-optimization #ruby-on-rails-6 #postgresql-13 #counter-cache
Вопрос:
Я только что добавил кэш счетчиков к отношениям между подписчиками и их подписками.
class Subscription < ApplicationRecord
belongs_to :subscriber,
validate: true,
# We need to populate it before it is accurate, so don't use it yet.
counter_cache: :_invalid_subscriptions_count
Его цель-эффективно определить, является ли это первая Подписка Подписчика.
class Subscriber < ApplicationRecord
...
def first_time_subscriber?
subscriptions.size <= 1
end
Я написал задачу, чтобы заполнить его партиями по 1000, чтобы избежать блокировки записей. Существует начальный большой блок подписчиков без подписок, поэтому они пропускаются.
desc "Fill in the all_subscriptions counter cache"
task cache_all_subscriptions_count: :environment do
starting_id = ENV["starting_id"] || Subscription.minimum(:subscriber_id)
Subscriber.where("id >= :id", {id: starting_id}).order(id: :asc).in_batches.each do |batch|
ids = batch.pluck(:id)
print "Batch IDs #{ids.first} - #{ids.last}..."
sql = <<~'SQL'
update
subscribers s
set _invalid_subscriptions_count = (
select
count(subscriptions.id)
from subscriptions
where subscriber_id = s.id
)
where s.id between $1 and $2
SQL
ApplicationRecord.connection.update(
sql,
"cache_all_subscriptions_count",
[[nil, ids.first], [nil, ids.last]]
)
puts "done."
end
end
К сожалению, требуется перенести большой объем данных. Около 30 миллионов Подписчиков с 50 миллионами подписок. Тем не менее, он работает намного медленнее, чем ожидалось. Он обрабатывает только около 10 в секунду. При таких темпах это займет три недели.
Как я могу это ускорить? Либо оптимизировав мой запрос, либо изменив мой план миграции.
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Update on subscribers s (cost=0.11..35564.17 rows=779 width=130)
-> Index Scan using subscribers_pkey on subscribers s (cost=0.11..35564.17 rows=779 width=130)
Index Cond: ((id >= 13828893) AND (id <= 13829892))
SubPlan 1
-> Aggregate (cost=44.04..44.04 rows=1 width=8)
-> Index Scan using index_subscriptions_on_subscriber_id on subscriptions (cost=0.11..44.03 rows=21 width=8)
Index Cond: (subscriber_id = s.id)
=> d subscribers
Table "public.subscribers"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------------------------- ----------------------------- ----------- ---------- ----------------------------------------- ---------- -------------- -------------
id | bigint | | not null | nextval('subscribers_id_seq'::regclass) | plain | |
_invalid_subscriptions_count | integer | | | | plain | |
...
Indexes:
"subscribers_pkey" PRIMARY KEY, btree (id)
...
Referenced by:
TABLE "subscriptions" CONSTRAINT "fk_rails_af293f646f" FOREIGN KEY (subscriber_id) REFERENCES subscribers(id)
...
Access method: heap
Options: autovacuum_vacuum_scale_factor=0, autovacuum_analyze_scale_factor=0, autovacuum_vacuum_threshold=100000, autovacuum_analyze_threshold=100000
=> d subscriptions
Table "public.subscriptions"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
------------------------ ----------------------------- ----------- ---------- ------------------------------------------- ---------- -------------- -------------
id | bigint | | not null | nextval('subscriptions_id_seq'::regclass) | plain | |
subscriber_id | bigint | | not null | | plain | |
...
Indexes:
"subscriptions_pkey" PRIMARY KEY, btree (id)
"index_subscriptions_on_subscriber_id" btree (subscriber_id)
...
Foreign-key constraints:
"fk_rails_af293f646f" FOREIGN KEY (subscriber_id) REFERENCES subscribers(id)
...
Referenced by:
...
Access method: heap
Options: autovacuum_vacuum_scale_factor=0, autovacuum_analyze_scale_factor=0, autovacuum_vacuum_threshold=100000, autovacuum_analyze_threshold=100000