Оптимизируйте заполнение кэша счетчиков рельсов

#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