如何使用 memcached 做快取

關於 memcached,雖然可以找到一些基本
,DK大神也有點到一些進階議題,不過最近看了 Using memcached PDF 之後才真正學到了不少實戰技巧跟如何設計快取的概念。以下是一些零散的筆記:

裝好之後,基本的啟動方式是

*
memcached -l 127.0.0.1 -P 11211 -m 128 -d for deamon
* memcached -l 127.0.0.1 -P 11211 -m 128 -vv for development debug

memcached 是一套 Name-Value Pair(NVP) 分散式記憶體快取系統,Key 的長度被限制在 250 characters,儲存的資料不能超過 1 megabyte。如果資料會超過 1mb,可以考慮使用壓縮工具,例如在 Rails 2.1 裡就內建了 ActiveSupport::Cache::CompressedMemCacheStore。

除了 memcached server,不同程式語言都有自己的 memcache client library 工具提供更方便的介面。一個基本的任務就是使用 Hashing algorithm 根據 Key 來決定該去存取一台 memcached server (如果有超過一台的 memcached server 的話)。Ruby 的 client 目前檯面上有幾套 1. memcache-client 2. fiveruns fork 版 memcache-client,針對 Hasing 的部份用C改寫了 3. 使用 libmemcached 的超快 memcached

有趣的是,不需要 client library,memcached 是可以直接 telnet 127.0.0.1 11211 的。telnet 之後打 stats 可以得到一些統計資料,除了目前共有多少筆資料跟共用多少空間之外,重要的有 cmd_get 跟 cmd_hits,就可以得出 cache hit ratio,這個數字應該努力到九成以上。另外還有你的 cmd_set 應該超過 cmd_get,

其他的 memcached 標準操作有 SET (新增或是更新一個值)、ADD(只有在該key不存在時,才會新增快取資料成功)、REPLACE(只有在該key已經存在時,才會更新資料成功)、GET(拿快取資料)。

在考慮使用 memcached 前,要知道它不是你系統中唯一的 cache,HTML 的整頁快取應該用 Web server、純 SQL query result 可以用 MySQL 內建 Query Cache,設定很簡單,效果很不錯 (Cache Performance Comparison。我自己的心得是很多時候你想要快取的物件其實並不是一個 SQL query 就可以搞定的,而是多個 SQL query 才計算出來,這時候去做純快取 SQL query result 我個人覺得也許不是很有意義,Rails 這部份就有人實做了query_memcached。。

另外要注意的是 memcached 並不是 persistent data store,只要一關掉 memcached server,裡面的資料就會通通不見,如果要拿來儲存 session authentication 資料要特別小心。

寫 memcached 程式的第一個問題是找出什麼資料需要快取? 一個常見的問題是我該快取 HTML fragment 還是純資料結構? 如果你操作介面只有一處用到,我們可以只快取 HTML fragment 即可,不然其實規模稍大的網站其實兩者都可以快取起來。

撰寫使用 memcached 程式的基本模式就是,先查看有沒有 key-value,有就把快取資料讀出來,沒有就運算結果後存到 memcached sever。這部份算是簡單的。真正困難的事情有兩件:一是清除過期的快取資料(expire),二是Key的命名。
閱讀全文〈如何使用 memcached 做快取〉

實戰敏捷開發 Practices of an Agile Developer (3) 測試篇

上一章談了來自客戶的 Feedback,這一章作者談另一種回饋機制:透過程式測試得知目前開發的情形(Coding Feedback!)。

Put Angels on Your Shoulders (讓測試當你的天使)

Code 一直在變,我們需要一種持續又快速的 feedback 告訴我們這些程式碼建不健康:是我想要的程式嗎? 最後的 commit 有沒有弄壞任何東西? 這時你需要的一種機制可以一直幫你確保這些,也就是自動化的單元測試(unit tests)。

因為測試的流程總是 1.Setup(設定初始資料) 2.Exercise(執行某些操作) 3. Verify(驗證輸出結果與預期相同) 4.Teardown(清空資料),所以目前每套程式語言一定都會有一套 xUnit framework 的測試框架可供使用。

另外還可以加裝一套 continuous build 工具(例如 CruiseControl),它可以自動 checkout 最新的程式碼並測試,一旦有人送交了有錯的程式碼,馬上就可以通知並修正(這時候fix bug最簡單且成本最低)。

當你的 unit tests 達到 Regression tests 程度時,這時候做重構(refactoring)時就有更大的自由(自信),因為不需要擔心改爛了什麼你不知道的東西。

另外搭配 test coverage 工具可以幫助你有個大概的鳥瞰。寫 test 像是一個聰明的投資,因此報酬效率也很重要,trivial method 不需要寫,把火力放在你最擔心出錯的地方。

Use It Before You Build It (用之前先測試怎麼用)

這節進一步談測試驅動開發(TDD),在寫程式前先寫好測試程式。透過先寫測試程式,可以從使用API的觀點來思考怎麼寫程式、怎麼設計API,因而寫出更好用、一致及簡潔的程式介面。

Different Makes a Difference (差一點差很多)

在我這台沒問題啊,為什麼換一台電腦就不對了? 只要換一個平台(即使是不同版本的作業系統),不同就是不同,而太晚發現這樣的問題要修理是非常麻煩的,尤其是正要 release 的時候才發現它沒辦法在某某你想要的平台上執行。

我們當然可以要求 QA team 要在不同平台上測試,但是如果他們只是手動測試,就不如自動測試來的及早發現。我們已經會在 commit 程式前在自己電腦上執行一次 unit testing,現在要做的事情是要在你所要支援的不同平台、不同版本上再測試一次。不過好累啊,這時候就必須靠 continuous integration 工具來幫忙,每當有人 commit 時,這個工具就會自動 checkout 出來執行測試,我們分別在不同平台(例如可以使用 VMware 或 Virtual PC)上設定好這個工具來做自動化測試。

Automate Acceptance Testing (自動化驗收測試)

重要的商業邏輯除了 unit testing,還需要驗收測試獨立於所有程式之外,而且客戶必須親自驗證這個結果。也因為因此,我們需要一個工具讓客戶也可以新增編輯跟修改驗收測試(without coding),

這本書介紹的工具是 fit.c2.com/,它使用 HTML table 當作定義的介面,客戶可以直接編輯加入新的功能,接著加入可能的輸入與正確的預期值。然後 developer 再根據這個 table 產生 fixtures 資料,並比對測試結果是否成功。

感覺像是協力式的 unit testing,你還是必須自己寫測試,只是必須有別人提供你什麼是正確答案。

Measure Real Progress (測量真正的進度)

紀錄花了多久做事提供了很好的 feedback,可以幫助專案計畫、估計跟效率測量。但是一般常用的 timesheet 多半用於薪水用途而非真正花費的時間,這點要特別注意。另外常用的紀錄完成百分比,作者也認為很沒用。一個比較好的方式是紀錄 1.估計多少時間 2.已經花了多少時間 3.估計還需要多少時間來完成。根據真正花了多少時間的經驗,加以比對一開始的估計值,可以獲得很好的經驗用於估計下一個類似的任務。

紀錄真正的時數對於使用 backlog(要完成的任務清單)和 Iteration 也非常重要,如果剩餘的時間超過這次 Iteration 了,就知道必須移動某些任務到下一個 Iteration,反之則有時間多加工作。

最後是注意紀錄的時間單位,以分鐘太細了,以週就太粗囉。我們的經驗是超過一天的 Ticket(Issue) 應該都可以想辦法拆小,1hr、2hr、4hr、8hr 應該是不錯的範圍。

Listen to Users (聆聽使用者)

作者在這章最後特別提了使用者的回饋。即使有寫測試程式,我們還是很容易忽略來自真正使用者的聲音。每一個使用者的抱怨都有其道理,盡量試著找出真正的問題,而不是怪罪使用者不懂、不了解系統如何操作。
程式Bug、文件Bug、使用者會認知錯誤也要算是Bug。而不是讓客服人員回答說:喔,那不是Bug, 你犯了一個大家都會犯的錯誤。即使聽起來很笨,也應該去試著聆聽。

主動攔截 Rails exception 錯誤

喔,炸了。

相信以上畫面大家都不陌生,這是一個標準的 Rails app 錯誤頁面。雖然我們努力避免,但總是有出錯的時候,一個上 production mode 的專業 Rails app 絕不會痴痴地等待使用者告訴你網站炸了,而是要能夠主動通知及紀錄下這個錯誤例外(exception),好讓我可以 trace error、fixed bug 甚至在發生錯誤沒多久就可以 E-mail 告訴這位使用者苦主發生了什麼事情。

解法有幾種:

  1. 安裝 exceiption_notifition,這是個官方的基本 Plugin ,它會在發生例外時寄 email 通知你(們)。

  2. 安裝 ExceptionLogger (作者 defunkt 也是 facebox 的作者 :p ),這個 Plugin 會紀錄到 database,並提供一個後台可以瀏覽。不過這個後台裝起來比較麻煩,例如你還得處理瀏覽權限等等。

  3. 使用 Hoptoad 第三方服務,這是個由知名Rails團隊 thoughtbot 所提供的 Web service。申請帳號之後可以拿到 API key 及 Plugin,於是你的網站發生 exception 的時候就會自動將錯誤訊息送到 Hoptoad 收集起來。Hoptoad 提供了還蠻不錯的後台可以瀏覽。這個解法安裝最簡單,功能又很夠用,還可以統計及追蹤例外處理的情況,我個人十分推薦使用。

github 上應該還有其他的 Plugins 可以幫助你處理這個問題。anyway… 最糟的就是什麼都不做,讓使用者告訴你有東西炸了,然後你再去爬去找 log 檔 :)

和多繽紛樂@Yahoo! Open Hack Day

我們得獎了!和多繽紛樂敝社參加 Y! Open Hack Day 2008 的作品,它是個賓果機產生器,你可以將各式資料扔進去,拉下拉霸之後就會亂數回答一個結果。除了直接提供選項之外,也可以選擇 Yahoo! 生活+、Yahoo! 知識+、Yahoo! 電影、Yahoo! 拍賣、Flickr 相簿、無名熱門相簿、Diggirl 熱門相簿等等資料。

和多獲佳作十萬元

Handlino and David Filo

和多與Y!共同創辦人David Flio的合照

會場音樂很吵(剛好我們的位置被安排在音響前方)、正妹很漂亮、食物飲料不缺、網路雖不快但也算順暢、贈品還不錯豐富,Y! 辦了一場想要大家認真 Coding,但又請了樂團炒熱氣氛的 Party… XD 對我來說,最 high 的不是台上怎麼玩,而是這充分 teamwork 的黑客松過程,在這兩天內和多瘋狂地 commit,一想到想要做什麼,分工一下不用多久就做出來了,我們也在會場內提早公開這個網站讓大家玩(Agile feedback!!)。

雖然超級疲累,不過得獎真的很爽 :D

最佳化 ActiveRecord SQL 查詢

Update(2008/9/5) 補充 named_scope 也可以用在 :select,感謝 tsechingho++

要看 AciveRecord 產生的 SQL 是什麼,除了可以直接 tail -f log/development.log 之外,也可以在 script/console 的情況下透過以下指令直接把 log 直接顯示出來:

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.clear_active_connections!

如此便可以好好在 console 的環境下實驗 ActiveRecord 囉。

我看到關於 ActiveRecord SQL 查詢最佳化方式有這幾種:

1. 使用 :select

只撈需要用到的欄位,特別是如果不需要用到的 text 或 binary 欄位請排除。

Event.find(:all, :select => "id, title, description")

搭配 named_scope 我們可以把常用的 :select 預先設定好,例如:

class User < ActiveRecord::Base named_scope :short, :select => "id, name, email"
end

User.short.find(:all)

2. 使用 :include

使用 :include 避免 N+1 次 queries 的問題。


@events = Event.find(:all, :include => [:group] )


@events.each do |e|
e.group.title
end

如果沒有加 :include 把相關的 groups 一起載入,在迴圈中就會產生 @event.size 次對 group 的個別 SQL 查詢,會非常傷。加了 :include 之後總共只需查詢兩次。

另外一個比較少人知道的是,在設定 Model associations 時,如果有很明顯的情境一定會順道載入二階 association model,可以設定 :include 在 has_many, belongs_to, has_one 上面,例如:


class User < ActiveRecord::Base has_one :foo, :include => [:bar]
end

如此便會在載入 @user.foo 的同時,也會提早載入 @user.foo.bar。

3. 資料庫索引

針對 foreign key 要加上資料庫索引 index。在 migration 上透過 add_index 就可以加上去了。

4. 特定情況下可用 :joins 取代 :include

在只需要用到 :conditions 而不需要載入該 model 的情況下,可以用 :joins 取代 :include。

Group.find(:all, :include => [ :group_memberships ], :conditions => [ "group_memberships.created_at > ?", Time.now - 30.days ] )

因為其中會載入 group_memberships model 只是為了加條件式,而沒有要撈出裡面的資料,所以可以改用 :joins,這其中的差異你看產生出的SQL就知道了 :)


Group.find(:all, :joins => [ :group_memberships ], :conditions => [ "group_memberships.created_at > ?", Time.now - 30.days ] )

5. 自己寫 SQL

ActiveRecord 可以直接寫 find_by_sql。

6. denormalization 逆正規化

當資料非常多,又要常常查詢其中計算的結果,這時可以考慮使用逆正規化的手法,將計算的結果也當做資料存起來。

一個最常見最基本的用法就是計算總數了,例如以下的例子可以解決需要常常查詢 @topic.posts.size 該篇主題有多少文章的情境:


class Topic < ActiveRecord::Base has_many :posts end class Posts < ActiveRecord::Base belongs_to :topic, :counter_cache => true
end

我們會在 Topic model 新增一個欄位 posts_count,然後 :counter_cache 就會幫我們在新增刪除 Post 時,自動更新所屬 Topic 的 posts_count 欄位資料。這時只要打 @topic.posts.size 就會直接回傳 posts_count,而無需每次都再發一個 SQL query 去 count 有多少筆 posts。

其他逆正規化的方式也包括了統計結算報表等,我們把需要複雜計算的的結果(可以透過非同步的機制如 cron 定期去觸發) 存到 Report model,這樣使用者直接撈 Report 的結果即可。

Rails plugins 投影片

釋出今天在 COSCUP 2008 的投影片,PDF請按這裡下載

Rails Plugins

View SlideShare presentation or Upload your own. (tags: plugin rails)

雖然還蠻走馬看花的,不過20分鐘的 talk 也夠把(我所知道的)常用的 Rails Plugins 都介紹過了一遍,其中也包含了幾個和多自產自用的 open source plugin :)

誠如我投影片所說,熟悉常用的 Rails Plugins 跟熟悉 core 一樣重要。不過投影片沒說的是:Plugin 的地雷可比 core 來的多,因此除了會安裝使用之外,最好也要具備基本 Ruby meta-programming 的能力來大概看懂這些 Plugins 是怎麼做的。

BTW,如果投影片對你有幫助的話,希望可以聽到您的留言鼓勵,或是推薦一下還有什麼好用 Rails plugin :p