Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Working with Data in Rails [on Russian]

Working with Data in Rails [on Russian]

Best practices for working with ActiveRecord and DB in general and a basic how-to on queries optimisation.

Avatar for Dmitry Koprov

Dmitry Koprov

April 20, 2019

Other Decks in Programming

Transcript

  1. Есть студенты ПММ? Вас отлично учат, ребята Спасибо кафедре ПО

    и АИС И лично всем преподавателям На правах рекламы
  2. User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" =

    $1 LIMIT 1 [["id", 2]] Rendered posts/_post.html.erb (23.2ms) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 3]] Rendered posts/_post.html.erb (15.1ms)
  3. User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" =

    $1 LIMIT 1 [["id", 2]] Rendered posts/_post.html.erb (23.2ms) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 3]] Rendered posts/_post.html.erb (15.1ms) N+1!11!!!
  4. С чего начать Нам потребуется статистика Если у вас PostgreSQL,

    отличный старт: pghero https://github.com/ankane/pghero Также отличный вариант: NewRelic Или его альтернативы (например, ELK*) *ELK — ElasticSearch + Kibana
  5. Основные проблемы Подсчёт элементов .count нужен если и только если

    нужно вывести общее количество записей Неявные N+1 .includes спасает далеко не всегда. Если у вас есть кастомный where, у вас будет N+1
  6. Подсчёт элементов • .count всегда вызывает COUNT в базе •

    COUNT одна из самых дорогих операций в СУБД (postgreSQL) • Если нужно узнать размер коллекции, берём .size # File activerecord/lib/active_record/relation.rb, line 210 def size loaded? ? @records.length : count(:all) end
  7. Подсчёт элементов • Надо узнать есть ли что-то в коллекции:.any?/.none?/.many?

    • .exists? не кеширует результат: N .exists? == N EXIST • Средство от склероза • https://github.com/rails/rails/blob/master/activerecord/lib/active_record/ relation.rb#L259-L292
  8. Подсчёт элементов Если после .size/.any?/.many? проходим по коллекции, надо добавлять

    .load перед предикатом %h2 Messages: = @messages.load.size - @messages.each do |message|
  9. Неявные N+1 class Comment < ActiveRecord::Base belongs_to :post scope :active,

    -> { where(soft_deleted: false) } end - @posts.each do |post| = post.content = render partial: :comment, collection: post.comments.active
  10. Неявные N+1 • Код даст N+1 • Независимо от того,

    что мы писали в контроллере • Выход: именованные ассоциации • includes(:custom_association)
  11. Неявные N+1 - выход из ситуации class Post < ActiveRecord::Base

    belongs_to :author, class_name: 'User' has_many :comments has_many :active_comments, -> { active }, class_name: 'Comment' end class Comment < ActiveRecord::Base belongs_to :post scope :active, -> { where(soft_deleted: false) } end
  12. Неявные N+1 - выход из ситуации class Post < ActiveRecord::Base

    belongs_to :author, class_name: 'User' has_many :comments has_many :active_comments, -> { active }, class_name: 'Comment' end class Comment < ActiveRecord::Base belongs_to :post scope :active, -> { where(soft_deleted: false) } end @posts = Post.where(author: current_user).includes(:active_comments)
  13. Неявные N+1 - выход из ситуации class Post < ActiveRecord::Base

    belongs_to :author, class_name: 'User' has_many :comments has_many :active_comments, -> { active }, class_name: 'Comment' end class Comment < ActiveRecord::Base belongs_to :post scope :active, -> { where(soft_deleted: false) } end @posts = Post.where(author: current_user).includes(:active_comments) - @posts.find_each do |post| = post.content = render partial: :comment, collection: post.active_comments
  14. Если в scope надо выполнить сортировку, «зовите» .reorder scope :custom_scope,

    -> { state_clause.reorder(created_at: :desc) } https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-reorder Общие советы
  15. Также не забывайте о .rewhere и, что более интересно, о

    .merge scope :custom_scope, -> { state_clause.merge(other_scope) } https://api.rubyonrails.org/classes/ActiveRecord/SpawnMethods.html#method-i-merge Общие советы
  16. «Частые и толстые» (по статистике) подзапросы можно выделить в Common

    Table Expressions a.k.a. CTE http://www.scimedsolutions.com/articles/75-arel-part-ii-common-table-expressions Общие советы
  17. Общие советы Arel, CTE и find_by_sql грозят превратить модель в

    сборник стихов на SQL? Используйте паттерн Repository https://www.monterail.com/blog/repository-pattern-active-record
  18. Общие советы Если ничего из вышеперечисленного не помогает, вам нужна

    readonly replica Подойтёт octopus gem, или… Rails 6! https://github.com/thiagopradi/octopus https://github.com/rails/rails/pull/34052