我在同類型的公司工作過幾年,老實講,Grooveshark 走到這一步,不意外。比較意外的是他能撐這麼久,三年前他們 CEO 就情緒崩潰過一次122 ,沒想到還能再多撐個三年。
Grooveshark 成立於 2006 年,當時的市場環境已經跟 Napster 成立的 1999 大不相同,自從 DMCA 數位千喜年法案通過之後,音樂服務已經有不少都走向付售權費合法化的路上,只有 Grooveshark 一家走不同的路。
Grooveshark 是群大學生一起做的,所以大家自己省一省,不用跟 VC 要錢,就可以開業。
也許是一開始走的太順,所以他們就想要這麼一直走下去,不去跟音樂公司談授權問題。
當然這樣造就的結果是,每次他們一要談下一筆增資案,律師信馬上就來,增資的錢跟本就是花在律師費上面。
我常常在想,Grooveshark能活這麼久,說不定是五大音樂公司故意的,反正把 GS 搞倒了也沒用,還會有下一家跳出來做,還不如讓 GS 吃不飽餓不死撐在那邊,擋住潛在中想做非法音樂這塊的人,同時也警告這些人,你做非法音樂是沒錢賺的。
最終搞倒 GS 的,我想除了是創業團隊已經累了之外,另外就是美國工程師行情太好,小本經營的 GS 請不起人了
MySQL對我來說,只是隻堪用的瑞士小刀,可以開一些簡單的 Table 放資料,然後就可以把 MySQL 放在那邊不用去理他。
但是除此之外都很難用,像是我昨天要把一個 160 MB 的 CSV 檔丟到我的 MySQL 去,用預設的指令,會卡在 4MB 的地方就慢到像停下來一樣。
CREATE TABLE website {
owner INT NOT NULL DEFAULT 0,
id CHAR(18) NOT NULL,
url VARCHAR(255),
PRIMARY KEY (owner, id)
}
LOAD DATA INFILE 'website.csv' ......
就這麼簡單的事,在我的 macbook 上匯到一個沒有其它人用的 MySQL ,竟然會卡住越寫越慢。
而在 PG 上,我把 3GB 的 csv 檔,寫入一個有 30 個 column 的 Table 只花了四分鐘,一秒可以寫 12.5 MB/s
這邊是比較好的討論平台,所以把我的心得放這邊好了。
就我看完的想法是, Database 效能影響的點有兩個,第一個是 index tree 的維護,第二個是「資料在硬碟上的儲存方式」
前者兩者用的都是 b+tree ,所以有同樣的問題,如果照 PK 循序寫入,因為樹會變的不平衡,會需要常常重新平衡樹,會有 lock tree or sub-tree 的發生,這時 DB 的效能會下降。
在儲存方式上,MySQL用的是 Index Optimized Table ,儲存資料的方式是照 index 的型式去寫入,所以循序寫入也有「平衡」的需求。而 PostgreSQL 則是有洞就塞,再用另外一張表去儲存 key -> 儲存位址。在 Update 時,MySQL是 in-place 把值給替換掉, PostgreSQL 則是把「儲存位址」標個墓碑,等 full vaccum 時再清掉
因為不想造成 index 上的 hotspot , Triton Ho 主張是要用 UUID ,不要在 insert 時,全部都往同一個 sub-tree 寫入,同時也省去了維護 Sequence Generator 的困擾。
這點我是同意的,對 PG 來說,是不是依 PK 的順序寫入跟本沒差,不依 PK 順序寫入,反而對 PG 來說維護 index 的成本較小,在寫入硬碟時,仍是循序寫入。
但是對 MySQL 來說就不一樣了,不依 PK 順序寫入的話,在寫入硬碟時,是 random access ,效能低 40~50%。
所以 Ant 才會說,要用 v1 Time-based UUID ,但是我覺得兩個人的對話跟本沒交集,用 v1 UUID ,那就直接用 Int/Long Sequence 就好了, index size 還可以更小。
至於 VACUUM 我更不覺得是個問題,因為除了 MYSQL 外,我用過的 MongoDB CouchDB Cassandra 全都是設計成要 FULL VACUUM 才會把空間釋放出來,
至於 PG 為什麼要引入 AUTO VACUUM 呢?我猜想是因為有太多非 DBA 的工程師,因為跟本不了解 DB 底層的運作,一跑就跑了半年沒清理過,所以 PG 才要把雞婆的幫使用者跑 VACUUM
做成 one-off 比較好啊,在 Scala Stream 是 lazy memorization collection ,我們最近一個月就踩到了兩次問題,造成 OutOfMemoryError
val stream = (1 to 10000).toStream
.map(id => dao.find(id)) // 1
.flatMap(_.getOptionalInt) // 2
.filter(_ < 10) // 3
.foldLeft(0)((ret, a) => ret + a)
這看起來是把一萬個 object ,取出其中的一個 Int 參數,過濾掉小於 10 的數值,再一個個取出來相加,並不會造成太大的的計憶體負擔,但實際上卻生成了 1, 2, 3 個各有一萬個 object 的 Stream
一不小心,就丟 OutOfMemoryError 了