Uvod u memcached i cache_fu
Šta je memcached?
Memcached je besplatan distributivni sistem za keširanje objekata u memoriji, otvorenog kôda i visokih performansi. Po sistemu ključ-vrednost, u njemu se mogu keširati rezultati poziva baze podataka, API-ja, renderovanja stranice….
Šta je cache_fu?
cache_fu je Rails plugin za keširanje ActiveRecord objekata koji koristi memcached. Cache_fu je pre nekoliko godina napravio Chris Wanstrath (koautor GitHuba)
Čemu sve ovo?
Kada je reč o performansama, poznato je da je za većinu web aplikacija najveće usko grlo baza podataka. Zato je potrebno minimalizovati komunikaciju aplikacije i baze što je više moguće. Cache_fu je jedan od načina da se to realizuje.
Šta treba da znam o memcachedu da bih počeo da koristim cache_fu?
- Posle instalacije, opcije za startovanje su opisane pozivom: memcached -h. Ovo je uglavnom dovoljno: memcached -d -m 512
- Kada se “ubije” memcached, sav keš nestaje
- Postoji ograničenje od 1MB za vrednost, po ključu (ovo uglavnom ne treba da vas brine)
Kako se koristi?
SAVET: podesite logovanje u konzoli kako biste mogli mogli da posmatrate AR/cache_fu interakciju sa memcachedom i bazom kada budete isprobavali kôd iz primera u Rails konzoli. Neki od primera koji slede imaju prikazan upravo takav izlaz.
Kôd prikazan u primerima možete pronaći na githubu.
cache_fu API (sastavljen od svega nekoliko metoda) se metodom acts_as_cached ubacuje u željenu klasu.
class Article < ActiveRecord::Base
acts_as_cached
Počnimo od keširanja ActiveRecord#count metode. Prebrojavanje redova u bazi je skupa operacija čak i sa srednje velikim tabelama (50K+ redova). Setite se da se prebrojavanje uvek izvršava prilikom paginacije. Railsov plugin will_paginate podržava :total_entries opciju preko koje je moguće proslediti (keširanu) vrednost broja redova u tabeli.
>> Article.cached(:count)
==> Got Article:count from cache. (0.00048)
SQL (0.5ms) SELECT count(*) AS count_all FROM "articles"
==> Set Article:count to cache. (0.00056)
=> 4
Svaki sledeći poziv će “zaobići” bazu
>> Article.cached(:count)
==> Got Article:count from cache. (0.00038)
=> 4
Keš se invalidira expire_cache metodom
>> Article.expire_cache(:count)
==> Deleted Article:count from cache. (0.00041)
=> true
after_create/destroy se može iskoristi za expire_cache(:count) poziv. Takođe moguće je iskoristiti :ttl (time-to-live) opciju prilikom keširanja. To će automatski invalidirati taj keš svakih 15 minuta.
>> Article.cached(:count, :ttl => 15.minutes)
Metodom cached je moguće keširati rezultat svake metode klase/instance ActiveRecord objekta. Parametri metoda se prosleđuju koristeći :with.
>> Article.cached(:random, :with => 1, :ttl => 5.minutes)
==> Got Article:random:1 from cache. (0.10194)
Article Load (0.9ms) SELECT * FROM "articles" ORDER BY random() LIMIT 1
==> Set Article:random:1 to cache. (0.00397)
=> [#<Article id: 6, title: "Haml tips", body: "...",...>]
Jedan izuzetak je metoda find kojoj se prosleđuje ID. I ona se može keširati na isti način ali postoji prečica koja pruža i automatsko invalidiranje keša.
>> Article.get_cache(1)
==> Got Article:1 from cache. (0.00034)
Article Load (0.4ms) SELECT * FROM "articles" WHERE ("articles"."id" = 1)
==> Set Article:1 to cache. (0.00065)
=> #<Article id: 1, title: "Rails console tips", body: "...", ....>
Da bi ovakav keš automatski bio invalidiran:
class Article < ActiveRecord::Base
acts_as_cached
after_save :expire_cache # postoji default implementacija
Relacije se keširaju takođe sa cached metodom
>> Article.find(1).cached(:comments)
Article Load (0.5ms) SELECT * FROM "articles" WHERE ("articles"."id" = 1)
==> Got Article:1:comments from cache. (0.00033)
==> Set Article:1:comments to cache. (0.00451)
=> [#<Comment id: 1, article_id: 1, ...>, #<Comment id: 2, article_id: 1, ...>, #<Comment id: 3, article_id: 1, ....>]
Naravno, kod relacija je potrebno malo dodatne invalidacije:
class Comment < ActiveRecord::Base
acts_as_cached
belongs_to :article
after_save :expire_cache
after_destroy :expire_cache
def expire_cache
super
article.expire_cache(:comments)
end
# ... (probajte da kreirate neki komentar
# za neki članak i posmatrajte log) ...
Za kraj, evo primera kada je potrebno keširati nešto što ima neki vremenski okvir (npr. “Današnji članci”).
class Article
acts_as_cached
after_save do |record|
expire_cache_by_date(record.created_at.to_date)
end
after_destroy do |record|
expire_cache_by_date(record.created_at.to_date)
end
def self.expire_cache_by_date(date)
expire_cache("on_date:#{date}")
end
def self.on_date(date)
find(:all, :conditions => ['date(created_at) = ?', date.to_date])
end
def self.cached_todays
# ubaciće datum u ključ keša
# (npr. "Article:on_date:2010-01-30").
# na taj način izbegnuto je eksplicitno
# invalidiranje keša nakon ponoći.
cached(:on_date, :with => Date.today)
end
Linkovi