之前三篇講了MoSonic整體設計上的思路參考。這篇講一下其中的一些細節最佳化方面遇到的問題。
Cache Money對於查詢類型的要求限制的非常死,整個系統變成只有兩種查詢:
- select id from table where simple condition
- select * from table where id = XXX
假設有user表,有password跟is_banned兩列。已知user_name,要如何獲得is_banned?
Cache Money的限制使得必須得有兩個查詢:
- select id from user where user_name=XXX
- select * from user where id=XXX
然後再從第二個查詢裡面的*去獲得is_banned。
有人會認為這很傻,完全可以用一個: select is_banned from user where user_name=XXX的查詢來替代,實現減少sql查詢次數,以及sql返回的數量兩方面的最佳化。
這是“最佳化”是一個很有意思的問題。如果我們要認為 select is_banned from user where user_name=XXX 是一個最佳化;首先必須清楚瞭解這條sql中資料庫究竟做了什麼。
首先,要支援where user_name=XXX的查詢,user_name這列是必須加索引的。
那麼,user_name的索引中包含了什嗎?這實質上是user_name對user表主鍵(id)的索引!
資料庫要返回is_banned這個資料,實際上它是需要:
- 首先查詢user_name的索引,獲得id的對應的row指標
- 根據指標定位至真實的row,並在row找到is_banned的列的資料,才能返回。
上述的資料庫內部操作,跟cache money的兩個查詢實質上是一一對應的。
唯一不同的是,資料庫查詢是硬碟定址,而cache money是memcache訪問。
memcache與傳統硬碟誰快?memcache與SSD硬碟誰快?一個memcache叢集與單個SSD物理硬碟誰快?高並發情景下又如何?
我們的初衷是要實現一個分布式的系統以解決效能問題,但為什麼突然又要把單個硬碟上的操作視為一個最佳化?
有人還會認為一開始user_name的索引其實建錯了,應該是建user_name + is_banned的聯合索引,這樣資料庫內部在執行select is_banned from user where user_name=XXX 這個查詢的時候,就可以直接在索引資料上獲得is_banned的值,而無需多一次定址。
這種又是不是最佳化?
我們先把user表的分布式放一邊,僅考慮單機的情況;我們假設它是最佳化。
我們原來的設計中,cache money在典型情況下是緩衝了100%的查詢,對於這種新冒出來的查詢,要怎麼緩衝?
仔細研究的話,這種緩衝其實是可以做的;但查詢類型增加了,緩衝類型增加了,相應的緩衝更新策略複雜度也會增加。
這裡的實際問題是:在架構中增加這些複雜度/編碼量,緩衝佔用,資料庫的索引空間;而換來的查詢最佳化是否值得?
這其實不可一概而論,是要視業務情境而定。
如果,以使用者名稱獲得密碼的查詢在產品業務中的調用異常頻繁,甚至就是業務本身100%的調用,那麼神馬編碼複雜度,緩衝佔用,索引空間都是浮雲,這是必須付出的代價。
OK,我們決定做不惜一切代價來實現這個最佳化;這其實又引入了另一個問題:
MoSonic支援的查詢類型是否是最佳化此業務的唯一選擇?
可不可以在不改變MoSonic現有結構的情況下來實現這樣的最佳化?
這個碰巧是有現成的案例,人人網,為了高效的檢查一個使用者帳號是否有效,專門搞了一整台伺服器做中介層緩衝所有人人網使用者是否被ban的資料,並提供高效的網路介面供系統其他模組調用。
具體可查閱人人網中介層:實踐篇。
所以,答案是可以。
MoSonic本身是作為一個通用的底層ORM架構,它的分布式支援,緩衝支援,都是透明的;但也不強制開發人員必須使用;對Cache Money不符合的業務情境,那麼是可以把緩衝禁用掉;開發人員根據自己業務特性去尋求更貼切的緩衝方案。
設計一個通用的資料庫訪問方案,實質上並不應該對特定業務做特定情境下的最佳化,而應該留出來空間,讓開發人員在遇到特殊情境時有實現特定最佳化的可能。
如果說,MoSonic把cache money這層透明緩衝做成強制的,開發人員連禁用某個表的緩衝控制都沒有,那才是設計的問題。
上述,實際上僅討論了一個小細節在單機情境下的情況,同一個問題如果延伸至分布式的情境,也會引發很多很有意思的話題,這篇就先不累述了。