背景
早期的Uber后臺(tái)軟件由Python寫(xiě)成,數(shù)據(jù)存儲(chǔ)使用Postgres。后期隨著業(yè)務(wù)的飛速發(fā)展后臺(tái)架構(gòu)也變化巨大,演進(jìn)成了微服務(wù)加數(shù)據(jù)平臺(tái)。數(shù)據(jù)存儲(chǔ)也由Postgres變成了 Schemaless ——Uber自主研發(fā)的以MySQL做為底層的高可用數(shù)據(jù)庫(kù)。Uber的數(shù)據(jù)庫(kù)主要存儲(chǔ)的是Trip數(shù)據(jù),就是一個(gè)叫車(chē)訂單從下單起,到上車(chē)、下車(chē)、付費(fèi)等的全過(guò)程跟蹤及處理。從2014年初起,由于業(yè)務(wù)增長(zhǎng)迅猛,Uber的原有基礎(chǔ)架構(gòu)已經(jīng)無(wú)法繼續(xù)支撐業(yè)務(wù)。改進(jìn)的項(xiàng)目花了將近一年時(shí)間。

對(duì)于新的數(shù)據(jù)庫(kù)存儲(chǔ)系統(tǒng),Uber的主要關(guān)鍵需求是:
要有能力通過(guò)增加服務(wù)器而線(xiàn)性地增加容量。增加服務(wù)器不但要增加可用的硬盤(pán)容量,還要減少系統(tǒng)的響應(yīng)時(shí)間。
需要有寫(xiě)緩沖能力,萬(wàn)一持久化到數(shù)據(jù)庫(kù)失敗時(shí),仍可以稍后重試。
需要通知下游依賴(lài)關(guān)系的方式,數(shù)據(jù)變更要能無(wú)損的通知出去。
需要二級(jí)索引。
系統(tǒng)要足夠健壯,可以支持7*24服務(wù)。
在調(diào)查對(duì)比了Cassandra、Riak和MongoDB等等之后,Uber技術(shù)團(tuán)隊(duì)沒(méi)有發(fā)現(xiàn)能完全滿(mǎn)足需求的現(xiàn)成解決方案。而再考慮到數(shù)據(jù)可靠性、對(duì)技術(shù)的把握能力等因素,他們決定自己開(kāi)發(fā)一套數(shù)據(jù)庫(kù)管理系統(tǒng)——Schemaless,一個(gè)鍵值型存儲(chǔ)庫(kù),可以存放JSON數(shù)據(jù)而無(wú)需嚴(yán)格的模式驗(yàn)證,是完全的無(wú)模式風(fēng)格。用MySQL作底層存儲(chǔ),其中只有順序?qū)懭耄贛ySQL主庫(kù)故障時(shí)支持寫(xiě)入緩沖。并有一個(gè)數(shù)據(jù)變更通知的發(fā)布-訂閱功能(命名為trigger),支持?jǐn)?shù)據(jù)的全局索引。
Schemaless項(xiàng)目技術(shù)負(fù)責(zé)人Jakob Thomsen 認(rèn)為 :
Schemaless的強(qiáng)大與簡(jiǎn)單更多是因?yàn)槲覀冊(cè)诖鎯?chǔ)節(jié)點(diǎn)中使用了MySQL。Schemaless本身是在MySQL之上相對(duì)較薄的一層,負(fù)責(zé)將路由請(qǐng)求發(fā)送給正確的數(shù)據(jù)庫(kù)。借助于MySQL第二索引及InnoDB的BufferPool,Schemaless的查詢(xún)性能很高。
在Evan Klitzke的文章中,他是從Postgres與 Innodb 的底層存儲(chǔ)機(jī)制對(duì)比開(kāi)始的,后面提到了他們碰到的若干Postgres問(wèn)題:
寫(xiě)入效率不高
數(shù)據(jù)主從復(fù)制效率不高
表?yè)p壞問(wèn)題
從庫(kù)上的 MVCC 支持問(wèn)題
難于升級(jí)到新版本
在Postgres的底層設(shè)計(jì)中,它的行數(shù)據(jù)是不可修改的,每個(gè)不可修改的行都叫做“元組”,每個(gè)唯一的元組都由一個(gè)唯一的 ctid 標(biāo)志,ctid也就實(shí)際指出了這個(gè)元組在磁盤(pán)上的物理偏移量。這樣對(duì)于一行修改過(guò)的數(shù)據(jù)來(lái)說(shuō),就會(huì)對(duì)應(yīng)著在物理上有多個(gè)元組。表是有索引的,主鍵索引和第二索引都以B樹(shù)組織,都直接指向ctid。
除了ctid之外還有一個(gè)關(guān)鍵字段prev,它的默認(rèn)值為null,但對(duì)于有數(shù)據(jù)修改的記錄,新的元組里面的prev字段里存儲(chǔ)的就是舊元組的ctid值。

與Postgres相對(duì)應(yīng)的是,MySQL的InnoDB引擎主鍵索引和第二也都以B樹(shù)組織,但是索引指向的是主鍵,而主鍵才真正指向數(shù)據(jù)記錄。而且,InnoDB的數(shù)據(jù)是可以修改的。兩者實(shí)現(xiàn)MVCC的機(jī)制不同,MySQL依靠UNDO空間中的 回滾段 ,而不是象Postgres依靠在數(shù)據(jù)表空間對(duì)同一條數(shù)據(jù)保持多份。

Postgres和InnoDB都通過(guò) WAL (Write Ahead Log)來(lái)保證數(shù)據(jù)可以在數(shù)據(jù)庫(kù)上安全寫(xiě)入,但對(duì)于主從庫(kù)的數(shù)據(jù)復(fù)制實(shí)現(xiàn)原理并不同。Postgres會(huì)直接把WAL發(fā)送到從庫(kù)上,讓從庫(kù)也執(zhí)行WAL來(lái)復(fù)制數(shù)據(jù)。而MySQL則是發(fā)送Binlog,在從庫(kù)上應(yīng)用Binlog。
由此,再來(lái)看看Uber對(duì)于Postgres有哪些不滿(mǎn)意:
寫(xiě)放大
一般來(lái)說(shuō)大家介意 寫(xiě)放大 的問(wèn)題是由于對(duì)SSD磁盤(pán)的使用。SSD磁盤(pán)是有壽命的,它的寫(xiě)入次數(shù)是有限的(雖然數(shù)字很大)。這樣如果應(yīng)用層只是想寫(xiě)入少量數(shù)據(jù)而已,但數(shù)據(jù)落入磁盤(pán)時(shí)卻變大了許多倍,那大家就會(huì)比較介意了。比如你只是想寫(xiě)入1K的數(shù)據(jù),可是最終卻有10K數(shù)據(jù)落盤(pán)。
Postgres的寫(xiě)放大問(wèn)題主要表現(xiàn)在對(duì)有索引的表進(jìn)行數(shù)據(jù)更新上。因?yàn)镻ostgres的索引都是指向元組的ctid,而元組又是不可更新的,所以當(dāng)你更新一條記錄時(shí),它會(huì)創(chuàng)建一個(gè)新的元組存入磁盤(pán),并且要針對(duì)所有的索引,為每個(gè)索引都創(chuàng)建一條新記錄來(lái)指向新的元組,不管你更改的字段和這個(gè)索引有沒(méi)有關(guān)系。這樣對(duì)于WAL來(lái)說(shuō),Postgres更改一條記錄操作會(huì)寫(xiě)入新的完整記錄,再加上多條索引記錄。