何时在ZODB中提交数据

时间:2012-06-28 23:42:16

标签: python zodb

我正在努力处理由以下代码生成的数据:

for Gnodes in G.nodes()       # Gnodes iterates over 10000 values 
    Gvalue = someoperation(Gnodes)
    for Hnodes in H.nodes()   # Hnodes iterates over 10000 values 
        Hvalue =someoperation(Hnodes)
        score = SomeOperation on (Gvalue,Hvalue)
        dic_score.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])

由于字典很大(10000个键X 10000个列表,每个包含3个元素),因此很难将其保存在内存中。我正在寻找一个解决方案,它存储密钥:值(以列表的形式)对生成后立即。这里建议Writing and reading a dictionary in specific format (Python)将ZODB与Btree结合使用。

如果这太天真,请耐心等待,我的问题是,何时应该调用transaction.commit()来提交数据?如果我在内部循环结束时调用它,则生成的文件非常大(不确定原因)。这是一个片段:

storage = FileStorage('Data.fs')
db = DB(store)
connection = db.open()
root = connection.root()
btree_container = IOBTree
root[0] = btree_container 
for nodes in G.nodes()
    btree_container[nodes] = PersistentList () ## I was loosing data prior to doing this 

for Gnodes in G.nodes()       # Gnodes iterates over 10000 values 
    Gvalue = someoperation(Gnodes)
    for Hnodes in H.nodes()   # Hnodes iterates over 10000 values 
        Hvalue =someoperation(Hnodes)
        score = SomeOperation on (Gvalue,Hvalue)
        btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
        transaction.commit()

如果我在两个循环之外调用它会怎么样?类似的东西:

    ......
       ......
          score = SomeOperation on (Gvalue,Hvalue)
          btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
    transaction.commit()

在调用transaction.commit()之前,所有数据都会保留在内存中吗?同样,我不确定为什么,但这会导致磁盘上的文件较小。

我想最小化内存中保存的数据。任何指导将不胜感激!

2 个答案:

答案 0 :(得分:27)

您的目标是在内存限制内使您的流程易于管理。为了能够使用ZODB作为工具,您需要了解ZODB事务的工作方式以及如何使用它们。

为什么你的ZODB变得如此之大

首先,您需要了解事务提交在这里做了什么,这也解释了为什么您的Data.fs变得如此之大。

ZODB按事务写出数据,其中任何已更改的持久对象都会写入磁盘。这里重要的细节是已更改的持久对象; ZODB以持久对象为单位工作。

并非每个python值都是持久对象。如果我定义一个直接的python类,它将不是持久的,也不是任何内置的python类型,如int或list。另一方面,您定义的任何继承自persistence.Persistent 的类都是持久对象。 BTrees类的一组以及您在代码中使用的PeristentList继承自Persistent

现在,在事务提交中,任何已更改的持久对象都将作为该事务的一部分写入磁盘。因此,任何附加到的PersistentList对象都将写入其中到磁盘。 BTrees处理这个效率更高一点;他们存储Buckets,它们本身是持久的,而Buckets又保存了实际存储的对象。因此,对于您创建的每个新节点,都会将一个Bucket写入事务,而不是整个BTree结构。请注意,因为树中保存的项本身就是持久对象,所以只有对它们的引用才会存储在Bucket记录中。

现在,ZODB通过将事务数据附加到Data.fs文件来写入事务数据,并且它不会自动删除旧数据。它可以通过从商店中查找给定对象的最新版本来构造数据库的当前状态。这就是为什么你的Data.fs增长如此之多,你在提交事务时写出越来越大的PersistentList实例的新版本。

删除旧数据称为打包,类似于PostgreSQL和其他关系数据库中的VACUUM命令。只需在db变量上调用the .pack() method即可删除所有旧版本,或使用该方法的tdays参数设置限制要保留多少历史记录,第一个是time.time()时间戳(自纪元以来的秒数),您可以在此之前打包,days是过去从当前时间或{{保留的天数1}}如果指定的话。随着旧事务中的部分列表被删除,打包应该会大大减少您的数据文件。请注意,打包是一项昂贵的操作,因此可能需要一段时间,具体取决于数据集的大小。

使用事务管理内存

您正在尝试构建一个非常大的数据集,使用持久性来解决内存约束,并使用事务来尝试将内容刷新到磁盘。但是,通常情况下,使用事务提交信号已经完成了数据集的构建,可以将其用作一个原子整体。

这里需要使用的是保存点。保存点本质上是子事务,在整个事务期间您可以要求将数据临时存储在磁盘上。提交交易时,它们将成为永久性的。要创建保存点,请在事务上调用.savepoint method

t

在上面的示例中,我将for Gnodes in G.nodes(): # Gnodes iterates over 10000 values Gvalue = someoperation(Gnodes) for Hnodes in H.nodes(): # Hnodes iterates over 10000 values Hvalue =someoperation(Hnodes) score = SomeOperation on (Gvalue,Hvalue) btree_container.setdefault(Gnodes, PersistentList()).append( [Hnodes, score, -1 ]) transaction.savepoint(True) transaction.commit() 标志设置为True,这意味着:我不打算回滚到此保存点;某些存储不支持回滚,并且发出不需要的信号会使您的代码在这种情况下工作。

另请注意,optimistic在整个数据集处理完毕后发生,这就是提交应该实现的目标。

保存点的一个作用是调用ZODB缓存的垃圾收集,这意味着当前未使用的任何数据都将从内存中删除。

请注意“目前尚未使用”'在那里;如果您的任何代码保留变量中的大值,则无法从内存中清除数据。根据您向我们展示的代码,我可以确定,这看起来很好。但我不知道您的操作如何工作或如何生成节点;例如,小心避免在迭代器执行时在内存中构建完整列表,或者构建大型词典,其中引用了所有列表列表。

您可以尝试一下创建保存点的位置;你可以在每次处理一个transaction.commit()时创建一个,或者只有在完成HNodes循环时才能创建一个,就像我上面所做的那样。您正在按GNodes构建一个列表,因此无论如何都会在循环遍历所有GNodes时将其保留在内存中,并且只有在您完成构建它之后,刷新到磁盘可能才有意义。满。

但是,如果您发现需要更频繁地清除内存,则应考虑使用H.nodes()课程或BTrees.OOBTree.TreeSet课程而不是BTrees.IOBTree.BTree来分解数据转换为更持久的对象。 PersistentList是有序的,但不是(容易)可索引的,而TreeSet可以通过使用简单的递增索引键用作列表:

BTree

上面的代码使用BTree而不是PersistentList,每隔100 for i, Hnodes in enumerate(H.nodes()): ... btree_container.setdefault(Gnodes, IOBTree())[i] = [Hnodes, score, -1] if i % 100 == 0: transaction.savepoint(True) 处理一次创建一个保存点。因为BTree使用桶本身就是持久对象,所以整个结构可以更容易地刷新到保存点,而不必留在内存中来处理所有HNodes

答案 1 :(得分:1)

什么构成交易取决于应用程序中需要“原子”的内容。如果事务失败,它将回滚到其先前的状态(在最后一次提交之后)。从应用程序代码中可以看出,您希望为每个Gnode计算一个值。所以,你的提交可以在Gnodes循环结束时进入:

for Gnodes in G.nodes():       # Gnodes iterates over 10000 values 
    Gvalue = someoperation(Gnodes)
    for Hnodes in H.nodes():   # Hnodes iterates over 10000 values 
        Hvalue =someoperation(Hnodes)
        score = SomeOperation on (Gvalue,Hvalue)
        btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
    # once we calculate the value for a Gnodes, commit
    transaction.commit()

从您的代码中可以看出,“Hvalue”组合不依赖于Gvalue或Gnodes。   如果这是一项昂贵的操作,即使每个Gnodes计算1000次也是如此   它不会影响它的计算。所以,我会把它移出循环。

# Hnodes iterates over 10000 values
hvals = dict((Hnodes, someoperation(Hnodes)) for Hnodes in H.nodes())
# now you have mapping of Hnodes and Hvalues

for Gnodes in G.nodes():       # Gnodes iterates over 10000 values 
    Gvalue = someoperation(Gnodes)
    for Hnodes, Hvalue in hvals.iteritems(): 
        score = SomeOperation on (Gvalue,Hvalue)
        btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
    # once we calculate the value for a given Gnodes, commit
    transaction.commit()