基于Django文件系统文件的缓存有5-10%的时间无法写入数据。

问题描述 投票:3回答:1

我们正在用Django Celery做后台数据处理,将一个CSV文件(最大15MB),转换成dict数据列表(其中还包括一些Django模型对象),并将其分成若干块在子任务中处理。

@task
def main_task(data):
  i = 0
  for chunk in chunk_up(data):
    chunk_id = "chunk_id_{}".format(i)
    cache.set(chunk_id, chunk, timeout=FIVE_HOURS)
    sub_task.delay(chunk_id)
    i += 1

@task
def sub_task(chunk_id):
  data_chunk = cache.get(chunk_id)
  ... # do processing

所有的任务都在后台的并发进程中运行,由Celery管理。我们最初使用的是Redis后端,但发现它会 在高峰负荷情况下,内存会经常耗尽。 和高并发。所以我们改用了 Django的基于文件的缓存后端。. 虽然这解决了内存问题,但我们看到20-30%的缓存条目从未被写入。没有抛出错误,只是无声的失败。当我们回过头来从CLI中查找缓存时,我们看到例如chunk_id_7和chunk_id_9会存在,但chunk_id_8不会。所以断断续续的,有些缓存条目无法被保存。

我们换上了 磁盘缓存 后台,并观察到同样的情况,尽管缓存故障似乎减少到5-10%(非常粗略的估计)。

我们注意到,在过去 Django基于文件的缓存存在并发进程问题但似乎很多年前就已经修复了(我们现在是v1.11)。有评论说这个缓存后台更多的是POC,不过同样不知道之后是否有变化。

基于文件的缓存是一个生产质量的缓存解决方案吗?如果是,是什么原因会导致我们的写入失败?如果不是,对于我们的用例,有什么更好的解决方案?

python django caching celery
1个回答
1
投票

在Django FileBased和DiskCache DjangoCache中,问题是缓存满了,被各自后台清空。在Django FB的情况下,清空发生在 MAX_ENTRIES 缓存中的数据达到(默认300),这时它的 随机 删除部分条目,基于 CULL_FREQUENCY (默认33%)。所以我们的缓存越来越满,随机条目被删除,这当然会导致 cache.get()sub_task 如果它的条目被随机删除,则会在某些块上失败。

对于DiskCache,默认的缓存 size_limit 是1GB。当达到这个值时,将根据以下情况对条目进行筛选 EVICTION_POLICY 默认为最近使用的. 在我们的案例中,在 size_limit 的时候,它删除了仍在使用的条目,但至少是最近的。

在了解了这个问题之后,我们尝试使用DiskCache与 EVICTION_POLICY = 'none' 以免在任何情况下被淘汰。这 几乎 工作,但对于一小部分(< 1%)的缓存条目,我们还是看到了 cache.get() 未能获得缓存中实际存在的条目。也许是SQLLite的错误?即使在添加了 retry=True 每逢 cache.get() 调用,但它仍然无法获得缓存中实际存在的缓存条目。

所以,我们最终实现了一个更加确定性的FileBasedCache,似乎可以做到这一点。

from django.core.cache.backends.filebased import FileBasedCache as DjangoFileBasedCached

class FileBasedCache(DjangoFileBasedCached):
    def _cull(self):
        '''
        In order to make the cache deterministic,
        rather than randomly culling,
        simply remove all expired entries

        Use MAX_ENTRIES to avoid checking every file in the cache
        on every set() operation. MAX_ENTRIES sh be set large enough
        so that when it's hit we can be pretty sure there will be
        expired files. If set too low then we will be checking
        for expired files too frequently which defeats the purpose of MAX_ENTRIES

        :return:
        '''
        filelist = self._list_cache_files()
        num_entries = len(filelist)
        if num_entries < self._max_entries:
            return  # return early if no culling is required
        if self._cull_frequency == 0:
            return self.clear()  # Clear the cache when CULL_FREQUENCY = 0

        for fname in filelist:
            with io.open(fname, 'rb') as f:
                # is_expired automatically deletes what's expired
                self._is_expired(f)

退一步讲,我们真正需要的是一个持久可靠的大数据存储,以便在Celery任务中访问。我们正在使用Django cache来实现这个目标,但也许它不是合适的工具?缓存并不是真的要100%可靠。我们是否应该使用其他方法来解决Celery任务之间传递大数据的基本问题?

© www.soinside.com 2019 - 2024. All rights reserved.