Skip to main content
 首页 » 编程设计

python之为什么带有 S3Boto 后端的 django-storages 的 default_storate.exists() 会导致大型 S3 存储桶出现内存错误

2025年05月04日31dudu

当运行 default_storage.exists()

时,我遇到了使用 S3Boto 后端的 django-storages 的内存泄漏问题

我在这里关注文档: http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html

这是我的设置文件的相关部分:

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 

这是我重复这个问题的方法:

./manage.py shell 
 
from django.core.files.storage import default_storage 
 
# Check default storage is right 
default_storage.connection 
>>> S3Connection:s3.amazonaws.com 
 
# Check I can write to a file 
file = default_storage.open('storage_test_2014', 'w') 
file.write("does this work?") 
file.close() 
file2 = default_storage.open('storage_test_2014', 'r') 
file2.read() 
>>> 'does this work?' 
 
# Run the exists command 
default_storage.exists("asdfjkl") # This file doesn't exist - but the same thing happens no matter what I put here - even if I put 'storage_test_2014' 
 
# Memory usage of the python process creeps up over the next 45 seconds, until it nears 100% 
# iPython shell then crashes 
>>> Killed 

我想到的唯一潜在问题是我的 S3 存储桶中有 93,000 个项目 - 我想知道 .exists 是否只是下载整个文件列表以便检查?如果是这样的话,肯定还有另一种方法吗?不幸的是,sorl-thumbnail 在生成新缩略图时使用了这个 .exists() 函数,这导致缩略图生成非常慢。

请您参考如下方法:

更新(2017 年 1 月 23 日)

为避免这种情况,您可以简单地传递 preload_metadata=False创建 Storage 时, 或设置 AWS_PRELOAD_METADATA = False在设置中。

感谢@r3mot 在评论中提出这个建议。

原始答案

其实是因为S3BotoStorage.exists调用 S3BotoStorage.entries ,具体如下:

    @property 
    def entries(self): 
        """ 
        Get the locally cached files for the bucket. 
        """ 
        if self.preload_metadata and not self._entries: 
            self._entries = dict((self._decode_name(entry.key), entry) 
                                for entry in self.bucket.list(prefix=self.location)) 

处理这种情况的最好方法是子类化 S3BotoStorage如下:

from storages.backends.s3boto import S3BotoStorage, parse_ts_extended 
 
 
class MyS3BotoStorage(S3BotoStorage): 
    def exists(self, name): 
        name = self._normalize_name(self._clean_name(name)) 
        k = self.bucket.new_key(self._encode_name(name)) 
        return k.exists() 
 
    def size(self, name): 
        name = self._normalize_name(self._clean_name(name)) 
        return self.bucket.get_key(self._encode_name(name)).size 
 
    def modified_time(self, name): 
        name = self._normalize_name(self._clean_name(name)) 
        k = self.bucket.get_key(self._encode_name(name)) 
        return parse_ts_extended(k.last_modified) 

您只需将此子类放入应用程序的一个模块中,并通过设置模块中的虚线路径引用它。这个子类的唯一缺点是每次调用 3 个重写方法中的任何一个都会导致 Web 请求,这可能不是什么大问题。