跳转到主要内容

Mongo持久化后端

项目描述

Mongo数据持久化

本文档概述了mongopersist包的一般功能。mongopersist是持久化Python对象的Mongo存储实现。它不是ZODB的存储。

mongopersist的目标是提供一个数据管理器,该管理器在事务边界将对象序列化到Mongo。Mongo数据管理器是一个持久化数据管理器,它在事务边界处理事件(参见transaction.interfaces.IDataManager),以及从持久化框架接收事件(参见persistent.interfaces.IPersistentDataManager)。

数据管理器的实例应该与事务的生存期相同,这意味着假设你在创建新事务时创建一个新的数据管理器。

>>> import transaction

注意:conn对象是pymongo.connection.Connection实例。在这种情况下,我们的测试使用的是mongopersist_test数据库。

现在让我们定义一个简单的持久化对象

>>> import datetime
>>> import persistent
>>> class Person(persistent.Persistent):
...
...     def __init__(self, name, phone=None, address=None, friends=None,
...                  visited=(), birthday=None):
...         self.name = name
...         self.address = address
...         self.friends = friends or {}
...         self.visited = visited
...         self.phone = phone
...         self.birthday = birthday
...         self.today = datetime.datetime.now()
...
...     def __str__(self):
...         return self.name
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

我们稍后将填写其他对象。但现在,让我们创建一个新的个人并将其存储在Mongo中

>>> stephan = Person(u'Stephan')
>>> stephan
<Person Stephan>

数据管理器提供了一个具有root属性的特例,其中可以存储对象树根。它是特殊的,因为它会立即将数据写入数据库

>>> dm.root['stephan'] = stephan
>>> dm.root['stephan']
<Person Stephan>

自定义持久化集合

默认情况下,持久化对象存储在一个具有类Python路径的集合中

>>> from mongopersist import serialize
>>> person_cn = serialize.get_dotted_name(Person)
>>> person_cn
'__main__.Person'
>>> import pprint
>>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
[{u'_id': ObjectId('4e7ddf12e138237403000000'),
  u'address': None,
  u'birthday': None,
  u'friends': {},
  u'name': u'Stephan',
  u'phone': None,
  u'today': datetime.datetime(2011, 10, 1, 9, 45),
  u'visited': []}]

如你所见,存储的个人文档看起来非常像Mongo。但哦,我忘记指定Stephan的全名了。让我们来指定它

>>> dm.root['stephan'].name = u'Stephan Richter'

这一次,数据不会自动保存

>>> conn[DBNAME][person_cn].find_one()['name']
u'Stephan'

因此,我们首先需要提交事务

>>> transaction.commit()
>>> conn[DBNAME][person_cn].find_one()['name']
u'Stephan Richter'

现在让我们为Stephan添加一个地址。地址也是持久化对象

>>> class Address(persistent.Persistent):
...     _p_mongo_collection = 'address'
...
...     def __init__(self, city, zip):
...         self.city = city
...         self.zip = zip
...
...     def __str__(self):
...         return '%s (%s)' %(self.city, self.zip)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

MongoPersist支持一个特殊属性_p_mongo_collection,允许你指定要使用的自定义集合。

>>> stephan = dm.root['stephan']
>>> stephan.address = Address('Maynard', '01754')
>>> stephan.address
<Address Maynard (01754)>

请注意,地址不会立即保存到数据库中

>>> list(conn[DBNAME]['address'].find())
[]

但一旦我们提交事务,所有内容都可用

>>> transaction.commit()
>>> pprint.pprint(list(conn[DBNAME]['address'].find()))
[{u'_id': ObjectId('4e7de388e1382377f4000003'),
  u'city': u'Maynard',
  u'zip': u'01754'}]
>>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
[{u'_id': ObjectId('4e7ddf12e138237403000000'),
  u'address': DBRef(u'address',
                    ObjectId('4e7ddf12e138237403000000'),
                    u'mongopersist_test'),
  u'birthday': None,
  u'friends': {},
  u'name': u'Stephan Richter',
  u'phone': None,
  u'today': datetime.datetime(2011, 10, 1, 9, 45)
  u'visited': []}]
>>> dm.root['stephan'].address
<Address Maynard (01754)>

非持久化对象

如你所见,即使是引用看起来也很整洁,并使用标准的MongoDB引用构造。但对于任意非持久化但可序列化的对象怎么办?好吧,让我们为这个创建一个电话号码对象

>>> class Phone(object):
...
...     def __init__(self, country, area, number):
...         self.country = country
...         self.area = area
...         self.number = number
...
...     def __str__(self):
...         return '%s-%s-%s' %(self.country, self.area, self.number)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)
>>> dm.root['stephan'].phone = Phone('+1', '978', '394-5124')
>>> dm.root['stephan'].phone
<Phone +1-978-394-5124>

现在让我们提交事务并再次查看Mongo文档

>>> transaction.commit()
>>> dm.root['stephan'].phone
<Phone +1-978-394-5124>
>>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
[{u'_id': ObjectId('4e7ddf12e138237403000000'),
  u'address': DBRef(u'address',
                    ObjectId('4e7ddf12e138237403000000'),
                    u'mongopersist_test'),
  u'birthday': None,
  u'friends': {},
  u'name': u'Stephan Richter',
  u'phone': {u'_py_type': u'__main__.Phone',
             u'area': u'978',
             u'country': u'+1',
             u'number': u'394-5124'},
  u'today': datetime.datetime(2011, 10, 1, 9, 45)
  u'visited': []}]

如你所见,对于任意非持久化对象,我们需要在子文档中添加一个小提示,但它非常小。如果__reduce__方法返回一个更复杂的结构,就会写入更多元数据。我们将在存储日期和其他任意数据时看到这一点。

>>> dm.root['stephan'].friends = {'roy': Person(u'Roy Mathew')}
>>> dm.root['stephan'].visited = (u'Germany', u'USA')
>>> dm.root['stephan'].birthday = datetime.date(1980, 1, 25)
>>> transaction.commit()
>>> dm.root['stephan'].friends
{u'roy': <Person Roy Mathew>}
>>> dm.root['stephan'].visited
[u'Germany', u'USA']
>>> dm.root['stephan'].birthday
datetime.date(1980, 1, 25)

如你所见,字典键始终转换为Unicode,而元组始终作为列表维护,因为BSON没有两种序列类型。

>>> pprint.pprint(conn[DBNAME][person_cn].find_one(
...     {'name': 'Stephan Richter'}))
{u'_id': ObjectId('4e7df744e138230a3e000000'),
 u'address': DBRef(u'address',
                   ObjectId('4e7df744e138230a3e000003'),
                   u'mongopersist_test'),
 u'birthday': {u'_py_factory': u'datetime.date',
               u'_py_factory_args': [Binary('\x07\xbc\x01\x19', 0)]},
 u'friends': {u'roy': DBRef(u'__main__.Person',
                            ObjectId('4e7df745e138230a3e000004'),
                            u'mongopersist_test')},
 u'name': u'Stephan Richter',
 u'phone': {u'_py_type': u'__main__.Phone',
            u'area': u'978',
            u'country': u'+1',
            u'number': u'394-5124'},
 u'today': datetime.datetime(2011, 9, 24, 11, 29, 8, 930000),
 u'visited': [u'Germany', u'USA']}

自定义序列化器

如您所见,生日的序列化几乎不理想。然而,我们可以提供一个自定义序列化器,该序列化器使用序号来存储数据。

>>> class DateSerializer(serialize.ObjectSerializer):
...
...     def can_read(self, state):
...         return isinstance(state, dict) and \
...                state.get('_py_type') == 'datetime.date'
...
...     def read(self, state):
...         return datetime.date.fromordinal(state['ordinal'])
...
...     def can_write(self, obj):
...         return isinstance(obj, datetime.date)
...
...     def write(self, obj):
...         return {'_py_type': 'datetime.date',
...                 'ordinal': obj.toordinal()}
>>> serialize.SERIALIZERS.append(DateSerializer())
>>> dm.root['stephan']._p_changed = True
>>> transaction.commit()

让我们再看看

>>> dm.root['stephan'].birthday
datetime.date(1980, 1, 25)
>>> pprint.pprint(conn[DBNAME][person_cn].find_one(
...     {'name': 'Stephan Richter'}))
{u'_id': ObjectId('4e7df803e138230aeb000000'),
 u'address': DBRef(u'address',
                   ObjectId('4e7df803e138230aeb000003'),
                   u'mongopersist_test'),
 u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839},
 u'friends': {u'roy': DBRef(u'__main__.Person',
                            ObjectId('4e7df803e138230aeb000004'),
                            u'mongopersist_test')},
 u'name': u'Stephan Richter',
 u'phone': {u'_py_type': u'__main__.Phone',
            u'area': u'978',
            u'country': u'+1',
            u'number': u'394-5124'},
 u'today': datetime.datetime(2011, 9, 24, 11, 32, 19, 640000),
 u'visited': [u'Germany', u'USA']}

好多了!

持久化对象作为子文档

为了给开发者提供更多控制,让哪些对象拥有自己的集合以及哪些没有,开发者可以提供一个特殊标志来标记持久化类,使其成为父对象文档的一部分

>>> class Car(persistent.Persistent):
...     _p_mongo_sub_object = True
...
...     def __init__(self, year, make, model):
...         self.year = year
...         self.make = make
...         self.model = model
...
...     def __str__(self):
...         return '%s %s %s' %(self.year, self.make, self.model)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

_p_mongo_sub_object用于标记一个对象类型,使其仅是另一个文档的一部分

>>> dm.root['stephan'].car = car = Car('2005', 'Ford', 'Explorer')
>>> transaction.commit()
>>> dm.root['stephan'].car
<Car 2005 Ford Explorer>
>>> pprint.pprint(conn[DBNAME][person_cn].find_one(
...     {'name': 'Stephan Richter'}))
{u'_id': ObjectId('4e7dfac7e138230d3d000000'),
 u'address': DBRef(u'address',
                   ObjectId('4e7dfac7e138230d3d000003'),
                   u'mongopersist_test'),
 u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839},
 u'car': {u'_py_persistent_type': u'__main__.Car',
          u'make': u'Ford',
          u'model': u'Explorer',
          u'year': u'2005'},
 u'friends': {u'roy': DBRef(u'__main__.Person',
                            ObjectId('4e7dfac7e138230d3d000004'),
                            u'mongopersist_test')},
 u'name': u'Stephan Richter',
 u'phone': {u'_py_type': u'__main__.Phone',
            u'area': u'978',
            u'country': u'+1',
            u'number': u'394-5124'},
 u'today': datetime.datetime(2011, 9, 24, 11, 44, 7, 662000),
 u'visited': [u'Germany', u'USA']}

我们想要对象持久化的原因是为了使它们能够自动获取更改

>>> dm.root['stephan'].car.year = '2004'
>>> transaction.commit()
>>> dm.root['stephan'].car
<Car 2004 Ford Explorer>

集合共享

由于Mongo非常灵活,有时将多种类型(类似)的对象存储在同一个集合中是有意义的。在这种情况下,您需要指示对象类型将它的Python路径作为文档的一部分存储。

警告:请注意,这种方法效率较低,因为必须加载文档才能创建一个幽灵,这会导致更多的数据库访问。

>>> class ExtendedAddress(Address):
...
...     def __init__(self, city, zip, country):
...         super(ExtendedAddress, self).__init__(city, zip)
...         self.country = country
...
...     def __str__(self):
...         return '%s (%s) in %s' %(self.city, self.zip, self.country)

为了实现集合共享,您只需创建另一个具有与其他(子类化将确保这一点)相同的_p_mongo_collection字符串的类。

现在让我们给斯蒂芬一个扩展地址。

>>> dm.root['stephan'].address2 = ExtendedAddress(
...     'Tettau', '01945', 'Germany')
>>> dm.root['stephan'].address2
<ExtendedAddress Tettau (01945) in Germany>
>>> transaction.commit()

当加载地址时,它们应该是正确的类型

>>> dm.root['stephan'].address
<Address Maynard (01754)>
>>> dm.root['stephan'].address2
<ExtendedAddress Tettau (01945) in Germany>

棘手的情况

基本可变类型的更改

棘手。我们如何让框架检测到像列表和字典这样的可变对象中的更改?答案是:我们跟踪它们属于哪个持久化对象,并提供持久化实现。

>>> type(dm.root['stephan'].friends)
 <class 'mongopersist.serialize.PersistentDict'>
>>> dm.root['stephan'].friends[u'roger'] = Person(u'Roger')
>>> transaction.commit()
>>> sorted(dm.root['stephan'].friends.keys())
[u'roger', u'roy']

对于列表来说也是如此

>>> type(dm.root['stephan'].visited)
 <class 'mongopersist.serialize.PersistentList'>
>>> dm.root['stephan'].visited.append('France')
>>> transaction.commit()
>>> dm.root['stephan'].visited
[u'Germany', u'USA', u'France']

循环非持久引用

存储在子文档中的任何可变对象都不能在对象树中有多个引用,因为没有全局引用。这些循环引用会被检测并报告

>>> class Top(persistent.Persistent):
...     foo = None
>>> class Foo(object):
...     bar = None
>>> class Bar(object):
...     foo = None
>>> top = Top()
>>> foo = Foo()
>>> bar = Bar()
>>> top.foo = foo
>>> foo.bar = bar
>>> bar.foo = foo
>>> dm.root['top'] = top
Traceback (most recent call last):
...
CircularReferenceError: <__main__.Foo object at 0x7fec75731890>

循环持久引用

通常,持久化对象之间的循环引用不是问题,因为我们总是只存储一个指向对象的链接。然而,有一种情况下,循环依赖成为问题。

如果您设置了一个具有循环引用的对象树,并将其一次添加到存储中,则必须在序列化期间插入对象,以便创建引用。但是,需要小心只创建一个最小的引用对象,以便系统不会尝试递归地减少状态。

>>> class PFoo(persistent.Persistent):
...     bar = None
>>> class PBar(persistent.Persistent):
...     foo = None
>>> top = Top()
>>> foo = PFoo()
>>> bar = PBar()
>>> top.foo = foo
>>> foo.bar = bar
>>> bar.foo = foo
>>> dm.root['ptop'] = top

容器和集合

既然我们已经谈了很多关于存储单个对象的血腥细节,那么关于映射整个集合的方法,比如一组人的集合,又该如何呢?

可以采取许多方法。以下实现定义了文档中的一个属性作为映射键,并为集合命名

>>> from mongopersist import mapping
>>> class People(mapping.MongoCollectionMapping):
...     __mongo_collection__ = person_cn
...     __mongo_mapping_key__ = 'short_name'

映射接受数据管理器作为参数。可以轻松创建一个子类来自动分配数据管理器。让我们看看

>>> People(dm).keys()
[]

列表中没有人的原因是因为还没有文档具有该键,或者键为null。让我们改变这一点

>>> People(dm)['stephan'] = dm.root['stephan']
>>> transaction.commit()
>>> People(dm).keys()
[u'stephan']
>>> People(dm)['stephan']
<Person Stephan Richter>

请注意,将“短名”属性设置在任何其他人的身上都会将其添加到映射中

>>> dm.root['stephan'].friends['roy'].short_name = 'roy'
>>> transaction.commit()
>>> sorted(People(dm).keys())
[u'roy', u'stephan']

写入冲突检测

由于Mongo不支持MVCC,它不提供写入冲突检测的概念。但是,可以使用文档上的序列号轻松实现简单的写入冲突检测。

让我们重置数据库并创建一个启用了冲突检测的数据管理器

>>> from mongopersist import conflict, datamanager
>>> conn.drop_database(DBNAME)
>>> dm2 = datamanager.MongoDataManager(
...     conn,
...     default_database=DBNAME,
...     root_database=DBNAME,
...     conflict_handler_factory=conflict.SimpleSerialConflictHandler)

现在我们添加一个人,并看到序列号已存储

>>> dm2.root['stephan'] = Person(u'Stephan')
>>> dm2.root['stephan']._p_serial
'\x00\x00\x00\x00\x00\x00\x00\x01'
>>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
{u'_id': ObjectId('4e7fe18de138233a5b000009'),
 u'_py_serial': 1,
 u'address': None,
 u'birthday': None,
 u'friends': {},
 u'name': u'Stephan',
 u'phone': None,
 u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000),
 u'visited': []}

接下来我们更改这个人并再次提交

>>> dm2.root['stephan'].name = u'Stephan <Unknown>'
>>> transaction.commit()
>>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
{u'_id': ObjectId('4e7fe18de138233a5b000009'),
 u'_py_serial': 2,
 u'address': None,
 u'birthday': None,
 u'friends': {},
 u'name': u'Stephan <Unknown>',
 u'phone': None,
 u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000),
 u'visited': []}

现在我们开始一个新的事务并做一些修改

>>> dm2.root['stephan'].name = u'Stephan Richter'

然而,在此期间,另一笔交易修改了该对象。(为了简化,我们在这里直接通过Mongo执行此操作。)

>>> _ = dm2._conn[DBNAME][person_cn].update(
...     {'name': u'Stephan <Unknown>'},
...     {'$set': {'name': u'Stephan R.', '_py_serial': 3}})
>>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
{u'_id': ObjectId('4e7fe1f4e138233ac4000009'),
 u'_py_serial': 3,
 u'address': None,
 u'birthday': None,
 u'friends': {},
 u'name': u'Stephan R.',
 u'phone': None,
 u'today': datetime.datetime(2011, 9, 25, 22, 22, 44, 343000),
 u'visited': []}

现在,我们的更改交易试图提交

>>> transaction.commit()
Traceback (most recent call last):
...
ConflictError: database conflict error
    (oid DBRef(u'__main__.Person',
               ObjectId('4e7ddf12e138237403000000'),
               u'mongopersist_test'),
     class Person,
     orig serial 2, cur serial 3, new serial 3)
>>> transaction.abort()

更改

0.8.4 (2013-06-13)

  • 在同一交易中修复插入后删除的问题。文档未被从Mongo中删除。

  • 修复了复杂对象的transaction.abort()行为。在交易中止后,_py_type信息不会丢失。

0.8.3 (2013-04-09)

  • 修复了MongoContainerIdNamesMongoContainerNone键上的add__setitem__行为。由于zope.container.contained.setitem得到了刚刚插入的对象,因此未触发ObjectAddedEventObjectMovedEvent

    MongoContainer始终需要_m_mapping_key并使用该对象的该属性来确定新键。

    IdNamesMongoContainer需要_m_mapping_keyNone并使用_id来确定新键。

0.8.2 (2013-04-03)

  • 修复了check_conflict:确保我们使用与对象相同的数据库和集合

0.8.1 (2013-03-19)

  • 修复了对象加载时由于直接分配给__name__而引起的_p_changed设置。这导致从容器中读取的所有对象在加载时都被标记为已更改。这也破坏了缓存性能。

0.8.0 (2013-02-09)

  • 功能:向集合包装器添加了find_objects()find_one_object(),因此每当您从数据管理器获取集合时,都可以通过find API直接加载对象。

  • 功能:为MongoContained对象添加了完全引用和加载其父对象的能力。这允许直接查询Mongo并从文档中创建对象,而无需通过容器进行,您可能不知道正确的容器。

0.7.7 (2013-02-08)

  • 错误:如果无法删除父属性和名称属性,则不会失败。

0.7.6 (2013-02-08)

  • 功能:切换到pymongo.MongoClient,设置默认的写入关注值,允许覆盖写入关注值。

0.7.5 (2013-02-06)

  • 测试:添加,清理测试

  • 错误:由于缺少文件,重新发布0.7.4版本

0.7.4 (2013-02-05)

  • 错误:由于UserDict实现了dict比较语义,任何空的MongoContainer都会等同于另一个空的。这种行为会导致对象更改无法被Mongo数据管理器正确识别。实现的解决方案是为Mongo容器实现默认的对象比较行为。

0.7.3 (2013-01-29)

  • 功能:更新到最新包版本,特别是pymongo 2.4.x。在这个版本中,pymongo不会重新导出objectiddbref

0.7.2 (2012-04-19)

  • 错误:为了避免在多线程环境中在Mongo容器中缓存MongoDataManager实例以避免在单个交易中存在多个MongoDataManagers,改用缓存IMongoDataManagerProvider。

0.7.1 (2012-04-13)

  • 性能:通过允许禁用记录的修改来略微改进了分析器。

  • 性能:在Mongo容器中添加了对_m_jar查找的缓存,因为计算出来相当昂贵。

  • 性能:使用懒散的哈希计算来处理DBRef。同时禁用对任意关键字参数的支持。这在大约2-4%的对象加载时间内产生了差异。

  • 错误:当缺少_py_serial时发生错误。这是由于0.6版本中的一个错误造成的。这还保护了第三方软件,这些软件不了解我们的元数据。

  • 性能:切换到repoze.lru(从lru),这要快得多。

  • 性能:为了避免过多的哈希计算,我们现在使用DBRef引用的哈希值作为缓存键。

  • 错误:ObjectId ids 在跨集合中不能保证唯一性。因此它们不适合作为全局缓存的键。所以我们使用完整的 DBRef 引用。

0.7.0 (2012-04-02)

  • 功能:一个新的 IConflictHandler 接口现在控制所有冲突解决的方面。以下是一些实现:

    • NoCheckConflictHandler:此处理程序不执行任何操作,当使用时,系统行为与之前将 detect_conflicts 标志设置为 False 时的行为相同。

    • SimpleSerialConflictHandler:此处理程序使用每个文档的序列号来跟踪版本,然后检测冲突。当检测到冲突时,会引发一个 ConflictError。此处理程序与将 detect_conflicts 设置为 True 相同。

    • ResolvingSerialConflictHandler:另一个序列处理程序,但它具有解决冲突的能力。为此,持久对象必须实现 _p_resolveConflict(orig_state, cur_state, new_state),它返回新的合并状态。(实验性)

    因此,数据管理器的 detect_conflicts 标志已被移除,并由 conflict_handler 属性替换。可以将 conflict_handler_factory 传递给数据管理器构造函数。工厂需要期望一个参数,即数据管理器。

  • 功能:新的 IdNamesMongoContainer 类使用Mongo ObjectId的自然名称作为容器中项的名称/键。不再需要处理生成名称的问题。当然,如果您过去指定了 None 作为键,它已经使用了对象 ID,但它存储在映射键字段中。现在对象 ID 在所有地方都直接使用。

  • 功能:当在持久对象上调用 setattr() 时,即使新值等于旧值,它也会被标记为已更改。为了最小化对 MongoDB 的写入,将最新数据库状态与新的状态进行比较,并且仅在检测到更改时才写入新状态。使用名为 serialize.IGNORE_IDENTICAL_DOCUMENTS(默认:True)的标志来控制此功能。(实验性)

  • 功能:现在 ConflictError 具有一个更有意义的 API。它不再仅引用对象和不同的序列号,而是实际上具有原始、当前和新状态文档。

  • 功能:现在在回滚事务时检测冲突。如果检测到冲突,实现的策略不会重置文档状态。

  • 功能:提供一个标志来打开 MongoDB 访问日志。默认情况下,该标志为 false,因为访问日志非常昂贵。

  • 功能:向 LoggingDecorator 添加了事务 ID。

  • 功能:添加了一个用于测试性能的小脚本。它并不非常复杂,但对于优化的一轮来说已经足够。

  • 功能:在所有级别上大幅提高了性能。这主要是通过删除不必要的数据库访问、更好的缓存和更高效的算法来实现的。这导致了 4-25 倍的加速。

    • 当解析类的路径时,现在会将结果缓存。更重要的是,查找失败也会缓存映射 path -> None。这很重要,因为 resolve() 方法的一个优化导致了很多失败的查找。

    • 在将dbref解析为类型时,如果知道集合中的文档存储其类型路径,我们会尝试使用文档尽早解析dbref。这样可以避免在不需要时频繁查询名称映射集合。

    • 获取对象文档以读取类路径时,现在将读取整个文档并将其存储在_latest_states字典中,以便其他代码可以获取并使用它。这应该可以避免从MongoDB中进行不必要的读取。

    • 大大提高了仅存储一种类型的对象且文档不存储类型的集合(即存储在名称映射集合中)的性能。

    • Mongo容器通过find()进行快速加载没有正确工作,因为setstate()没有将状态从幽灵状态更改为活动状态,因此状态再次从MongoDB加载并设置在对象上。现在我们使用新的_latest_states缓存,当通过适当渠道调用setstate()时查找文档。现在这种“快速加载”方法真正实现了O(1)数据库查找。

    • 为Mongo容器实现了更多映射方法,因此现在获取完整项目列表的所有方法都很快。

    • 当Mongo对象ID用作哈希键时,使用ID的哈希值。ObjectId类的__cmp__()方法太慢了。

    • ObjectWriter类中的对象缓存集合名称查找。

  • 错误:在生产环境中,我们曾多次遇到在文档中突然丢失一些状态的情况,这阻止了对象再次加载。原因是_original_states属性没有存储原始MongoDB文档,而是存储了一个修改过的文档。然而,由于这些状态在回滚时用于重置状态,因此修改过的文档被存储,使受影响的对象无法访问。

  • 错误:当一个事务被终止时,所有已加载对象的状态都会被重置。现在,只有修改过的对象状态被重置。这应该大大降低由于缺少完整MVCC而导致的问题(由于读取和修改对象的比率)。

  • 错误:当通过键/名称(find_*()方法)查找项时,您永远不会得到正确的对象,而是得到数据库中找到的第一个对象。这是由于用更通用的参数覆盖了搜索过滤器。

0.6.1 (2012-03-28)

  • 功能:在集合方法周围添加了相当详细的调试日志

0.6.0 (2012-03-12)

  • 功能:切换到乐观数据转储,这种方法通过早期和随数据到达来处理事务。在事务失败/终止时,所有更改都将取消。有关详细信息,请参阅optimistic-data-dumping.txt。以下是新功能的一些示例

    • 数据管理器跟踪在对象被修改之前的所有原始文档,因此可以进行任何更改。

    • 为数据管理器添加了API(DataManager.insert(obj))以将对象插入数据库。

    • 为数据管理器添加了API(DataManager.remove(obj))以从数据库中删除对象。

    • 可以在事务的任何阶段将数据刷新到Mongo(DataManager.flush()),同时保留完全撤销所有更改的能力。刷新具有以下特性

      • 在给定的交易期间,我们保证用户将始终收到相同的Python对象。这要求flush不会重置对象缓存。

      • _p_serial增加1。(在对象编写器中自动完成)

      • 对象从注册对象中删除,并将_p_changed标志设置为False

      • 在刷新之前,检测潜在冲突。

    • 实现了刷新策略:在执行任何查询之前,都会刷新更改。一个简单的包装器 CollectionWrapper 用于确保在正确的方法调用之前调用刷新。两个新的API方法 DataManager.get_collection(db_name, coll_name)DataManager.get_collection_from_object(obj) 允许快速获取包装后的集合。

  • 特性:将 processSpec() 重命名为 process_spec() 以符合包命名约定。

  • 特性:创建了一个 ProcessSpecDecorator,用于在 CollectionWrapper 类中处理 find()find_one()find_and_modify() 集合方法中的规范。

  • 特性:现在,当从容器中删除对象时,如果 _m_remove_documents 设置为 True,则 MongoContainer 类会从数据库中删除对象。默认设置为 True

  • 特性:当向 MongoContainer 添加项目时,如果键为 None,则选择 OID 作为键。ID 是完美的键,因为它们在集合内保证唯一。

  • 特性:由于人们不喜欢使用 None 键的 setitem 实现,我还添加了 MongoContainer.add(value, key=None) 方法,这使得指定键是可选的。如果键为 None,则默认使用 OID。

  • 特性:从 MongoContainer.find(...)MongoContainer.find_one(...) 方法中移除了 fields 参数,因为它没有被使用。

  • 特性:如果容器有 N 个项目,则需要 N+1 次查询才能完全加载项目列表。这是由于一次查询返回所有 DBRefs,然后使用一次查询来加载每个的州。现在,第一次查询加载所有完整状态,并使用 DataManager.setstate(obj, doc=None) 的扩展来使用之前查询的数据加载对象的州。

  • 特性:将 MongoContainer.get_collection() 更改为返回 CollectionWrapper 实例。

0.5.5 (2012-03-09)

  • 特性:将 ZODB 依赖项移动到测试依赖项。

  • 错误:当对象有 SimpleContainer 作为属性时,简单地加载此对象会导致它在事务结束时写入。罪魁祸首是包含 SimpleContainer 状态的持久字典。在状态加载期间修改了此字典,导致它被注册为已更改的对象,并标记为 _p_mongo_sub_object,具有原始对象作为 _p_mongo_doc_object

0.5.4 (2012-03-05)

  • 特性:通过 IMongoSpecProcessor 适配器添加了一个钩子,在每次查找之前调用以处理/记录规范。

0.5.3 (2012/01/16)

  • 错误:MongoContainer 没有发出任何 Zope 容器或生命周期事件。这已通过使用 zope.container.contained 辅助函数来解决。

0.5.2 (2012-01-13)

  • 特性:为 MongoContainer 类添加了一个接口,描述了额外的属性和方法。

0.5.1 (2011-12-22)

  • 错误:MongoContainer 类没有实现 IContainer 接口。

0.5.0 (2011-11-04)

  • 初始发布

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

mongopersist-0.8.4.zip (102.2 kB 查看哈希值)

上传时间:

由以下组织支持