В съвременната уеб разработка кеширането е бърз и мощен начин за ускоряване на нещата. Когато се извърши правилно, кеширането може да донесе значителни подобрения в цялостната ефективност на приложението ви. Когато се направи погрешно, това определено ще завърши с катастрофа.
Анулирането на кеш паметта, както може би знаете, е един от трите най-трудни проблема в компютърните науки - другите два са назоваването на нещата и грешките, причинени един от друг. Един лесен изход е да обезсилите всичко, отляво и отдясно, винаги когато нещо се промени. Но това побеждава целта на кеширането. Искате да дезактивирате кеша само когато е абсолютно необходимо.
Ако искате да се възползвате максимално от кеширането, трябва да сте много конкретни относно това, което обезсилвате и да запазите приложението си от загуба на ценни ресурси за многократна работа.
В тази публикация в блога ще научите техника за по-добър контрол върху поведението на кешовете на Rails: по-специално, прилагане на обезсилване на кеш на ниво поле. Тази техника разчита Релси ActiveRecord и ActiveSupport::Concern
както и манипулация на touch
поведение на метода.
Тази публикация в блога се основава на моята скорошна опитност в проект, при който видяхме значително подобрение в производителността след внедряване на обезсилване на кеш на ниво поле. Той помогна за намаляване на ненужните обезсилвания на кеша и многократното изобразяване на шаблони.
Ruby не е най-бързият език, но като цяло е подходящ вариант, когато става дума за скорост на развитие. Освен това си метапрограмиране и вградените възможности за специфичен за домейна език (DSL) дават на разработчика огромна гъвкавост.
Има проучвания като Изследване на Якоб Нилсен които ни показват, че ако дадена задача отнеме повече от 10 секунди, ще загубим фокуса си. И възвръщането на фокуса ни отнема време. Така че това може да бъде неочаквано скъпо.
За съжаление, в Ruby on Rails е много лесно да надхвърлите този 10-секунден праг с генерирането на шаблон. Няма да видите това да се случи в нито едно приложение „здравей свят“ или малък проект за домашни любимци, но в реални проекти, където много неща се зареждат на една страница, повярвайте ми, генерирането на шаблони може много лесно да започне да се влачи.
И точно това трябваше да реша в моя проект.
Но как точно ускорявате нещата?
Отговорът: Бенчмарк и оптимизиране.
В моя проект две много ефективни стъпки за оптимизация бяха:
Поправяне N + 1 заявки лесно е. Това, което можете да направите, е да проверите вашите регистрационни файлове - всеки път, когато видите множество SQL заявки като тези по-долу във вашите дневници, премахнете ги, като ги замените с нетърпеливо зареждане:
Learning Load (0.4ms) SELECT 'learnings'.* FROM 'learnings' WHERE 'project'.'id' = ? Learning Load (0.3ms) SELECT 'learnings'.* FROM 'learnings' WHERE 'project'.'id' = ? Learning Load (0.4ms) SELECT 'learnings'.* FROM 'learnings' WHERE 'project'.'id' = ?
За това има скъпоценен камък, който се нарича куршум за да помогне за откриването на тази неефективност. Можете също така да преминете през всеки от случаите на използване и междувременно да проверите регистрационните файлове, като ги проверите по горния модел. Като премахнете всички N + 1 неефективност, можете да бъдете достатъчно уверени, че няма да претоварите вашата база данни и времето, прекарано в ActiveRecord, ще намалее значително.
След като направих тези промени, проектът ми вече работи по-оживено. Но реших да го пренеса на следващото ниво и да видя дали мога да намаля времето за зареждане още повече. Все още имаше доста ненужно изобразяване в шаблоните и в крайна сметка това помогна кеширането на фрагменти.
Кеширането на фрагменти обикновено помага значително да се намали времето за генериране на шаблон. Но поведението на кеша на Rails по подразбиране не го намаляваше за моя проект.
Идеята зад кеширането на фрагменти от Rails е брилянтна. Той осигурява супер прост и ефективен кеширащ механизъм.
Авторите на Ruby On Rails са написали много добра статия в Signal v. Noise за това как работи кеширане на фрагменти .
Да кажем, че имате малко потребителски интерфейс, който показва някои полета на обект.
cache_key
въз основа на класа на обекта и updated_at
поле.cache_key
, той проверява дали има нещо в кеша, свързано с този ключ.Това предполага, че кешът никога не трябва да бъде обезсилен изрично. Винаги, когато променяме обекта и презареждаме страницата, за обекта се изобразява ново съдържание в кеша.
Rails по подразбиране предлага и възможност за обезсилване на кеша на родителските обекти в случай, че детето се промени:
belongs_to :parent_entity, touch: true
Това, когато е включено в даден модел, ще бъде автоматично докосване родителят, когато детето е докоснат . Можете да научите повече за touch
тук . С това Rails ни предоставя прост и ефективен начин да обезсилим кеша за нашите родителски обекти едновременно с кеша за дъщерните обекти.
Кеширането в Rails обаче е създадено за обслужване на потребителски интерфейси, където HTML фрагментът, представляващ родителския обект, съдържа HTML фрагменти, представляващи само дъщерните обекти на родителя. С други думи, HTML фрагментът, представляващ дъщерните обекти в тази парадигма, не може да съдържа полета от родителския обект.
Но не това се случва в реалния свят. Може много добре да се наложи да направите неща в приложението си Rails, които нарушават това условие.
Как бихте се справили със ситуация, при която потребителският интерфейс показва полета на родителски обект вътре в HTML фрагмента, представляващ дъщерния обект?
Ако детето съдържа полета от родителския обект, значи имате проблеми с поведението за невалидност на кеша на Rails.
Всеки път, когато тези полета, представени от родителския обект, бъдат променени, ще трябва да докоснете всички дъщерни обекти, принадлежащи на този родител. Например, ако Parent1
е модифициран, ще трябва да се уверите, че кешът за Child1
и Child2
изгледите са обезсилени.
Очевидно това може да доведе до огромно затруднение в работата. Докосването на всяка дъщерна същност, когато родителят се е променил, би довело до много заявки към база данни без основателна причина.
Друг подобен сценарий е, когато обектите, свързани с has_and_belongs_to
асоциация бяха представени в списъка и модифицирането на тези обекти започна каскада от обезсилване на кеша чрез веригата на асоцииране.
class Event Така че, за горния потребителски интерфейс, би било нелогично да се докосва участникът или събитието, когато местоположението на потребителя се промени. Но трябва да докоснем събитието и участника, когато името на потребителя се промени, нали?
Така че техниките в статията Signal v. Noise са неефективни за определени екземпляри на UI / UX, както е описано по-горе.
Въпреки че Rails е супер ефективен за прости неща, реалните проекти имат свои собствени усложнения.
Невалидност на кеша на ниво ниво на релси
В моите проекти използвам малък Ruby DSL за справяне със ситуации като горепосочените. Тя ви позволява да декларирате декларативно полетата, които ще задействат обезсилване на кеша чрез асоциациите.
Нека да разгледаме няколко примера за това къде наистина помага:
Пример 1:
class Event Този фрагмент използва способностите за метапрограмиране и вътрешните DSL възможности на Ruby .
За да бъдем по-конкретни, само промяна на име в събитието ще обезсили кеша на фрагменти от свързаните с него задачи. Промяната на други полета на събитието - като цел или местоположение - няма да обезсили кеша на фрагментите на задачата. Бих нарекъл това контрол на обезсилено кеширане на кеш на ниво поле .

Пример 2:
Нека да разгледаме пример, който показва обезсилване на кеша чрез has_many
асоциативна верига.
Показаният по-долу фрагмент на потребителския интерфейс показва задача и нейния собственик:

За този потребителски интерфейс HTML фрагментът, представляващ задачата, трябва да бъде обезсилен само когато задачата се промени или когато името на собственика се промени. Ако всички други полета на собственика (като часовата зона или предпочитанията) се променят, кешът на задачите трябва да остане непокътнат.
Това се постига с помощта на DSL, показан тук:
class User Прилагане на DSL
Основната същност на DSL е touch
метод. Първият му аргумент е асоциация, а следващият аргумент е списък с полета, който задейства touch
относно тази асоциация:
touch :tasks, in_case_of_modified_fields: [:first_name, :last_name]
Този метод се предоставя от Touchable
модул:
module Touchable extend ActiveSupport::Concern included do before_save :check_touchable_entities after_save :touch_marked_entities end module ClassMethods def touch association, options @touchable_associations ||= {} @touchable_associations[association] = options end end end
В този код основният момент е, че съхраняваме аргументите на touch
обадете се. След това, преди да запазим обекта, маркираме асоциацията мръсна, ако посоченото поле е модифицирано. Докосваме обектите в тази асоциация, след като запазваме, ако асоциацията е мръсна.
Тогава частната част на концерна е:
... private def klass_level_meta_info self.class.instance_variable_get('@touchable_associations') end def meta_info @meta_info ||= {} end def check_touchable_entities return unless klass_level_meta_info.present? klass_level_meta_info.each_pair do |association, change_triggering_fields| if any_of_the_declared_field_changed?(change_triggering_fields) meta_info[association] = true end end end def any_of_the_declared_field_changed?(options) (options[:in_case_of_modified_fields] & changes.keys.map).present? end …
В check_touchable_entities
метод, проверяваме ако декларираното поле се промени . Ако е така, ние отбелязваме асоциацията като мръсна, като задаваме meta_info[association]
до true
.
След това, след като запазим обекта, проверяваме нашия мръсни асоциации и докоснете обектите в него, ако е необходимо:
… def touch_marked_entities return unless klass_level_meta_info.present? klass_level_meta_info.each_key do |association_key| if meta_info[association_key] association = send(association_key) association.update_all(updated_at: Time.zone.now) meta_info[association_key] = false end end end …
И това е! Сега можете да извършите анулиране на кеш на ниво поле в Rails с прост DSL.
Заключение
Кеширането на релси обещава подобрения на производителността във вашето приложение с относителна лекота. Приложенията от реалния свят обаче могат да бъдат сложни и често представляват уникални предизвикателства. Поведението по подразбиране на кеша на Rails работи добре за повечето сценарии, но има определени сценарии, при които малко повече оптимизация при обезсилване на кеша може да продължи много.
Сега, след като знаете как да приложите обезсилване на кеш на ниво поле в Rails, можете да предотвратите ненужни обезсилвания на кешове във вашето приложение.
Разбиране на основите
Какво означава DSL?
DSL означава „език, специфичен за домейна“.
Какво прави методът ActiveRecord touch?
Методът на докосване задава полето updated_at / on на текущото време и записва записа.