【问题标题】:In Google App Engine, how to I reduce memory consumption as I write a file out to the blobstore rather than exceed the soft memory limit?在 Google App Engine 中,如何在将文件写入 blobstore 时减少内存消耗,而不是超过软内存限制?
【发布时间】:2012-02-03 06:09:15
【问题描述】:

我正在使用 blobstore 以 csv 格式备份和恢复实体。该过程适用于我所有的小型模型。但是,一旦我开始使用超过 2K 实体的模型,我就会超出软内存限制。我一次只获取 50 个实体,然后将结果写入 blobstore,所以我不清楚为什么我的内存使用量会增加。我可以通过增加下面传递的“限制”值来可靠地使方法失败,这会导致方法运行时间稍长以导出更多实体。

  1. 有关如何优化此过程以减少内存消耗的任何建议?

  2. 此外,生成的文件大小只有

简化示例:

file_name = files.blobstore.create(mime_type='application/octet-stream')
with files.open(file_name, 'a') as f:
    writer = csv.DictWriter(f, fieldnames=properties)
    for entity in models.Player.all():
      row = backup.get_dict_for_entity(entity)
      writer.writerow(row)

产生错误: 总共处理 7 个请求后,超过了 150.957 MB 的软私有内存限制

简化示例 2:

问题似乎出在 python 2.5 中的 using files 和 with 语句上。排除 csv 内容,我可以通过简单地尝试将 4000 行文本文件写入 blobstore 来重现几乎相同的错误。

from __future__ import with_statement
from google.appengine.api import files
from google.appengine.ext.blobstore import blobstore
file_name = files.blobstore.create(mime_type='application/octet-stream')   
myBuffer = StringIO.StringIO()

#Put 4000 lines of text in myBuffer

with files.open(file_name, 'a') as f:
    for line in myBuffer.getvalue().splitlies():
        f.write(line)

files.finalize(file_name)  
blob_key = files.blobstore.get_blob_key(file_name)

产生错误: 在总共处理 24 个请求后,超过了 154.977 MB 的软私有内存限制

原文:

def backup_model_to_blobstore(model, limit=None, batch_size=None):
    file_name = files.blobstore.create(mime_type='application/octet-stream')
    # Open the file and write to it
    with files.open(file_name, 'a') as f:
      #Get the fieldnames for the csv file.
      query = model.all().fetch(1)
      entity = query[0]
      properties = entity.__class__.properties()
      #Add ID as a property
      properties['ID'] = entity.key().id()

      #For debugging rather than try and catch
      if True:
        writer = csv.DictWriter(f, fieldnames=properties)
        #Write out a header row
        headers = dict( (n,n) for n in properties )
        writer.writerow(headers)

        numBatches = int(limit/batch_size)
        if numBatches == 0:
            numBatches = 1

        for x in range(numBatches):
          logging.info("************** querying with offset %s and limit %s", x*batch_size, batch_size)
          query = model.all().fetch(limit=batch_size, offset=x*batch_size)
          for entity in query:
            #This just returns a small dictionary with the key-value pairs
            row = get_dict_for_entity(entity)
            #write out a row for each entity.
            writer.writerow(row)

    # Finalize the file. Do this before attempting to read it.
    files.finalize(file_name)

    blob_key = files.blobstore.get_blob_key(file_name)
    return blob_key

日志中的错误如下所示

......
2012-02-02 21:59:19.063
************** querying with offset 2050 and limit 50
I 2012-02-02 21:59:20.076
************** querying with offset 2100 and limit 50
I 2012-02-02 21:59:20.781
************** querying with offset 2150 and limit 50
I 2012-02-02 21:59:21.508
Exception for: Chris (202.161.57.167)

err:
Traceback (most recent call last):
  .....
    blob_key = backup_model_to_blobstore(model, limit=limit, batch_size=batch_size)
  File "/base/data/home/apps/singpath/163.356548765202135434/singpath/backup.py", line 125, in backup_model_to_blobstore
    writer.writerow(row)
  File "/base/python_runtime/python_lib/versions/1/google/appengine/api/files/file.py", line 281, in __exit__
    self.close()
  File "/base/python_runtime/python_lib/versions/1/google/appengine/api/files/file.py", line 275, in close
    self._make_rpc_call_with_retry('Close', request, response)
  File "/base/python_runtime/python_lib/versions/1/google/appengine/api/files/file.py", line 388, in _make_rpc_call_with_retry
    _make_call(method, request, response)
  File "/base/python_runtime/python_lib/versions/1/google/appengine/api/files/file.py", line 236, in _make_call
    _raise_app_error(e)
  File "/base/python_runtime/python_lib/versions/1/google/appengine/api/files/file.py", line 179, in _raise_app_error
    raise FileNotOpenedError()
FileNotOpenedError

C 2012-02-02 21:59:23.009
Exceeded soft private memory limit with 149.426 MB after servicing 14 requests total

【问题讨论】:

  • 尝试将 model.all() 分配给循环外的变量并重用它,为每个批次创建一个新查询可能会占用大量内存。
  • 这让我更进一步,但在获取大约 2200 个实体后,我仍然超出了内存限制。如何更有效地做到这一点?

标签: google-app-engine


【解决方案1】:

最好不要自己进行批处理,而只是遍历查询。迭代器会选择一个足够大的批次大小(可能是 20):

q = model.all()
for entity in q:
    row = get_dict_for_entity(entity)
    writer.writerow(row)

这避免了使用不断增加的偏移量重新运行查询,这很慢并且会导致数据存储中出现二次行为。

关于内存使用的一个经常被忽视的事实是,与实体的序列化形式相比,实体的内存表示可以使用 30-50 倍的 RAM;例如磁盘上 3KB 的实体可能使用 RAM 中的 100KB。 (确切的爆炸因素取决于许多因素;如果您有很多名称长且值小的属性,情况会更糟,对于具有长名称的重复属性更糟。)

【讨论】:

  • 谢谢吉多。我尝试遍历查询并看到同样的问题。我在帖子顶部添加了条件的最简化版本。由于某种原因,我似乎仍然在不断地分配内存并在达到 30 秒超时之前达到内存限制。如果只是超时,我当然会推迟任务。
  • 遗憾的是我也没有想法,除了暗示 csv 模块中可能存在内存泄漏。你只得到软内存错误还是硬内存错误?您可以忽略的软错误,它只有在成功完成处理进程后才会终止进程。显然这是一个次优的情况,但如果它只是偶尔发生,也许可以忍受它;将创建一个新进程来处理下一个请求。
  • 这似乎与在 python 2.5 中使用“with”语句和文件有关。如果我只是尝试将 4000 行文本文件从 StringIO 对象写入 blobstore 文件,我可以重现相同的软内存错误。
【解决方案2】:

What is the proper way to write to the Google App Engine blobstore as a file in Python 2.5 中报告了类似的问题。在一个答案中,建议您偶尔尝试插入 gc.collect() 调用。鉴于我对文件 API 实现的了解,我认为这是正确的。试试看!

【讨论】:

    【解决方案3】:

    我不能说 Python 中的内存使用情况,但考虑到您的错误消息,错误很可能源于 a blobstore backed file in GAE can't be open for more than around 30 seconds 因此如果您的处理时间较长,您必须定期关闭并重新打开它。

    【讨论】:

    • 我不认为您可以关闭 blobstore 文件并重新打开它。它们是不可变的。此外,这个问题似乎与超时无关。我可以从控制台应用程序运行该方法并获得相同的软私有内存问题。
    【解决方案4】:

    由于请求限制为 30 秒,这可能是 Time Exceed 错误。在我的实现中,为了绕过它而不是为操作使用 webapp 处理程序,我在默认队列中触发了一个事件。队列很酷的一点是它需要一行代码来调用它,它有 10 分钟的时间限制,如果任务失败,它会在时间限制之前重试。我不确定它是否能解决您的问题,但值得一试。

    from google.appengine.api import taskqueue
    ...
    taskqueue.add("the url that invokes your method")
    

    您可以找到有关队列的更多信息here

    或者考虑使用backend 进行严肃的计算和文件操作。

    【讨论】:

      猜你喜欢
      • 2020-04-23
      • 2017-02-11
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多