What have you found for these years?

2008-12-26

cache

cache 真的是一個相當具有有挑戰性的題目啊... 講好聽一點是這樣。
講難聽一點就是隨隨便便就會有很大的挫折感 @@
日前一直在寫 image server 的 cache, 本來是計畫有兩種 cache,
第一種是 uri => body 的 cache. 這應該很單純,不過我也不知道
是不是有誰的可以直接拿來用?

我想 cache 與 optimization 都有一個很大的原則,
除了要測要測要測以外,太過賣弄技巧的方法往往不會有很大的效果,
甚至會有反效果,反而變慢。而太過於簡單的方法,也往往成效不大。
也就是說,其實 cache 一方面要賣弄特殊技巧,另一方面也仍然要
simple and clean... 還滿兩難的...

第一種 uri => body 的 cache, 理所當然效果極佳,因為我用 ab 測啊 XD
不過假設 ab 的 cache hit 是 99.9%(我跑 1000 筆),
依照比例算的話,就算 cache hit 只有 50%, 速度也還算有很大的提昇。

第二種設想的 cache, 就吃很大的鱉了。本來是想做 id => checksum 的
cache, 使得同樣的圖片只需要從 db 撈一次即可,其他的 thumbnail 就
可以省掉 access db 的狀況。問題是現在又卡到 lazy thumbnail 的問題。

由於有些 thumbnail 是 create on the fly, 然後再存入 storage.
這樣做的主因是,規格中的縮圖(以後打縮圖,比較短)種類實在太多了!
多到鬼扯,我也不覺得每張都會用到,可能大部份都用不到。

這邊我也調整很久了,細節不多談,只是現在就變成要在 image server
做縮圖的動作,因為這要是再拆出一個縮圖 server, 流程變太複雜了...

越來越能理解,為什麼有時候 brute force 其實是唯一的方法。
因為把演算法搞得太複雜,需要的成本非常高,效益也不見得真的好。
怪不得 complexity 只看次方,不看常數?畢竟如果不是獲得極大的效益,
有時候 brute force 真的還比較好。一來不見得真的慢多少,
二來則是好維護好擴充得多,也比較不會有 bug...

總之就是因為要縮圖,使得要避開 db access 變得有點棘手,
再加上縮圖本來就很慢啊!少掉 db access 真的有差?
最後再考量到 id => checksum cache 其實也只有第一次 request 有效,
接下來 uri => body 開始發揮作用,就沒差了...

也就是說,除非 cache hit 低於 10%, 甚至要更低更低,
這種 id => checksum 的 cache 可能才有效吧...

反倒是仔細想想,404 的 request 應該永遠是 404,
那還不如把 404 的 request 也 cache 起來。
當然如果有人來暴力 DoS 的話,memcache 大概也會被塞入一堆垃圾,
但是如果不 cache 404, 在有些狀況下卻會變得極慢!(相較於 cache hit)

例如 id 對,checksum 對,結果去 storage 撈發現沒有資料 @_@
改善方法可能是還要額外檢查 label, 而不是直接去撈資料。
不過仔細想想,如果我 cache 了 404, 這根本就不是問題了。

所以 image server 現在差不多算是完工了,就是只 cache uri,
不管是 200 或 404 都 cache 起來,暫定 cache 一天。

*


下一個要做的,則是各種 searching 需求。
我不知道是不是應該引入 search engine,
但是由於從來沒用過,還是想能避開就避開...

只是當然也不能接受暴力搜尋,那穩死的,
所以就要用 cache 啦。跟建立 index 的味道大概也差不多吧?
自己私下是稱之為訂閱的系統,雖然各種狀況不一,
剛剛才寫好草稿的那個看起來就不怎麼像是訂閱...

總之設計大概是這樣,重要資料放在 photos-core,
可以被清除然後重建的,都放在 photos-cache.

這邊 ActiveRecord 大概會很無力吧,
我暫時的作法是

WhatEver = Photo.dup
WhatEver.establish_connection :cache
WhatEver.set_table_name :what_ever

測試之下覺得應該可以用... 當然很粗暴,但先試試吧。
至於 DataMapper, 則依靠 storage_names 和 repository 即可。
Photo.storage_names[:what_ever] = 'what_ever'

實際處理的程式就可以寫成:
    repository :comments_count do
result = Photo.get(photo.id) || Photo.new(:id => photo.id)
result.attributes = photo.attributes

result.comments_count =
photo.comments(:created_at.gt => Time.now - 86400*7).count

result.save
end

photo 是從 :default 中撈出來的,所以這樣是從 :default 中撈出
資料塞入 photos-cache.comments_count, 然後唯一不同的是 comments_count,
僅僅計算七天內的留言數,超過的通通當作零!

所以這樣就可以抓出七天內最多留言的相片。pageview 的部份我還在想要怎麼做。

總之這邊只要丟進任一 photo 即可進行計算並 cache 起來。
目前希望的是跑一堆 process 去隨便抓 photo 並進行 cache 計算。

基本上因為是隨機挑的,所以希望能做到 lock, 就是如果碰到被 lock 住的,
跳過就好,繼續掃其他的。如果一張都不剩了,那就可以 sleep 個一分鐘之類的。
require 'app/datamapper/init' # 這是所有 datamapper 的 model/connection
require 'cron/comments_count' # 目前只做了這個 job

# 隨機抓出一張還沒處理的
photo = Photo.first(:cron.not => 'done', :offset => rand(Photo.count).to_i)
# 不過奇怪的是,不知道為什麼這樣偶爾會抓到 nil?
# 啊啊,對啦,我的 count 沒下條件,差點忘了,我的錯@@
# 看來是該睡了,每次想睡都烹出一堆醉雞。

if photo && PhotoLock.get(photo.id).nil?
# 如果 lock 不存在,則繼續做;否則換下一張
begin
# 建立 lock, 這存在 photos-cache.photo_locks 裡面
lock = PhotoLock.create(:photo_id => photo.id)

# 狀態設為 locked, 不過沒影響,僅供 tracking 參考
photo.update_attributes(:cron => 'locked')

# 所有的 job 在這邊開始做
Roodo.cron_comments_count(photo)

# 完成!
photo.update_attributes(:cron => 'done')
ensure
# 無論如何,lock 要在離開前解除,以便於如果失敗下次重新嘗試
lock.destroy

end
end

所以最新七天留言數排行就這樣抓:
@photos = CommentsCount.all(:order => 'comments_count DESC', :limit => 50)

這算第一步吧,希望 pageview 的部份能比照辦理,盤算很久了。
不過中間真的被打斷太多次,寫程式真的需要一股氣啊!
每次重新回來都需要開始回憶,重新繪製地圖,種植樹木養森林,
把思緒從長期記憶中再載入回短期記憶中...

就像停電就是大損失一樣,中斷思緒也是一樣的 :(

參考:
集中火力與否

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0