Olery成立于5年前。隨著時(shí)間的流逝,,最初由Ruby開發(fā)機(jī)構(gòu)開發(fā)的單一產(chǎn)品(Olery聲望)逐漸發(fā)展成為一套不同的產(chǎn)品和許多不同的應(yīng)用程序,。今天,我們不僅擁有信譽(yù)產(chǎn)品,,還擁有Olery反饋,,酒店點(diǎn)評(píng)數(shù)據(jù)API,可嵌入網(wǎng)站上的小部件以及不久的將來更多產(chǎn)品/服務(wù),。 在應(yīng)用程序數(shù)量方面,,我們也有了長足的發(fā)展。今天,,我們部署了超過25種不同的應(yīng)用程序(全部為Ruby),,其中一些是Web應(yīng)用程序(Rails或Sinatra),但大多數(shù)是后臺(tái)處理應(yīng)用程序,。 盡管我們可以為迄今為止所取得的成就感到非常自豪,,但總會(huì)有一些隱患:我們的主數(shù)據(jù)庫。從Olery開始,,我們就已經(jīng)建立了一個(gè)數(shù)據(jù)庫設(shè)置,,其中涉及MySQL來存儲(chǔ)關(guān)鍵數(shù)據(jù)(用戶,合同等),,而MongoDB則用于存儲(chǔ)評(píng)論和類似數(shù)據(jù)(本質(zhì)上是在數(shù)據(jù)丟失的情況下我們可以輕松檢索的數(shù)據(jù)),。盡管此設(shè)置對(duì)我們非常有用,但隨著我們的發(fā)展,,特別是在MongoDB中,,我們開始遇到各種問題。這些問題中的一些是由于應(yīng)用程序與數(shù)據(jù)庫交互的方式所致,,有些是由于數(shù)據(jù)庫本身所致,。 例如,在某個(gè)時(shí)間點(diǎn),,我們必須從MongoDB中刪除大約一百萬個(gè)文檔,,然后在以后重新插入它們。此過程的結(jié)果是數(shù)據(jù)庫幾乎處于完全鎖定狀態(tài),,持續(xù)了幾個(gè)小時(shí),,導(dǎo)致性能下降,。直到我們執(zhí)行數(shù)據(jù)庫修復(fù)(使用MongoDB的repairDatabase命令)。由于數(shù)據(jù)庫的大小,,此修復(fù)程序本身也花費(fèi)了數(shù)小時(shí)才能完成,。 在另一個(gè)實(shí)例中,我們注意到應(yīng)用程序性能下降,,并設(shè)法將其追溯到我們的MongoDB集群,。但是,經(jīng)過進(jìn)一步檢查,,我們無法找到問題的真正原因,。無論我們安裝了什么度量標(biāo)準(zhǔn),使用的工具或運(yùn)行的命令,,我們都找不到原因,。直到我們替換了集群的主節(jié)點(diǎn),性能才恢復(fù)到正常水平,。 這只是兩個(gè)例子,,隨著時(shí)間的流逝,我們遇到了很多這樣的情況,。這里的核心問題不僅在于我們的數(shù)據(jù)庫正在運(yùn)行,,而且每當(dāng)我們調(diào)查數(shù)據(jù)庫時(shí),都根本沒有跡象表明導(dǎo)致問題的原因,。 無模式問題我們面臨的另一個(gè)核心問題是MongoDB(或任何其他無模式存儲(chǔ)引擎)的基本功能之一:缺少模式,。缺少模式聽起來可能很有趣,并且在某些情況下,,它當(dāng)然可以帶來好處,。但是,對(duì)于許多人而言,,無模式存儲(chǔ)引擎的使用會(huì)導(dǎo)致隱式模式的問題。這些架構(gòu)不是由您的存儲(chǔ)引擎定義的,,而是根據(jù)應(yīng)用程序的行為和期望定義的,。 例如,您可能有一個(gè)頁面集合,,其中您的應(yīng)用程序需要一個(gè)帶有字符串類型的標(biāo)題字段,。盡管沒有明確定義,但這里的模式非常多,。如果數(shù)據(jù)的結(jié)構(gòu)隨時(shí)間而變化,,這是有問題的,尤其是如果舊數(shù)據(jù)沒有遷移到新的結(jié)構(gòu)中(在無模式存儲(chǔ)引擎中這是很成問題的),。例如,,假設(shè)您具有以下Ruby代碼:
這將適用于每個(gè)帶有標(biāo)題字段且返回字符串的文檔,。 對(duì)于使用其他字段名稱(例如post_title)或根本沒有標(biāo)題的字段的文檔,這將不起作用,。 要處理這種情況,,您需要按以下方式調(diào)整代碼:
解決此問題的另一種方法是在模型中定義架構(gòu)。例如,,Mongoid是流行的Ruby MongoDB ODM,,可以讓您做到這一點(diǎn)。但是,,在使用此類工具定義架構(gòu)時(shí),,應(yīng)該思考為什么他們沒有在數(shù)據(jù)庫本身中定義架構(gòu)。這樣做將解決另一個(gè)問題:可重用性,。如果只有一個(gè)應(yīng)用程序,,那么在代碼中定義架構(gòu)并不是什么大問題。但是,,當(dāng)您有數(shù)十個(gè)應(yīng)用程序時(shí),,這很快就會(huì)變成一團(tuán)糟。 無模式存儲(chǔ)引擎通過消除對(duì)模式的擔(dān)心,,有望使您的生活更輕松,。實(shí)際上,這些系統(tǒng)只是讓您自己負(fù)責(zé)確保數(shù)據(jù)一致性,。在某些情況下,,這可能會(huì)解決,但我敢打賭,,對(duì)于大多數(shù)情況而言,,這只會(huì)適得其反。 好的數(shù)據(jù)庫的要求這使我想到了一個(gè)好的數(shù)據(jù)庫的要求,,更具體地說是Olery的要求,。對(duì)于系統(tǒng),尤其是數(shù)據(jù)庫,,我們重視以下方面:
一致性很重要,因?yàn)樗兄谠O(shè)定對(duì)系統(tǒng)的明確期望,。如果數(shù)據(jù)總是以某種方式存儲(chǔ),,那么使用該數(shù)據(jù)的系統(tǒng)將變得更加簡單。如果在數(shù)據(jù)庫級(jí)別需要某個(gè)字段,則應(yīng)用程序無需檢查該字段是否存在。數(shù)據(jù)庫即使在高壓下也應(yīng)該能夠保證某些操作的完成。沒有什么比僅插入數(shù)據(jù)而令人沮喪的了,,只有在幾分鐘之后才顯示數(shù)據(jù)。 可見性適用于兩件事:系統(tǒng)本身以及從其中獲取數(shù)據(jù)的難易程度,。如果系統(tǒng)出現(xiàn)異常,則應(yīng)易于調(diào)試,。反過來,,如果用戶想查詢數(shù)據(jù),這也應(yīng)該很容易,。 正確性意味著系統(tǒng)的行為符合預(yù)期,。如果將某個(gè)字段定義為數(shù)字值,則不應(yīng)將文本插入該字段,。 MySQL的缺點(diǎn)是眾所周知的,,因?yàn)樗梢宰屇鷾?zhǔn)確地做到這一點(diǎn),結(jié)果您可能會(huì)得到虛假數(shù)據(jù),。 可伸縮性不僅適用于性能,,而且還適用于財(cái)務(wù)方面,以及系統(tǒng)如何滿足隨著時(shí)間變化的需求,。一個(gè)系統(tǒng)的性能可能非常好,,但是卻不能以大量金錢為代價(jià),也不會(huì)減慢依賴于它的系統(tǒng)的開發(fā)周期,。 遠(yuǎn)離MongoDB考慮到以上值,,我們著手尋找MongoDB的替代者。上面提到的值通常是傳統(tǒng)RDBMS的一組核心功能,,因此我們著眼于兩個(gè)候選對(duì)象:MySQL和PostgreSQL,。 MySQL是第一個(gè)候選對(duì)象,因?yàn)槲覀円呀?jīng)在一些關(guān)鍵數(shù)據(jù)中使用它,。但是MySQL并非沒有問題,。例如,在將字段定義為int(11)時(shí),,您可以輕松地插入文本數(shù)據(jù),,MySQL會(huì)嘗試對(duì)其進(jìn)行轉(zhuǎn)換。一些例子:
值得注意的是,,在這種情況下,MySQL會(huì)發(fā)出警告,。 但是,,由于警告只是警告,因此常常(即使不是總是)將它們忽略,。 MySQL的另一個(gè)問題是任何表修改(例如添加列)都會(huì)導(dǎo)致表被鎖定以進(jìn)行讀取和寫入,。 這意味著使用此類表的任何操作都必須等待修改完成,。 對(duì)于具有大量數(shù)據(jù)的表,這可能需要數(shù)小時(shí)才能完成,,這可能導(dǎo)致應(yīng)用程序停機(jī),。 這已導(dǎo)致SoundCloud等公司開發(fā)諸如lhm之類的工具來解決這一問題。 基于上述考慮,,我們開始研究PostgreSQL,。 PostgreSQL在很多方面做得很好,而MySQL則做不到,。 例如,,您不能將文本數(shù)據(jù)插入數(shù)字字段:
PostgreSQL還具有以各種方式更改表的功能,而無需為每個(gè)操作鎖定表,。例如,,添加一個(gè)沒有默認(rèn)值并且可以設(shè)置為NULL的列可以快速完成,而無需鎖定整個(gè)表,。 PostgreSQL中還有許多其他有趣的功能,,例如:基于Trigram的索引和搜索,全文本搜索,,對(duì)JSON查詢的支持,,對(duì)查詢/存儲(chǔ)鍵值對(duì)的支持,對(duì)發(fā)布/訂閱的支持等等,。 所有PostgreSQL中最重要的是在性能,,可靠性,正確性和一致性之間取得平衡,。 遷移到PostgreSQL最后,,我們決定與PostgreSQL達(dá)成和解,以便在我們關(guān)心的各個(gè)主題之間取得平衡,。從MongoDB遷移整個(gè)平臺(tái)到完全不同的數(shù)據(jù)庫的過程并非易事,。為了簡化過渡過程,我們將這個(gè)過程大致分為3個(gè)步驟: 設(shè)置PostgreSQL數(shù)據(jù)庫并遷移一小部分?jǐn)?shù)據(jù),。更新所有依賴MongoDB來使用PostgreSQL的應(yīng)用程序,,以及支持此功能所需的任何重構(gòu)。將生產(chǎn)數(shù)據(jù)遷移到新數(shù)據(jù)庫并部署新平臺(tái),。 遷移子集在我們甚至考慮遷移所有數(shù)據(jù)之前,,我們需要使用一小部分最終數(shù)據(jù)來運(yùn)行測(cè)試。如果您知道即使是一小部分?jǐn)?shù)據(jù)也會(huì)給您帶來很多麻煩,,那么遷移毫無意義,。 雖然存在可以解決此問題的工具,但我們還必須轉(zhuǎn)換一些數(shù)據(jù)(例如,重命名字段,,更改類型等),,因此必須為此編寫自己的工具。這些工具大部分是一次性的Ruby腳本,,每個(gè)腳本執(zhí)行特定的任務(wù),,例如移交評(píng)論,清理編碼,,更正主鍵序列等,。 最初的測(cè)試階段并未發(fā)現(xiàn)任何可能阻礙遷移過程的問題,盡管我們的某些數(shù)據(jù)部分存在問題,。例如,,某些用戶提交的內(nèi)容并非總是正確地編碼,因此,,如果不先清除它們就無法導(dǎo)入,。需要進(jìn)行的另一個(gè)有趣的更改是將評(píng)論的語言名稱從其全名(“荷蘭語”,“英語”等)更改為語言代碼,,因?yàn)槲覀兊男虑楦蟹治龆褩J褂谜Z言代碼代替了全名,。 更新應(yīng)用到目前為止,大部分時(shí)間都花在了更新應(yīng)用程序上,,尤其是那些嚴(yán)重依賴MongoDB聚合框架的應(yīng)用程序,。投入一些測(cè)試覆蓋率較低的舊版Rails應(yīng)用程序,您將有數(shù)周的工作時(shí)間,。這些應(yīng)用程序的更新過程基本上如下:
對(duì)于非Rails應(yīng)用程序,我們決定使用Sequel,,而我們?cè)赗ails應(yīng)用程序中堅(jiān)持使用ActiveRecord(至少現(xiàn)在是這樣),。 Sequel是一個(gè)很棒的數(shù)據(jù)庫工具包,它支持我們可能想使用的大多數(shù)(如果不是全部)PostgreSQL特定功能,。與ActiveRecord相比,,其查詢構(gòu)建DSL的功能也要強(qiáng)大得多,盡管有時(shí)可能會(huì)有些冗長,。 例如,,假設(shè)您要計(jì)算使用某個(gè)語言環(huán)境的用戶數(shù)量以及每個(gè)語言環(huán)境的百分比(相對(duì)于整個(gè)集合)。在普通的SQL中,,這樣的查詢?nèi)缦滤荆?/p>
在我們的例子中,,這將產(chǎn)生以下輸出(使用PostgreSQL命令行界面時(shí)):
Sequel允許您使用純Ruby編寫上述查詢,而無需字符串片段(這是ActiveRecord經(jīng)常需要的):
如果您不喜歡使用Sequel.lit('*'),,也可以使用以下語法:
雖然這兩個(gè)查詢可能都比較冗長,,但它們更易于重用部分查詢,,而不必訴諸字符串連接。 將來,,我們可能還會(huì)將Rails應(yīng)用程序移至Sequel,但是考慮到Rails與ActiveRecord緊密相連,,我們尚不確定是否值得花時(shí)間和精力,。 遷移生產(chǎn)數(shù)據(jù)最終,這使我們進(jìn)入了遷移生產(chǎn)數(shù)據(jù)的過程,?;旧嫌袃煞N方法可以執(zhí)行此操作:
選項(xiàng)1有一個(gè)明顯的缺點(diǎn):停機(jī)時(shí)間。另一方面,,方法2不需要停機(jī),,但是很難處理。例如,,在此設(shè)置中,,您在遷移數(shù)據(jù)時(shí)必須考慮添加的所有數(shù)據(jù),否則會(huì)丟失數(shù)據(jù),。 幸運(yùn)的是,,Olery具有相當(dāng)獨(dú)特的設(shè)置,因?yàn)閷?duì)數(shù)據(jù)庫的大多數(shù)寫入操作僅在相當(dāng)固定的時(shí)間間隔內(nèi)進(jìn)行,。確實(shí)更改頻率更高的數(shù)據(jù)(例如用戶和合同信息)是相當(dāng)少量的數(shù)據(jù),,這意味著與我們的評(píng)論數(shù)據(jù)相比,遷移所需的時(shí)間要少得多,。 這部分的基本流程是:
第2步花費(fèi)了迄今為止最長的時(shí)間,大約是24小時(shí),。另一方面,,遷移步驟1和5中提到的數(shù)據(jù)僅花費(fèi)了大約45分鐘,。 結(jié)論自我們完成遷移以來已經(jīng)快一個(gè)月了,到目前為止,,我們感到非常滿意,。到目前為止,所產(chǎn)生的影響不過是積極的,,在各種情況下甚至導(dǎo)致我們應(yīng)用程序的性能大大提高,。例如,由于遷移,,我們的酒店評(píng)論數(shù)據(jù)API(在Sinatra上運(yùn)行)最終獲得了比以前更低的響應(yīng)時(shí)間: 遷移是在1月21日進(jìn)行的,,最大的高峰只是應(yīng)用程序執(zhí)行了硬重啟(導(dǎo)致該過程中的響應(yīng)時(shí)間稍慢),。 21日之后,平均響應(yīng)時(shí)間幾乎縮短了一半,。 我們看到性能大幅提高的另一種情況就是所謂的“審查持久性”,。 這個(gè)應(yīng)用程序(作為守護(hù)程序運(yùn)行)的目的很簡單:保存評(píng)論數(shù)據(jù)(評(píng)論,評(píng)論等級(jí)等),。 盡管我們最終對(duì)該應(yīng)用程序進(jìn)行了一些非常大的更改以進(jìn)行遷移,,但結(jié)果卻非常有益: 我們的刮板(scrapers )也最終更快了: 區(qū)別并不像復(fù)審持久性那么大,,但是由于抓取工具僅使用數(shù)據(jù)庫來檢查是否存在復(fù)審(相對(duì)較快的操作),,因此這并不奇怪。 最后是計(jì)劃抓取過程的應(yīng)用程序(簡稱為“調(diào)度程序”): 由于調(diào)度程序僅按特定的間隔運(yùn)行,因此該圖有些難以理解,,但是遷移后的平均處理時(shí)間明顯減少了,。 最后,我們對(duì)到目前為止的結(jié)果非常滿意,,我們當(dāng)然不會(huì)錯(cuò)過MongoDB,。 性能非常好,與之相比,,圍繞它的工具使其他數(shù)據(jù)庫顯得蒼白,,與MongoDB相比(尤其是對(duì)于非開發(fā)人員而言),查詢數(shù)據(jù)要輕松得多,。 盡管確實(shí)有一個(gè)服務(wù)(Olery Feedback)仍在使用MongoDB(盡管是一個(gè)單獨(dú)的相當(dāng)小的集群),,但我們打算將來也將其遷移到PostgreSQL,。 原文:https://developer./blog/goodbye-mongodb-hello-postgresql/ 本文:http:///goodbye-mongodb-hello-postgresql 討論:請(qǐng)加入知識(shí)星球或者微信圈子【首席架構(gòu)師圈】 |
|