軟件公司爲什麼要放棄MongoDB?

本文轉至:http://database.51cto.com/art/201503/469510_all.htm
(只做轉載, 不表明本站和博主贊成文中觀點或證明文中信息)

Olery成立於2010年,總部位於阿姆斯特丹。該初創公司爲酒店行業提供聲譽管理與媒體監控工具,幫助酒店將網絡評論和社交媒體反饋轉化成可執行的商業智能分析。mysql

Olery成立最初是使用MySQL來存儲(用戶、合同等等)核心數據,用MongoDB來存儲評論及其相似的數據(即哪些在數據丟失的狀況下很容易恢復的數據)。一開始,這樣的安裝運行的很是好,然而,隨着公司的成長,開始遇到了各類各樣的問題,尤爲是MongoDB的問題居多。其中一些問題是因爲應用與數據庫的交互方式而引發的,一些則是由數據庫自己而產生的。sql

例如,某個時刻,Olery須要從MongoDB中刪除一百萬個文檔,之後再把這些數據從新插入到MongoDB裏。這樣的處理方法使得整個數據庫幾乎要被鎖定數個小時,天然服務性能就會下降。並且直到對數據庫執行修復(即在 MongoDB上執行repairDatabase命令)後纔會解鎖。並且完成修復還要花費數個小時,修復所花的小時數要根據數據庫的大小來肯定。數據庫

在另外一實例中Olery注意到應用程序的性能下降和設法跟蹤到的 MongoDB 集羣。然而,通過進一步檢查,沒法找到問題的真正緣由。不管怎麼安裝,或使用什麼工具敲了什麼命令都找不到緣由。直到Olery更換了集羣的初選,性能才恢復正常。服務器

這只是兩個例子,Olery已經有過許多這樣的狀況。這個問題的核心是,這不僅數據庫在運行,並且不管什麼時候察看它都沒有絕對的跡象代表是什麼緣由致使的問題。網絡

無模式的問題數據結構

另外,Olery面對的核心問題是mongoDB的重要特徵之一:模式的缺少。模式的缺少可能聽起來是有趣的,而且在一些狀況下是有好處的。然而,對於許多無模式存儲引擎的用法,其致使了一些模式之間的內部問題。這些模式沒有經過存儲引擎定義而是經過應用的行爲及其可能的須要而定義的。app

例如:你可能有一頁存儲你的應用須要的字符串類型的title字段的集合。這兒這個模式是很是符合當前情形的,即便它沒有被明確的定義。但若是這個數據結果改變超時,尤爲是若是原來的數據沒有被遷移到新的數據結構,這就成了問題(在一些無模式的存儲引擎上是至關有問題的)。例如,你可能有下面這樣的 Ruby代碼:框架

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

這樣,針對每個有「title」字段並返回一個String的文檔,它都能正常工做。然而,對於那些使用不一樣字段名字(例如:post_title)或者根本沒有標題字段的文檔來講,它將不能正常工做。爲了處理這種狀況,你須要將代碼調整爲下面內容:ide

  1. if post.title 
  2.  
  3. post_slug = post.title.downcase.gsub(/\W+/, '-'
  4.  
  5. else 
  6.  
  7. # ... 
  8.  
  9. end 

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

無模式存儲引擎但願經過刪除對模式的限制的方式,讓你的工做變得更簡單。但現實的狀況是,確保數據一致性的責任推到了用戶本身的身上。有時候無模式引擎能夠工做,但我打賭,更多的時候是事與願違。

好數據庫的需求

有了更多的特殊需求後,迫使Olery尋求一款更好的數據庫來解決問題。對於系統,特別是數據庫,Olery很是注重如下幾點:

  • 一致性
  • 數據和系統行爲的可視化
  • 正確性和明確性
  • 可拓展

一致性是重要的在於它有助於幫助Olery對系統設定明確的指望。若是數據老是按照一樣的方式存儲,那麼系統能夠很方便的使用這些數據。若是在數據庫層面要求表的莫一列必須存在,那麼在應用層面就不用檢查這列數據是否存在。數據庫即便實在高壓狀況下,也必須保證每一次操做的完整性。沒有什麼事情比單純的插入數據,過了幾分鐘後卻找不到數據的事更讓人沮喪了。

可見性包含了兩點:系統自己以及從中獲取數據的容易程度。若是一個系統出錯那麼應該易於調試。反過來,用戶應很容易查到想要查詢的數據。

正確性是指系統的行爲如Olery所指望的那樣。若是某個字段定義爲一個數值型,沒有人能夠像其中插入文本。這方面MySQL是臭名昭著,一旦你這樣作你將獲得僞結果。

可擴展性不只針對性能而言,並且也涉及財務方面和系統可以多麼好地應對不斷變化的需求。一個系統在沒有大量資金成本或減緩系統所依賴的開發週期狀況下,很難表現得很是好。

 

放棄MongoDB

上面的需求牢記於心後,Olery就開始尋找一個取代MongoDB的數據庫。上面提到的特性一般是傳統RDBM特徵的一組核心集,因此Olery鎖定了兩個候選者:MySQL和PostgreSQL。

原本,MySQL是第一候選,由於Olery的一些關鍵數據已經在使用它存儲。然而,MySQL也有一些問題。例如,當將一個字段定義爲int(11)時,你卻能夠輕鬆地向該字段插入文本數據,由於MySQL會試圖對它進行轉換。下面是一些例子:

  1. mysql> create table example ( `number` int(11) not null ); 
  2.  
  3. Query OK, 0 rows affected (0.08 sec) 
  4.  
  5. mysql> insert into example (number) values (10); 
  6.  
  7. Query OK, 1 row affected (0.08 sec) 
  8.  
  9. mysql> insert into example (number) values ('wat'); 
  10.  
  11. Query OK, 1 row affected, 1 warning (0.10 sec) 
  12.  
  13. mysql> insert into example (number) values ('what is this 10 nonsense'); 
  14.  
  15. Query OK, 1 row affected, 1 warning (0.14 sec) 
  16.  
  17. mysql> insert into example (number) values ('10 a'); 
  18.  
  19. Query OK, 1 row affected, 1 warning (0.09 sec) 
  20.  
  21. mysql> select * from example; 
  22.  
  23. +--------+ 
  24.  
  25. | number | 
  26.  
  27. +--------+ 
  28.  
  29. | 10 | 
  30.  
  31. | 0 | 
  32.  
  33. | 0 | 
  34.  
  35. | 10 | 
  36.  
  37. +--------+ 
  38.  
  39. rows in set (0.00 sec) 

值得注意的是,MySQL在這些狀況下會發出警告。可是,僅僅是警告而已,它們一般(若非老是)會被忽略。

此外,MySQL的另外一個問題是,任何表的修改操做(例如:添加一列)都會致使表被鎖,此時將沒法進行讀或寫操做。這就意味着,使用這種表的任何操做都不得不等待修改完成以後才能進行。對於包含有大量數據的表,這可能會花費幾個小時才能完成,極可能會致使應用程序宕機。這已經致使一些公司(例如 SoundCloud)不得不本身開發工具(例如lhm)來解決該問題。

瞭解到上面的問題後,Olery開始考察PostgreSQL。PostgreSQL能夠解決不少MySQL不能解決的問題。例如,PostgreSQL中你不能將文本數據插入一個數字字段:

  1. olery_development=# create table example ( number int not null ); 
  2.  
  3. CREATE TABLE 
  4.  
  5. olery_development=# insert into example (number) values (10); 
  6.  
  7. INSERT 0 1 
  8.  
  9. olery_development=# insert into example (number) values ('wat'); 
  10.  
  11. ERROR: invalid input syntax for integer: "wat" 
  12.  
  13. LINE 1: insert into example (number) values ('wat'); 
  14.  
  15.  
  16. olery_development=# insert into example (number) values ('what is this 10 nonsense'); 
  17.  
  18. ERROR: invalid input syntax for integer: "what is this 10 nonsense" 
  19.  
  20. LINE 1: insert into example (number) values ('what is this 10 nonsen... 
  21.  
  22.  
  23. olery_development=# insert into example (number) values ('10 a'); 
  24.  
  25. ERROR: invalid input syntax for integer: "10 a" 
  26.  
  27. LINE 1: insert into example (number) values ('10 a'); 

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

還有其餘各類有趣的功能,如在 PostgreSQL 能夠:trigram 爲基礎的索引和檢索,全文檢索,支持JSON查詢,支持查詢/存儲鍵-值對,支持發佈/訂閱等更多。

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

 

遷移到PostgreSQL

最後,爲了在所關心的各類項目之中達到平衡,Olery決定使用PostgreSQL。可是,將整個平臺從MongoDB遷移到一個大相徑庭的數據庫並非很容易的事。爲了使轉移工做簡單化,Olery將此過程分紅了3個步驟:

  • 搭建一個PostgreSQL數據庫,並遷移數據的一個小子集。
  • 更新全部依賴於MongoDB的應用程序,連同任何須要的重構,都用依賴於PostgreSQL的程序替代。
  • 將產品數據遷移到新數據庫上,而後部署新平臺。

部分數據遷移

在考慮把全部數據遷移到新數據庫以前,Olery先遷移了一小部分數據來作測試。若是僅僅是遷移一小部分數據,就有很是多的麻煩的話,那麼數據庫遷移也就沒什麼意義了。

儘管有現成的工具能夠利用,但仍是有些數據(好比,列重命名,數據類型不一致)要作轉換,對於這些數據Olery本身開發了些工具。這些工具中,大部分都是Ruby寫的一次性腳步,用於刪除一些評論,整理數據編碼,修正主鍵發生序列等等。

在測試開始階段儘管有些數據上的問題,並無出現大的會阻礙遷移的問題。例如,有些用戶提交的數據沒有徹底按格式編碼,致使這些數據被從新編碼以前,不能被導入到新數據庫。例外一個有意思的改變是,以前評論的數據存的是評論用的語言的名稱(如「荷蘭語」,「英語」等),如今改了存語言的編碼,由於 Olery新的語義分析系統使用的是語言編碼,而再也不是語言名稱。

更新應用

目前爲止,花費時間最多的就是更新應用,尤爲是那些嚴重依賴MongoDB聚合框架的應用。扔掉那少數幾個遺留的Rails應用吧,光是測試就會花掉你幾個星期的時間。更新應用的過程大體以下:

  • 用PostgreSQL的相關代碼來替換掉MongoDB的驅動/設置模塊的代碼
  • 運行測試
  • 修復Bugs
  • 反覆運行測試,直到全部測試經過

對於非Rails應用,Olery推薦使用 Sequel,對於Rails應用,Olery如今還沒法擺脫ActiveRecord(至少是如今)。Sequel是一個很是好的數據庫工具集,它支持絕大多數(若是不是所有)咱們想使用的PostgreSQL特性。相較於ActiveRecord,它基於DSL的query要強大的多,儘管可能耗時會有點長。

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

  1. SELECT locale,count(*) AS amount, 
  2.  
  3. (count(*) / sum(count(*)) OVER ()) * 100.0 AS percentageFROM users 
  4.  
  5. GROUP BY localeORDER BY percentage DESC
  6.  
  7. 在咱們的例子中,將會產生如下輸出(當使用PostgreSQL命令行界面時): 
  8.  
  9. locale | amount | percentage 
  10.  
  11. --------+--------+-------------------------- 
  12.  
  13. en | 2779 | 85.193133047210300429000 
  14.  
  15. nl | 386 | 11.833231146535867566000 
  16.  
  17. it | 40 | 1.226241569589209074000 
  18.  
  19. de | 25 | 0.766400980993255671000 
  20.  
  21. ru | 17 | 0.521152667075413857000 
  22.  
  23. | 7 | 0.214592274678111588000 
  24.  
  25. fr | 4 | 0.122624156958920907000 
  26.  
  27. ja | 1 | 0.030656039239730227000 
  28.  
  29. ar-AE | 1 | 0.030656039239730227000 
  30.  
  31. eng | 1 | 0.030656039239730227000 
  32.  
  33. zh-CN | 1 | 0.030656039239730227000 
  34.  
  35. (11 rows

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

  1. star = Sequel.lit('*')User.select(:locale
  2.  
  3. .select_append { count(star).as(:amount) } 
  4.  
  5. .select_append { ((count(star) / sum(count(star)).over) * 100.0).as(:percentage) } 
  6.  
  7. .group(:locale
  8.  
  9. .order(Sequel.desc(:percentage)) 

若是你不喜歡使用「Sequel.lit(「*」)」,你也可使用下面的語法:

  1. User.select(:locale
  2.  
  3. .select_append { count(users.*).as(:amount) } 
  4.  
  5. .select_append { ((count(users.*) / sum(count(users.*)).over) * 100.0).as(:percentage) } 
  6.  
  7. .group(:locale
  8.  
  9. .order(Sequel.desc(:percentage)) 

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

將來可能也會將Olery的Rails應用程序遷移到Sequel,可是考慮到Rails與ActiveRecord耦合得如此緊密,因此Olery還不徹底肯定這是否值得花費時間和精力。

 

遷移生產數據

最終Olery來到遷移生產數據的過程。通常有兩種方法來作這件事:

  • 關掉整個平臺,直到全部數據都已遷移完成。
  • 遷移數據的同時保持系統運行。

第一個選項具備一個明顯的缺點:停機時間。第二個選項不須要停機可是很難處理。例如,在這個方案中,當你遷移數據的同時,你必需要考慮全部將要添加的數據,不然你就會損失數據。

幸運的是,Olery有一個獨特的方案就是Olery的數據庫的絕大多數寫操做都是至關按期的,常常變化的數據(例如用戶通信錄信息)只佔總數據量的一小部分,相比起Olery檢查數據,遷移它們花費的時間至關的小。

這部分的基本流程是:

  • 遷移關鍵數據,例如用戶、合同和那些不管如何都沒法承擔損失的數據。
  • 遷移不那麼關鍵的數據(咱們能夠從新收集,從新計算等的數據)
  • 測試是否全部事情都已完成,並運行在一組分離的服務器上。
  • 將生產環境轉換到新的服務器上。
  • 從新遷移第一步的數據,確保在遷移過程當中產生的數據沒有丟失。

到目前爲止,第二步花費的時間最長,大約爲24小時。另外一方面,遷移步驟1和5中提到的數據只花了45分鐘。

結論

Olery遷移完成而且直到很是滿意大概過去了一個月。到如今爲止除了那些積極的影響,還曾在各類狀況中讓應用的性能大幅提升。舉例來講,Olery的 酒店評論數據API(Hotel Review Data API)(在Sinatra運行)相比遷移以前交互延遲變低了許多:

軟件公司爲什麼要放棄MongoDB?

 

遷移是在1月21日開始的,高峯表示應用性能的硬重啓(在處理期間致使交互時間輕微變慢)。在21日以後交互的平均時間大體是原來的一半。

在另一種被Olery稱做「評論持久化」(譯者注:即存儲評論)的過程當中,Olery發現了性能上巨大的提高。後臺程序目標很簡單:保存評論數據(評論內容,評論分數等等)。當最終完成了爲遷移工做作的不少大的更改後,結果使人振奮:

軟件公司爲什麼要放棄MongoDB?

 

抓取器也變的更快了:

軟件公司爲什麼要放棄MongoDB?

 

抓取器性能提高沒有評論存儲的過程那樣大,由於抓取器只用數據庫來查詢某個評論是否存在(一個相對很快的操做),因此這樣的結果並不很使人吃驚。

最後來到程序裏用來調度抓取過程的進程(簡單稱之爲「調度器」):

軟件公司爲什麼要放棄MongoDB?

 

 

由於調度器只是以固定頻度運行,這個圖可能有點難以理解,可是無論怎樣,在遷移以後有一個很清晰的平均處理時間的降低。

相關文章
相關標籤/搜索