再见 MongoDB,你好 PostgreSQL(二)

2015-03-18 22:55:46 · 作者: · 浏览: 137
费几个小时才能完成,很可能会导致应用程序宕机。这已经导致一些公司(例如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。但是,将整个平台从MongoDB迁移到一个截然不同的数据库并不是很容易的事。为了使转移工作简单化,我们将此过程分成了3个步骤:


部??数据迁移


在考虑把所有数据迁移到新数据库之前,我们先迁移了一小部分数据来做测试。如果仅仅是迁移一小部分数据,就有非常多的麻烦的话,那么数据库迁移也就没什么意义了。


尽管有现成的工具可以利用,但还是有些数据(比如,列重命名,数据类型不一致)要做转换,对于这些数据我们自己开发了些工具。这些工具中,大部分都是Ruby写的一次性脚步,用于删除一些评论,整理数据编码,修正主键发生序列等等。


在测试开始阶段尽管有些数据上的问题,并没有出现大的会阻碍迁移的问题。例如,有些用户提交的数据没有完全按格式编码,导致这些数据被重新编码之前,不能被导入到新数据库。例外一个有意思的改变是,之前评论的数据存的是评论用的语言的名称(如“荷兰语”,“英语”等),现在改了存语言的编码,因为我们新的语义分析系统使用的是语言编码,而不再是语言名称。


更新应用


目前为止,花费时间最多的就是更新应用,尤其是那些严重依赖MongoDB聚合框架的应用。扔掉那少数几个遗留的Rails应用吧,光是测试就会花掉你几个星期的时间。更新应用的过程大致如下:


对于非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