软件公司为什么要放弃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?

 

 

由于调度器只是以固定频度运行,这个图可能有点难以理解,可是无论怎样,在迁移以后有一个很清晰的平均处理时间的降低。

相关文章
相关标签/搜索