再見 MongoDB,你好 PostgreSQL

來源:互聯網
上載者:User

再見 MongoDB,你好 PostgreSQL

Olery 差不多成立於5年前。始於Ruby代理開發的單一產品(Olery Reputation),隨著時間的推移,我們開始致力於一系列不同的產品和應用程式。當今,我們的產品不僅有(Olery) Reputation,還有Olery Feedback, Hotel Review Data API,widgets ,在不久的將來它可以嵌入到網站和更多供應項目中。

我們增加了很多應用程式的數量。當今,我們部署了超過25個不同的應用程式(全為Ruby),它們中的一些是web應用程式(Rails或者Sinatra),但大多數的是後台運行程式。

我們最引以為豪的是迄今為止我們所取得的成就,不過在這些成就的背後總閃現著一樣東西,即基礎資料庫。從Olery成立之日起,我們就安裝了資料庫,它用MySQL來儲存(使用者、合約等等)核心資料,用MongoDB來儲存評論及其類似的資料(即哪些在資料丟失的情況下很容易恢複的資料)。一開始,這樣的安裝啟動並執行非常好,然而,隨著公司的成長,我們開始遇到了各種各樣的問題,尤其是MongoDB的問題居多。其中一些問題是由於應用與資料庫的互動方式而引起的,一些則是由資料庫本身而產生的。

例如,某個時刻,我們需要從MongoDB中刪除一百萬個文檔,以後再把這些資料重新插入到MongoDB裡。這樣的處理方法使得整個資料庫幾乎要被鎖定數個小時,自然服務效能就會降低。而且直到對資料庫執行修複(即在MongoDB上執行repairDatabase命令)後才會解鎖。而且完成修複還要花費數個小時,修複所花的小時數要根據資料庫的大小來確定。

在另一執行個體中我們注意到我們的應用程式的效能降低和設法跟蹤到的 MongoDB 叢集。然而,經過進一步檢查,我們無法找到問題的真正原因。無論我們怎麼安裝,或使用什麼工具敲了什麼命令我們都找不到原因。直到我們更換了叢集的初選,效能才恢複正常。

這隻是兩個例子,我們已經有過許多這樣的情況。這個問題的核心是,這不只資料庫在運行,而且無論我們何時察看它都沒有絕對的跡象表明是什麼原因導致的問題。

無模式的問題

另外,我們面對的核心問題是mongoDB的重要特徵之一:模式的缺乏。模式的缺乏可能聽起來是有趣的,並且在一些情況下是有好處的。然而,對於許多無模式儲存引擎的用法,其導致了一些模式之間的內部問題。這些模式沒有通過你的儲存引擎定義而是通過你的應用的行為及其可能的需要而定義的。

例如:你可能有一頁儲存你的應用需要的字串類型的title欄位的集合。這兒這個模式是非常符合當前情形的,即使它沒有被明確的定義。但如果這個資料結果改變逾時,尤其是如果原來的資料沒有被遷移到新的資料結構,這就成了問題(在一些無模式的儲存引擎上是相當有問題的)。例如,你可能有下面這樣的Ruby代碼:

post_slug = post.title.downcase.gsub(/\W+/, '-')

這樣,針對每一個有“title”欄位並返回一個String的文檔,它都能正常工作。然而,對於那些使用不同欄位名字(例如:post_title)或者根本沒有標題欄位的文檔來說,它將不能正常工作。為了處理這種情況,你需要將代碼調整為下面內容:

if post.title
  post_slug = post.title.downcase.gsub(/\W+/, '-')
else
  # ...
end

另一種處理方法是,在你的模型中定義一個模式。例如 Mongoid,一個流行的針對Ruby的MongoDB ODM,就能讓你做到這一點。然而,當使用這些工具定義一個模式時,你可能會好奇為什麼它們不在資料庫內定義該模式。實際上,這樣做可以解決另一個問題:可重用性。如果你只有一個應用程式,那麼在代碼中定義模式並不是什麼大問題。然而,如果你有許多應用程式的話,這將很快會成為一個大麻煩。

無模式儲存引擎希望通過刪除對模式的限制的方式,讓你的工作變得更簡單。但現實的情況是,確保資料一致性的責任推到了使用者自己的身上。有時候無模式引擎可以工作,但我打賭,更多的時候是事與願違。

好資料庫的需求

Olery有了更多的特殊需求後,迫使我尋求一款更好的資料庫來解決問題。對於系統,特別是資料庫,我們非常注重以下幾點:

  1. 一致性

  2. 資料和系統行為的可視化

  3. 正確性和明確性

  4. 可拓展

一致性是重要的在於它有助於協助我們對系統設定明確的期望。如果資料總是按照同樣的方式儲存,那麼系統可以很方便的使用這些資料。如果在資料庫層面要求表的莫一列必須存在,那麼在應用程式層面就不用檢查這列資料是否存在。資料庫即使實在高壓情況下,也必須保證每一次操作的完整性。沒有什麼事情比單純的插入資料,過了幾分鐘後卻找不到資料的事更讓人沮喪了。

可見度包含了兩點:系統本身以及從中擷取資料的容易程度。如果一個系統出錯那麼應該易於調試。反過來,使用者應很容易查到想要查詢的資料。

正確性是指系統的行為如我們所期望的那樣。如果某個欄位定義為一個數值型,沒有人可以像其中插入文本。這方面MySQL是臭名昭著,一旦你這樣做你將得到偽結果。

可擴充性不僅針對效能而言,而且也涉及金融方面和系統能夠多麼好地應對不斷變化的需求。一個系統在沒有大量資金成本或減緩系統所依賴的開發週期情況下,很難表現得非常好。

搬離MongoDB

上面的需求牢記於心後,我們就開始尋找一個取代MongoDB的資料庫。上面提到的特性通常是傳統RDBM特徵的一組核心集,所以我們鎖定了兩個候選者:MySQL和PostgreSQL。
本來,MySQL是第一候選,因為我們的一些關鍵資料已經在使用它儲存。然而,MySQL也有一些問題。例如,當將一個欄位定義為int(11)時,你卻可以輕鬆地向該欄位插入文本資料,因為MySQL會試圖對它進行轉換。下面是一些例子:

mysql> create table example ( `number` int(11) not null );
Query OK, 0 rows affected (0.08 sec)
 
mysql> insert into example (number) values (10);
Query OK, 1 row affected (0.08 sec)
 
mysql> insert into example (number) values ('wat');
Query OK, 1 row affected, 1 warning (0.10 sec)
 
mysql> insert into example (number) values ('what is this 10 nonsense');
Query OK, 1 row affected, 1 warning (0.14 sec)
 
mysql> insert into example (number) values ('10 a');
Query OK, 1 row affected, 1 warning (0.09 sec)
 
mysql> select * from example;
+--------+
| number |
+--------+
|    10 |
|      0 |
|      0 |
|    10 |
+--------+
4 rows in set (0.00 sec)

值得注意的是,MySQL在這些情況下會發出警告。但是,僅僅是警告而已,它們通常(若非總是)會被忽略。

此外,MySQL的另一個問題是,任何錶的修改操作(例如:添加一列)都會導致表被鎖,此時將無法進行讀或寫操作。這就意味著,使用這種表的任何操作都不得不等待修改完成之後才能進行。對於包含有大量資料的表,這可能會花費幾個小時才能完成,很可能會導致應用程式宕機。這已經導致一些公司(例如SoundCloud)不得不自己開發工具(例如lhm)來解決該問題。

瞭解到上面的問題後,我們開始調查PostgreSQL。PostgreSQL可以解決很多MySQL不能解決的問題。例如,PostgreSQL中你不能將文本資料插入一個數字欄位:

olery_development=# create table example ( number int not null );
CREATE TABLE
 
olery_development=# insert into example (number) values (10);
INSERT 0 1
 
olery_development=# insert into example (number) values ('wat');
ERROR:  invalid input syntax for integer: "wat"
LINE 1: insert into example (number) values ('wat');
                                            ^
olery_development=# insert into example (number) values ('what is this 10 nonsense');
ERROR:  invalid input syntax for integer: "what is this 10 nonsense"
LINE 1: insert into example (number) values ('what is this 10 nonsen...
                                            ^
olery_development=# insert into example (number) values ('10 a');
ERROR:  invalid input syntax for integer: "10 a"
LINE 1: insert into example (number) values ('10 a');

PostgreSQL 還具有在許多方式中不需要每一個操作都上鎖就可以改寫表的能力。例如,添加一列沒有預設值卻可以設定為null的列並能夠快速完成無需鎖定整個表。

還有其他各種有趣的功能,如在 PostgreSQL 可以:trigram 為基礎的索引和檢索,全文檢索索引,支援JSON查詢,支援查詢/儲存鍵-值對,支援發布/訂閱等更多。

最重要的是PostgreSQL在效能,可靠性,正確性和一致性之間能夠權衡。

遷移到PostgreSQL

最後,為了在所關心的各種項目之中達到平衡,我們決定使用PostgreSQL。但是,將整個平台從MongoDB遷移到一個截然不同的資料庫並不是很容易的事。為了使轉移工作簡單化,我們將此過程分成了3個步驟:

  1. 搭建一個PostgreSQL資料庫,並遷移資料的一個小子集。

  2. 更新所有依賴於MongoDB的應用程式,連同任何需要的重構,都用依賴於PostgreSQL的程式替代。

  3. 將產品資料移轉到新資料庫上,然後部署新平台。

部��資料移轉

在考慮把所有資料移轉到新資料庫之前,我們先遷移了一小部分資料來做測試。如果僅僅是遷移一小部分資料,就有非常多的麻煩的話,那麼資料庫遷移也就沒什麼意義了。

儘管有現成的工具可以利用,但還是有些資料(比如,列重新命名,資料類型不一致)要做轉換,對於這些資料我們自己開發了些工具。這些工具中,大部分都是Ruby寫的一次性腳步,用於刪除一些評論,整理資料編碼,修正主鍵發生序列等等。

在測試開始階段儘管有些資料上的問題,並沒有出現大的會阻礙遷移的問題。例如,有些使用者提交的資料沒有完全按格式編碼,導致這些資料被重新編碼之前,不能被匯入到新資料庫。例外一個有意思的改變是,之前評論的資料存的是評論用的語言的名稱(如“荷蘭語”,“英語”等),現在改了存語言的編碼,因為我們新的語義分析系統使用的是語言編碼,而不再是語言名稱。

更新應用

目前為止,花費時間最多的就是更新應用,尤其是那些嚴重依賴MongoDB彙總架構的應用。扔掉那少數幾個遺留的Rails應用吧,光是測試就會花掉你幾個星期的時間。更新應用的過程大致如下:

  1. 用PostgreSQL的相關代碼來替換掉MongoDB的驅動/設定模組的代碼

  2. 運行測試

  3. 修複Bugs

  4. 反覆運行測試,直到所有測試通過

對於非Rails應用,我們推薦使用 Sequel,對於Rails應用,我們現在還無法擺脫ActiveRecord(至少是現在)。Sequel是一個非常好的資料庫工具集,它支援絕大多數(如果不是全部)我們想使用的PostgreSQL特性。相較於ActiveRecord,它基於DSL的query要強大的多,儘管可能耗時會有點長。

舉個例子,假設你想計算有多少使用者使用某種語言,並計算每種語言所佔的比例(相對於整個集合)。純粹的SQL查詢語句如下所示:

SELECT locale,count(*) AS amount,
(count(*) / sum(count(*)) OVER ()) * 100.0 AS percentageFROM users
GROUP BY localeORDER BY percentage DESC;

在我們的例子中,將會產生以下輸出(當使用PostgreSQL命令列介面時):

locale | amount |        percentage
--------+--------+--------------------------
 en    |  2779 | 85.193133047210300429000
 nl    |    386 | 11.833231146535867566000
 it    |    40 |  1.226241569589209074000
 de    |    25 |  0.766400980993255671000
 ru    |    17 |  0.521152667075413857000
        |      7 |  0.214592274678111588000
 fr    |      4 |  0.122624156958920907000
 ja    |      1 |  0.030656039239730227000
 ar-AE  |      1 |  0.030656039239730227000
 eng    |      1 |  0.030656039239730227000
 zh-CN  |      1 |  0.030656039239730227000
(11 rows)

Sequel允許你使用純Ruby編寫上面的查詢,而不需要字串分段(ActiveRecord經常需要):

star = Sequel.lit('*')User.select(:locale)
    .select_append { count(star).as(:amount) }
    .select_append { ((count(star) / sum(count(star)).over) * 100.0).as(:percentage) }
    .group(:locale)
    .order(Sequel.desc(:percentage))

如果你不喜歡使用“Sequel.lit(“*”)”,你也可以使用下面的文法:

User.select(:locale)
    .select_append { count(users.*).as(:amount) }
    .select_append { ((count(users.*) / sum(count(users.*)).over) * 100.0).as(:percentage) }
    .group(:locale)
    .order(Sequel.desc(:percentage))

 

雖然這可能有些冗長,但是上面的兩種查詢都使得它們更易於重用,而無需進行字串串連。

 

未來可能也會將我們的Rails應用程式遷移到Sequel,但是考慮到Rails與ActiveRecord耦合得如此緊密,所以我們還不完全確定這是否值得花費時間和精力。

遷移生產資料

最終我們來到遷移生產資料的過程。一般有兩種方法來做這件事:

  1. 關掉整個平台,直到所有資料都已遷移完成。

  2. 遷移資料的同時保持系統運行。

第一個選項具有一個明顯的缺點:停機時間。第二個選項不需要停機但是很難處理。例如,在這個方案中,當你遷移資料的同時,你必須要考慮所有將要添加的資料,否則你就會損失資料。

幸運的是,Olery有一個獨特的方案就是我們的資料庫的絕大多數寫操作都是相當週期性,經常變化的資料(例如使用者通訊錄資訊)只佔總資料量的一小部分,相比起我們檢查資料,遷移它們花費的時間相當的小。

 

該部分的基礎工作流程是:

 

  • 遷移諸如使用者、連絡人之類的關鍵資料,基本上所有我們無論如何都無法賠償損失的資料。

  • 遷移不太重要的資料(如我們可以再抓取、再計算獲得的資料等)。 

  • 測試正常運行在一組獨立伺服器的一切

  • 切換產品環境到新的伺服器。

 

再遷移步驟1的資料,確保在平均故障時間內建立資料不會丟失。

第2步是目前最耗時的,大約需要24小時。相反的是,步驟1和5中提到的資料移轉只需要45分鐘。

結論

我們遷移完成並且直到非常滿意大概過去了一個月。到現在為止除了那些積極的影響,還曾在各種情況中讓應用的效能大幅提高。舉例來說,我們的 酒店評論資料API(Hotel Review Data API)(在Sinatra運行)相比遷移之前互動延遲變低了許多:

遷移是在1月21日開始的,高峰表示應用效能的硬重啟(在處理期間導致互動時間輕微變慢)。在21日之後互動的平均時間大致是原來的一半。

在另外一種被我們稱作“評論持久化”(譯者註:即儲存評論)的過程中,我們發現了效能上巨大的提升。背景程式目標很簡單:儲存評論資料(評論內容,評論分數等等)。當我們最終完成了為遷移工作做的很多大的更改後,結果令人振奮:

抓取器也變的更快了:

抓取器效能提升沒有評論儲存的過程那樣大,因為抓取器只用資料庫來查詢某個評論是否存在(一個相對很快的操作),所以這樣的結果並不很令人吃驚。

最後來到程式裡用來調度抓取過程的進程(簡單稱之為“調度器”):

因為調度器只是以固定頻度運行,這個圖可能有點難以理解,但是不管怎樣,在遷移之後有一個很清晰的平均處理時間的下降。

最後,我們已經對現在的結果非常的滿意,而且我們肯定不會懷念MongoDB了。它的效能非常好,它的處理方案使其它資料庫相比之下黯然失色,並且查詢資料的過程與MongoDB相比實在太令人滿意了(尤其是對於non開發人員而言)。儘管我們仍然還有一個服務(Olery Feedback)仍舊使用MongoDB(儘管這運行在一個獨立的,相對小的叢集上),我們仍然打算將來把它移植到PostgreSQL上。

------------------------------------華麗麗的分割線------------------------------------

CentOS 6.3環境下yum安裝PostgreSQL 9.3

PostgreSQL緩衝詳述

Windows平台編譯 PostgreSQL

Ubuntu下LAPP(Linux+Apache+PostgreSQL+PHP)環境的配置與安裝

Ubuntu上的phppgAdmin安裝及配置

CentOS平台下安裝PostgreSQL9.3

PostgreSQL配置Streaming Replication叢集

如何在CentOS 7/6.5/6.4 下安裝PostgreSQL 9.3 與 phpPgAdmin 

------------------------------------華麗麗的分割線------------------------------------

PostgreSQL 的詳細介紹:請點這裡
PostgreSQL 的:請點這裡

英文原文:Goodbye MongoDB, Hello PostgreSQL

本文永久更新連結地址: 

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.