Rails API заявките са изключително бавни -› 300+ сек

Приложението ми изведнъж стана много бавно с време за реакция, което може да достигне до 3 минути. Опитах в две различни локални среди, за да се уверя, че не се забавя от CPU/RAM или друг хардуерен компонент, но проблемът остава.

Някои контексти

Използвам ruby 2.0.0 и rails 4.0.0. Pow като стелажен сървър и база данни PostgreSQL. Заявката, използвана за този тест, е основно извикване на API за извличане на всички записи в таблица entries. Самата таблица няма много записи (~12k).

Заявка за API на пощальон

И ето какво показва дневникът за тази заявка:

Started GET "/en/*****/api/v1/entries.json" for 127.0.0.1 at 2014-09-11 11:42:13 +0100
  ActiveRecord::SchemaMigration Load (0.7ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Api::V1::EntriesController#index as JSON
  Parameters: {"subdomain"=>"cms", "not"=>[:new, :create, :edit, :update, :destroy], "locale"=>"en", "tenant"=>"*****"}
...
...
...
...
Completed 200 OK in 309341ms (Views: 307471.4ms | ActiveRecord: 1620.3ms)

Да, прочетохте добре... 299 секунди за изобразяване на изгледа.

Действие за влизане

Api::V1::EntriesController#index е доста прост

  def index  
    # Set Status
    @status = params[:status].present? ? status_id("#{params[:status]}") : [0..3]

    # Set Promoted
    @promoted = params[:promoted].present? ? params[:promoted] : [true,false]

    # Fetch Entries   
    @entries = Entry.where(status_id: @status, promoted: @promoted).filter_by_tag(params[:tag]).order("published_at DESC").paginate( page: params[:page], per_page: params[:limit] )
    respond_with @entries
  end

Преглед на нова реликва

Подробности за нови реликви

И накрая SQL отчетите:

Нови отчети Relic SQL

Както можете да видите по-горе, продължителността и времето на SQL заявките не съвпадат. Въпреки че заявките не отнемат много време, времето се увеличава много бързо (до 300 секунди в края). Ще ви спестя подробностите, но дългият списък от заявки (благодарение на acts-as-tagable-on) не показва никакви странни или дълги неочаквани заявки. Най-дългата заявка отнема 300 ms.

развитие.рб

  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports and disable caching.
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = true

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations
  config.active_record.migration_error = :page_load

  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true

  # Do not compress assets
  config.assets.compress = false

  # Disable color for log file
  config.colorize_logging = false

Журналите на Pow и Rails не показват нищо нередно. Прескочих .dev в настройките на прокси сървъра на моя Ethernet адаптер.

Някаква идея какво може да причини толкова много забавяне на приложението ми?

Редактиране #1

След отговора на apneadiving премахнах всички асоциации, които биха могли да забавят моята заявка.

Вече е опростено изобразяването на прост JSON масив от идентификатори (~300 записа).

/app/controllers/api/v1/entries_controller.rb

def index

  # Set Status
  @status = params[:status].present? ? status_id("#{params[:status]}") : [0..3]

  # Set Promoted
  @promoted = params[:promoted].present? ? params[:promoted] : [true,false]

  # Fetch entries
  @entries = Entry.where(status_id: @status, promoted: @promoted)
  respond_with @entries
end

/app/serializers/entry_serializer.rb

class EntrySerializer < ActiveModel::Serializer
  attributes :id
end

Резултатът е повече от 3 секунди за толкова проста заявка...

Started GET "/en/*****/api/v1/entries.json?limit=2" for 127.0.0.1 at 2014-09-12 10:07:10 +0100
Processing by Api::V1::EntriesController#index as JSON
  Parameters: {"limit"=>"2", "subdomain"=>"cms", "not"=>[:new, :create, :edit, :update, :destroy], "locale"=>"en", "tenant"=>"*****"}
  Account Load (1.3ms)  SELECT "accounts".* FROM "accounts" WHERE "accounts"."domain" = '******' ORDER BY "accounts"."id" ASC LIMIT 1
   (0.6ms)  BEGIN
  SQL (2.2ms)  INSERT INTO "sessions" ("cookie", "created_at", "domain", "locale", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["cookie", "Dn/aXXbbWG8t8A5ZYGVgsQ=="], ["created_at", Fri, 12 Sep 2014 09:07:11 UTC +00:00], ["domain", "*******"], ["locale", :en], ["updated_at", Fri, 12 Sep 2014 09:07:11 UTC +00:00]]
   (0.6ms)  COMMIT
  ApiKey Load (0.6ms)  SELECT "api_keys".* FROM "api_keys" WHERE "api_keys"."account_id" = 2 AND "api_keys"."access_token" IS NULL LIMIT 1
  ApiKey Load (0.6ms)  SELECT "api_keys".* FROM "api_keys" WHERE "api_keys"."account_id" = 2 AND "api_keys"."access_token" = '****************' LIMIT 1
  Account Load (0.8ms)  SELECT "accounts".* FROM "accounts" WHERE "accounts"."domain" = '*****' ORDER BY "accounts"."id" ASC LIMIT 1
  SQL (2.6ms)  UPDATE "api_keys" SET "count" = COALESCE("count", 0) + 1 WHERE "api_keys"."account_id" = 2 AND "api_keys"."id" = 2
  Entry Load (4.9ms)  SELECT "entries".* FROM "entries" WHERE "entries"."account_id" = 2 AND "entries"."promoted" IN ('t', 'f') AND (("entries"."status_id" BETWEEN 0 AND 3 OR 1=0))
Completed 200 OK in 3558ms (Views: 3246.6ms | ActiveRecord: 27.7ms)

Индекси на таблицата с записи:

# \d entries
                                               Table "public.entries"
      Column       |            Type             |                             Modifiers                             
-------------------+-----------------------------+-------------------------------------------------------------------
 id                | integer                     | not null default nextval('entries_id_seq'::regclass)
 title             | character varying(255)      | 
 slug              | character varying(255)      | 
 status_id         | integer                     | 
 promoted          | boolean                     | 
 published_at      | timestamp without time zone | default '2014-07-31 15:06:20.462154'::timestamp without time zone
 created_at        | timestamp without time zone | 
 updated_at        | timestamp without time zone | 
 account_id        | integer                     | 
 excerpt           | text                        | 
 user_id           | uuid                        | 
 extra             | hstore                      | 
 entry_views_count | integer                     | 
Indexes:
    "entries_pkey" PRIMARY KEY, btree (id)
    "index_entries_on_slug" UNIQUE, btree (slug)
    "entries_title" gin (to_tsvector('english'::regconfig, title::text))
    "index_entries_on_account_id" btree (account_id)
    "index_entries_on_promoted" btree (promoted)
    "index_entries_on_status_id" btree (status_id)
    "index_entries_on_user_id" btree (user_id)

person Liyali    schedule 11.09.2014    source източник
comment
трябва да навлезете дълбоко в заявката си, но тя трябва да е твърде сложна и без адаптирани индекси. Помислете поне за използване на кеширане   -  person apneadiving    schedule 11.09.2014
comment
Но изглежда, че проблемът не идва от заявките (Views: 307471.4ms | ActiveRecord: 1620.3ms). Някаква идея какво може да причини такава разлика между Продължителност и Кампо за време в изображението на отчетите на New Relic SQL?   -  person Liyali    schedule 11.09.2014
comment
добре, поради мързеливото зареждане изглежда, че изгледите са отговорни за бавността, но не са. За да сте сигурни, просто добавете .to_a в края на вашето запитване. Ще се изпълни веднага в контролера, не повече в изгледа   -  person apneadiving    schedule 11.09.2014
comment
добра точка. замяна на Entry.where(status_id: @status, promoted: @promoted).filter_by_tag(params[:tag]).order("published_at DESC").paginate( page: params[:page], per_page: params[:limit] ) с Entry.where(status_id: @status, promoted: @promoted).filter_by_tag(params[:tag]).order("published_at DESC").paginate( page: params[:page], per_page: params[:limit] ).to_a Получих Изпълнено 200 OK за 102830ms (Прегледи: 101043.8ms | ActiveRecord: 629.4ms)   -  person Liyali    schedule 11.09.2014
comment
интересно, как генерирате своя json   -  person apneadiving    schedule 11.09.2014
comment
Разгледайте този -› stackoverflow.com/questions/10451722/   -  person usha    schedule 11.09.2014
comment
@apneadiving с помощта на сериализатор на ActiveModel. В него няма нищо фантастично, което да забави приложението, доколкото виждам.   -  person Liyali    schedule 11.09.2014
comment
@Vimsha благодаря! Не само се опитвам да подобря производителността, но и да проследя грешка, която значително забавя приложението ми (300 000 ms...)   -  person Liyali    schedule 11.09.2014
comment
Добре, така че нещо, което може да навреди сериозно на изгледите в N+1 заявки, наричате ли асоциации?   -  person apneadiving    schedule 11.09.2014
comment
да, извиквам една has_many асоциация в сериализатора. Нека се опитам да го премахна. Все пак не съм правил промени в този файл от векове.   -  person Liyali    schedule 11.09.2014


Отговори (1)


Трябва да заредите възможно най-много данни, за да предотвратите случването на N+1:

entries = Entry.where(status_id: @status, promoted: @promoted).filter_by_tag(params[:tag])
entries = entries.includes(:entry_fields).order("published_at DESC")
entries = entries.paginate( page: params[:page], per_page: params[:limit] )

includes документът е тук

person apneadiving    schedule 11.09.2014
comment
Благодаря @apneadiving. Предполагам, че трябва да добавя def entry_fields object.entry_fields end и нов attributes :entry_fields в моя сериализатор? след като направите това, страницата се зарежда по-бързо, но все още отнема 12 секунди само за 350 записа. - person Liyali; 11.09.2014
comment
защо не запазихте has_many в сериализатора? мислиш ли, че пропуска нетърпеливото зареждане? - person apneadiving; 11.09.2014
comment
просто опитах с has_many :entry_fields вместо това и получих същите резултати при зареждане :\ - person Liyali; 11.09.2014
comment
странно, че трябва да работи: stackoverflow. com/questions/18134649/ - person apneadiving; 11.09.2014
comment
Благодаря! Току-що редактирах първоначалния си въпрос, за да добавя някои подробности (раздел Редактиране). - person Liyali; 12.09.2014
comment
Предполагам, че трябва да оптимизирате вашата заявка SELECT "entries".* FROM "entries" WHERE "entries"."account_id" = 2 AND "entries"."promoted" IN ('t', 'f') AND (("entries"."status_id" BETWEEN 0 AND 3 OR 1=0)) с индекси - person apneadiving; 12.09.2014
comment
Току-що добавих своя списък с индекси към първоначалния въпрос. - person Liyali; 12.09.2014