将大文件写入Blob存储时内存不足

3 投票
1 回答
1150 浏览
提问于 2025-04-17 10:27

我正在使用一个实验性的 blobstore 文件 API 来写一个包含一些事件数据的 CSV 文件。因为数据量很大,所以我在分批写入。我的代码在后台运行,有很多时间,但我快没内存了,我不明白为什么。

这是我的代码:

from __future__ import with_statement
from google.appengine.api import files

q = Event.all()

events = q.fetch(50)
while events:
    with files.open(blobname, 'a') as f:
        buf = StringIO()

        for event in events:
            buf.write(event.id)
            buf.write(',')
            buf.write(`event.logged`)
            buf.write(',')
            buf.write(event.type)
            buf.write(',')
            buf.write(event.timestamp)
            buf.write(',')

            needAmpersand = False
            for prop in event.dynamic_properties():
                if needAmpersand:
                    buf.write('&')
                needAmpersand = True
                buf.write(prop + '=' + str(getattr(event, prop)))
            buf.write('\n')

        f.write(buf.getvalue())
        buf.close()

    events = q.fetch(50)

files.finalize(blobname)

这段代码在处理事件的循环中大约运行了 20 次,然后就因为用掉了超过 140 MB 的内存而中止。Event 是这个应用程序特有的数据库模型。简单来说,Event 就是记录远程机器上发生的事情,之后这些事件会通过一个叫做 map reduce 的操作来生成统计数据,现在我只想下载它们。我们的数据库里有成千上万的事件(以后我们会换一种存储方式,但现在就这样)。

我注意到,使用 f.open 会在每次完成 with 语句后调用 f.close,因为 f.close() 是通过 f.__exit__() 来调用的。

之前的代码版本只是对每个要写入 'StringIO' 的元素调用了 f.write(..)。那个版本的内存消耗得更快,但其他表现类似。这个代码中仍然有一些东西导致了内存泄漏。

谁能帮帮我?

更新
我刚刚尝试注释掉 f.write(buf.getvalue()),虽然这显然不会创建一个包含任何内容的 blobstore 项目,但它最终还是完成了,处理了所有的 Event 实体。我是不是漏掉了什么,还是说 f.write() 会导致内存泄漏,或者会把所有内容缓冲到 finalize() 之前?

1 个回答

1

当你调用 f.write(buf.getvalue()) 时,其实是在让 StringIO 把自己变成一个完整的内存对象,然后把这个对象传给 f.write。这样做会比较耗费资源。

你可以试试 buf.seek(0),这个命令会把指针移回到流的开头,然后直接用 f.write(buf)。因为 StringIO 是一种类似文件的对象,f.write 应该可以像读取流一样读取它。

可以看看这个 源代码,文档或代码里并没有明确说明 file_service_pb.AppendRequest 是否能处理 StringIO。你可以试试看。

撰写回答