【问题标题】:Redis Queue + python-rq: Right pattern to prevent high memory usage?Redis Queue + python-rq:防止高内存使用的正确模式?
【发布时间】:2014-02-11 19:03:21
【问题描述】:

我们目前在 Heroku 托管的 Python 应用程序中使用 Redis to Go。

我们使用 Redis 和 python-rq 纯粹作为一个任务队列来提供一些时间密集型任务的延迟执行。一个任务正在从 PostgreSQL 数据库中检索一些数据并将结果写回它 - 因此 Redis 实例中根本没有保存任何有价值的数据。我们注意到,根据执行的作业数量,Redis 正在消耗越来越多的内存(增长 @ ~10 MB/小时)。 CLI 上的 FLUSHDB 命令修复了这个问题(将其降低到使用的 RAM 约为 700kB),直到 RAM 再次满为止。

根据我们(未更改的标准)设置,作业结果会保留 500 秒。随着时间的推移,一些作业当然会失败,它们会被移动到失败的队列中。

  • 为了在稳定的 RAM 容量下完成任务,我们必须采取哪些不同的措施?
  • RAM 消耗从何而来?
  • 我可以完全关闭持久性吗?
  • 从文档中我知道 500 秒 TTL 意味着密钥“过期”,但并未真正删除。此时密钥是否仍会消耗内存?我能以某种方式改变这种行为吗?
  • 它是否与失败的队列有关(显然没有附加到作业的 TTL,这意味着(我认为)这些将永远保留)?
  • 只是好奇:当纯粹将RQ用作队列时,Redis DB中保存了什么?它是实际的可执行代码还是只是对要执行的函数的位置的引用?

很抱歉,我提出了一些非常愚蠢的问题,但我对排队的话题很陌生,经过 2 天以上的研究,我已经到了不知道下一步该做什么的地步。 谢谢, KH

【问题讨论】:

    标签: python heroku asynchronous redis redistogo


    【解决方案1】:

    又玩了两天,我发现了问题所在。我想与您分享此内容以及有用的工具:

    核心问题

    实际的问题是我们忽略了在将对象保存到 PostgreSQL 数据库之前将其转换为字符串。如果没有这个转换,字符串表示最终会出现在数据库中(由于各个对象的 __str__() 函数准确地返回了我们想要的表示);然而,对于 Redis,whole 对象被传递了。将其传递给 Redis 后,关联的任务因UnpickleError 异常而崩溃。这消耗了崩溃后未释放的 5 MB RAM。

    其他操作

    为了进一步减少内存占用,我们实施了以下补充操作(请注意,我们将所有内容保存到单独的数据库中,因此 Redis 保存的结果根本不会在我们的应用程序中使用):

    • 我们通过调用enqueue_call([...] result_ttl=0)将任务结果的TTL设置为0
    • 我们定义了一个自定义异常处理程序 - black_hole - 接受所有异常并返回 False。这可以防止 Redis 将任务移动到仍会使用一些内存的失败队列。例外情况会事先通过电子邮件发送给我们以进行跟踪。

    有用的工具:

    我们刚刚与redis-cli合作。

    • redis-cli info | grep used_memory_human --> 显示当前内存使用情况。非常适合比较任务执行前后的内存占用情况。
    • redis-cli keys '*' --> 显示所有当前存在的键。这个概述让我了解到,一些任务即使应该被删除也没有被删除(如上所述,它们因 UnpickleError 而崩溃,因此没有被删除)。
    • redis-cli monitor --> 显示 Redis 中正在发生的事情的实时概览。这帮助我发现来回移动的物体太大了。
    • redis-cli debug object <key> --> 显示密钥值的转储。
    • redis-cli hgetall <key> --> 显示键值的更易读转储(对于将 Redis 纯粹用作任务队列的特定用例特别有用,因为似乎任务是由 python-rq 以这种格式创建的。

    此外,我可以回答我上面发布的一些问题:

    从文档中我知道 500 秒 TTL 意味着密钥“过期”,但并未真正删除。此时密钥是否仍会消耗内存?我能以某种方式改变这种行为吗?

    实际上,正如文档所暗示的那样,它们已被删除。

    它是否与失败的队列有关(显然没有附加到作业的 TTL,这意味着(我认为)这些将永远保留)?

    令人惊讶的是,Redis 本身崩溃的作业并未移至失败队列,它们只是“被放弃”,这意味着值仍然存在,但 RQ 并不像对待失败作业的正常方式那样关心它。

    相关文档

    【讨论】:

      【解决方案2】:

      如果您使用来自http://python-rq.org/docs/exceptions/ 的“黑洞”异常处理程序,您还应该在此处添加job.cancel()

      def black_hole(job, *exc_info):
          # Delete the job hash on redis, otherwise it will stay on the queue forever
          job.cancel()
          return False
      

      【讨论】:

        【解决方案3】:

        对我来说不是很明显的一件事是 RQ 作业同时具有“描述”和“数据”属性。如果未指定,则将描述设置为数据的字符串表示形式,在我的情况下,这不必要地冗长。将描述显式设置为简短的摘要可以节省我的开销。

        enqueue(func, longdata, description='short job summary')
        

        【讨论】:

          猜你喜欢
          • 2014-09-27
          • 1970-01-01
          • 1970-01-01
          • 2018-12-20
          • 2019-10-19
          • 2019-03-17
          • 2013-11-24
          • 2019-02-02
          • 1970-01-01
          相关资源
          最近更新 更多