Mongoid如何处理对类定义变更?

huangz created at
97c45b49c5861500a15c3f9a691b7d4b

在Python的MongoEngine(MongoDB ODB)中,数据和类定义绑定,一旦修改类,比如增加获删除一个字段/方法,再取出旧对象的数据就会出错,唯一的办法是写一个“一次性”程序,将所有旧的类定义转化成新的类定义对象,非常麻烦。

请教一下@Rei,campo用的Mongoid是怎么处理这种问题的?

6 people marked
5aec84cd0b5479a0d1d89b6ffa2a9a20
Rei

差不多的,数据迁移一向是需要谨慎测试和备份但是又一次性的活。

mongoid 修改字段取旧数据不会出错,只会缺失字段值。

试过用 mongoid_rails_migrations,相当于一次性的脚本。有些可以重复使用或者稍微修改可以适应新需求的放在 lib/task。

如果 MongoEngine 修改字段会报错,那么这个映射层太紧了。Mongodb 本身功能足够丰富了,不需要用太厚重的包裹。mongoid 定义 field 只相当于添加一些便利方法,文档对象还是可以像 hash 那样用。

5aec84cd0b5479a0d1d89b6ffa2a9a20
Rei

不过换个角度看,数据模式不对就报错强制开发者写迁移脚本,适合那些希望清楚知道数据库里面有什么数据的人,也算一种风格。

97c45b49c5861500a15c3f9a691b7d4b
updated at

好的,明白了。

如果只能有这种模式的话,似乎对频繁修改模型的网站来说很苦阿,修改一下,更新对象数据,再修改一下,再更新对象数据。。。

话说NoSQL的两大特点:1,速度;2,灵活性;用OXM来使用NoSQL,似乎就是丢掉了灵活性,只剩下速度了,但要求更大的灵活性,似乎又要什么都自己来写了,真是矛盾呢。

5aec84cd0b5479a0d1d89b6ffa2a9a20
Rei

用mongodb的时候虽然会用ODM,但本质还是操纵文档。模式变更的时候需要考虑:老数据有没有需要清理的过期数据、有没有需要新增的必须字段、有没有需要转换处理的字段。有的话就要写迁移脚本。

虽然没了数据库模式迁移,但还是有数据迁移。

97c45b49c5861500a15c3f9a691b7d4b
updated at

刚才用100kb的小文本文件,用1000次循环,在我的本本上,对redis(via redis-py),mongodb(via py-mongo)和ZODB(Zope的数据库,OODB,直接使用key-value模式,没有依附其他数据结构)进行了小规模写入和读取测试。

发现一些结果,有些有点意外:

逐次写入

也就是

for record in file:
    db.insert(record)
    db.save()

Mongodb没有强制将数据写入硬盘的函数,忽略。
ZODB没有异步模式,只能对每一条record调用一次save(commit),用了50秒。
Redis出奇地使用了约2分钟之多,我想是因为Redis的同步save设计还没不成熟的原因。

连续写入

也就是

for record in file:
    db.insert(record)
db.save() # if exists

使用Mongodb的collection.insert(record),速度比Redis.set(record)稍快一点,MongoDB 1.x秒,而Redis 2秒左右,考虑到Mongodb和Redis默认都是使用异步模式,速度相差不大,算是意料之中。
ZODB没有连续写入的方法,忽略。

读取

for i in range(1000):
    copy_to_a_list(list[i], db.get[i])

无索引情况下,ZODB是0.1x,Mongodb是1秒多,Redis是0.3秒。

有索引/缓存情况下,ZODB是0.004秒,MongoDB仍是1秒多,比原来快了零点几秒,Redis是直接key-value,没有东西可索引的,速度和无索引情况一样。

暂时总结

Redis的写入情况和MongoDB相似,但Redis的读取速度比MongoDB稍快。
MongoDB的索引似乎效果不大。
ZODB的读取速度惊人,秒杀MongoDB和Redis;但没有异步写入是它的硬伤,可尝试以扬长避短,考虑将它用在“读取多,写入小”的情况下。

5aec84cd0b5479a0d1d89b6ffa2a9a20
Rei

redis 的结果比较奇怪,它应该超过 memcached 的速度这一级别。我有空也测一下。

E4599614be424ebec4e12f583efdb84d

@huangz
其实redis是很不适合用来做持久存储的,它的save命令每次都会对整个数据集进行快照,所以效率低是很正常的。如果使用的不是append only,那每次save的磁盘io量就等于整个数据集的大小。使用append only的话磁盘io少一些,但对整个内存内容进行snapshot本身就很花时间。你可以用bgsave,不过它不是同步的是异步的,耗时也一点不少。不是它这个的设计不成熟,而是它注重的只是内存数据库。作者也有试着向diskstore方向发展,参考:http://timyang.net/data/redis-diskstore/
ZODB比redis的读取速度快的原因是,它的数据都在python本身的runtime里面,不需要像redis一样通过socket去连接外部的服务器,不需要编译和解释命令,就只是一次dict的查找。不一样的东西,其实不具备可比性
我有一个400M+的redis数据库,不使用vm,使用appendonly,做一次save需要几秒时间。10G数据的话,首先你要确保内存有10G+大(用vm的话性能就更差了),然后……save一次,估计得几十秒。它根本就不适于用来跟mongodb比较,因为是不同的东西。redis是内存数据库,涉及到磁盘IO的它就痿了

97c45b49c5861500a15c3f9a691b7d4b
updated at

@reus

正想去看看Redis的save方法的相关资料,你解决了我对save方法的疑问,3Q。
我刚才又进行了一些测试,情况和你说的类似。

你说的ZODB数据常驻运行时而造成速度优势的问题,我特意分开了写入和读取步骤,发现即使那样,时间还是没有变化,ZODB的读取速度比Redis和MongoDB都快。

我估计造成区别的原因是Redis和MongoDB都是网络服务(如你所说),而ZODB的数据库则是一个文件系统,一个open函数就能将数据读出来,这里是ZODB比Redis和MongoDB都占优势的地方。

但我怀疑这种模式在ZODB数据库包含大量数据之后,是否还能继续保持这种优势,这个要等研究ZODB的原理和代码之后才能有进一步理解。

之后我想做数据量更大的测试,但遗憾地发现我没有那么大的内存,本来还想继续测试,但是想来那么小的数据集表示不了什么,还是就此打住好了。

97c45b49c5861500a15c3f9a691b7d4b
updated at

环境 Linux mypad 2.6.39-ARCH #1 SMP PREEMPT Tue Jun 7 05:49:02 UTC 2011 i686 Intel(R) Core(TM)2 Duo CPU T6570 @ 2.10GHz GenuineIntel GNU/Linux

条件,100kb文件,循环写入/读取1000次,每个测试方法是分开运行的,比如 python -m unittest testFile.TestCase.testMethod && python -m unittest ...

Zodb,同步,第一个是写入时间,第二个是无内存缓存的查询,第二个似乎是有内存缓存的查询。
Ran 1 test in 50.151s

Ran 1 test in 0.038s

Ran 1 test in 0.003s

mongodb 异步,第一个是写入时间,第二个是无索引查询,第三个是建立索引的时间,第四个是带索引的查询
Ran 1 test in 1.450s

Ran 1 test in 1.159s

Ran 1 test in 0.002s

Ran 1 test in 1.137s

redis,默认设置(不带save或bgsave),后面是查询时间
Ran 1 test in 1.002s

Ran 1 test in 0.296s

redis,在所有数据写入之后调用save,后面是查询时间
Ran 1 test in 1.287s

Ran 1 test in 0.299s

redis,在所有数据写入之后调用bgsave,后面是查询时间
Ran 1 test in 1.004s

Ran 1 test in 0.323s

5aec84cd0b5479a0d1d89b6ffa2a9a20
Rei

@huangz 要贴脚本啊。

才看明白刚才用的是 redis save。我觉得redis是拿来做半持久化的,当作持久层用是不合适的。

97c45b49c5861500a15c3f9a691b7d4b

@Rei 似乎勉强一点也可以做数据库用,只是一旦意外down机,会丢失部分数据。。。真刺激阿。。。

E4599614be424ebec4e12f583efdb84d

@huangz 可以用replication,类似多机热备的结构

E4599614be424ebec4e12f583efdb84d

@huangz 之前的说法有错误,如果redis配置了使用aof,那是不会遍历整个db的,而只是把增量数据存入磁盘
默认是不启用aof的,你可以试下启用aof再测试下,应该会比现在的结果快
不过调用save命令就一定会产生全库遍历,aof是通过配置选项appendfsync决定是否每次查询后都将结果写入磁盘的,不需要调用save命令

97c45b49c5861500a15c3f9a691b7d4b