Django 中的每个请求缓存?
我想实现一个装饰器,它为任何方法(而不仅仅是视图)提供按请求缓存。这是一个示例用例。
我有一个自定义标签,用于确定是否 一长串记录中的一条记录是 一个“最爱”。为了检查是否 该商品是您的最爱,您必须查询 数据库。理想情况下,你会 执行一次查询即可获取所有 收藏夹,然后检查一下 针对每条记录的缓存列表。
一个解决方案是获取所有 视图中的收藏夹,然后通过 设置到模板中,然后 进入每个标签调用。
或者,标签本身也可以 执行查询本身,但仅执行 第一次被调用。然后 结果可以缓存以供后续使用 来电。好处是你可以使用 此标签来自任何模板,在任何 视图,而不提醒视图。
在现有的缓存机制中,你 只能将结果缓存 50 毫秒, 并假设这与 当前请求。我想做那个 相关性可靠。
这是我当前拥有的标签的示例。
@register.filter()
def is_favorite(record, request):
if "get_favorites" in request.POST:
favorites = request.POST["get_favorites"]
else:
favorites = get_favorites(request.user)
post = request.POST.copy()
post["get_favorites"] = favorites
request.POST = post
return record in favorites
有没有办法从 Django 获取当前请求对象,而不传递它?我可以从标签传递请求,该请求将始终存在。但我想从其他函数中使用这个装饰器。
是否存在按请求缓存的现有实现?
I would like to implement a decorator that provides per-request caching to any method, not just views. Here is an example use case.
I have a custom tag that determines if
a record in a long list of records is
a "favorite". In order to check if an
item is a favorite, you have to query
the database. Ideally, you would
perform one query to get all the
favorites, and then just check that
cached list against each record.One solution is to get all the
favorites in the view, and then pass
that set into the template, and then
into each tag call.Alternatively, the tag itself could
perform the query itself, but only the
first time it's called. Then the
results could be cached for subsequent
calls. The upside is that you can use
this tag from any template, on any
view, without alerting the view.In the existing caching mechanism, you
could just cache the result for 50ms,
and assume that would correlate to the
current request. I want to make that
correlation reliable.
Here is an example of the tag I currently have.
@register.filter()
def is_favorite(record, request):
if "get_favorites" in request.POST:
favorites = request.POST["get_favorites"]
else:
favorites = get_favorites(request.user)
post = request.POST.copy()
post["get_favorites"] = favorites
request.POST = post
return record in favorites
Is there a way to get the current request object from Django, w/o passing it around? From a tag, I could just pass in request, which will always exist. But I would like to use this decorator from other functions.
Is there an existing implementation of a per-request cache?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
使用自定义中间件,您可以获得保证为每个请求清除的 Django 缓存实例。
这是我在项目中使用的:
要使用中间件,请在settings.py中注册它,例如:
然后您可以按如下方式使用缓存:
有关更多信息,请参阅 django 低级缓存 api 文档:
Django Low-Level Cache API
修改要使用的 memoize 装饰器应该很容易请求缓存。查看 Python 装饰器库,了解 memoize 装饰器的一个很好的示例:
Python 装饰器库
Using a custom middleware you can get a Django cache instance guaranteed to be cleared for each request.
This is what I used in a project:
To use the middleware register it in settings.py, e.g:
You may then use the cache as follows:
Refer to the django low level cache api doc for more information:
Django Low-Level Cache API
It should be easy to modify a memoize decorator to use the request cache. Have a look at the Python Decorator Library for a good example of a memoize decorator:
Python Decorator Library
编辑:
我提出的最终解决方案已编译成 PyPI 包: https://pypi.org/project/django-request-cache/
编辑 2016-06-15:
我发现了一个更简单的解决方案来解决这个问题,并且有点捂脸因为从一开始就没有意识到这应该是多么容易。
这样,您就可以使用
request.cache
作为缓存实例,该实例仅在request
存在期间存在,并且当请求发生时将被垃圾收集器完全清理。完成了。如果您需要从通常不可用的上下文访问请求对象,则可以使用可以在线找到的所谓“全局请求中间件”的各种实现之一。
** 初步答案:**
这里没有其他解决方案解决的一个主要问题是,当您在单个进程的生命周期中创建和销毁其中多个 LocMemCache 时,LocMemCache 会泄漏内存。 django.core.cache.backends.locmem 定义了几个全局字典,它们保存对每个 LocalMemCache 实例的缓存数据的引用,并且这些字典永远不会被清空。
下面的代码解决了这个问题。它首先是 @href_ 的答案和 @squarelogic.hayden 的评论中链接的代码所使用的更清晰的逻辑的组合,然后我进一步对其进行了完善。
编辑2016年6月15日:
我发现了一个明显更简单的解决这个问题的方法,但我有点不好意思,因为我从一开始就没有意识到这应该是多么容易。
这样,您就可以使用
request.cache
作为缓存实例,该实例仅在request
存在期间存在,并且当请求发生时将被垃圾收集器完全清理。完成了。如果您需要从通常不可用的上下文访问请求对象,则可以使用可以在线找到的所谓“全局请求中间件”的各种实现之一。
EDIT:
The eventual solution I came up with has been compiled into a PyPI package: https://pypi.org/project/django-request-cache/
EDIT 2016-06-15:
I discovered a significantly simpler solution to this problem, and kindof facepalmed for not realizing how easy this should have been from the start.
With this, you can use
request.cache
as a cache instance that lives only as long as therequest
does, and will be fully cleaned up by the garbage collector when the request is done.If you need access to the
request
object from a context where it's not normally available, you can use one of the various implementations of a so-called "global request middleware" that can be found online.** Initial answer: **
A major problem that no other solution here solves is the fact that LocMemCache leaks memory when you create and destroy several of them over the life of a single process.
django.core.cache.backends.locmem
defines several global dictionaries that hold references to every LocalMemCache instance's cache data, and those dictionaries are never emptied.The following code solves this problem. It started as a combination of @href_'s answer and the cleaner logic used by the code linked in @squarelogic.hayden's comment, which I then refined further.
EDIT 2016-06-15:
I discovered a significantly simpler solution to this problem, and kindof facepalmed for not realizing how easy this should have been from the start.
With this, you can use
request.cache
as a cache instance that lives only as long as therequest
does, and will be fully cleaned up by the garbage collector when the request is done.If you need access to the
request
object from a context where it's not normally available, you can use one of the various implementations of a so-called "global request middleware" that can be found online.我想出了一个技巧,可以将东西直接缓存到请求对象中(而不是使用标准缓存,它将与 memcached、文件、数据库等绑定在一起)。
所以,基本上我只是将缓存的值(类别对象)存储在在本例中)在请求字典中的新键“c_category”下。或者更准确地说,因为我们不能只在请求对象上创建一个密钥,所以我将密钥添加到请求对象的方法之一 - get_host() 中。
乔治.
I came up with a hack for caching things straight into the request object (instead of using the standard cache, which will be tied to memcached, file, database, etc.)
So, basically I am just storing the cached value (category object in this case) under a new key 'c_category' in the dictionary of the request. Or to be more precise, because we can't just create a key on the request object, I am adding the key to one of the methods of the request object - get_host().
Georgy.
多年后,一个超级黑客在单个 Django 请求中缓存 SELECT 语句。您需要在请求范围的早期执行
patch()
方法,就像在中间件中一样。patch() 方法用名为execute_sql_cache 的替代方法替换了Django 内部的execute_sql 方法。该方法查看要运行的 sql,如果它是 select 语句,它首先检查线程本地缓存。只有当在缓存中没有找到它时,它才会继续执行 SQL。在任何其他类型的 sql 语句上,它都会破坏缓存。有一些逻辑不缓存大型结果集,即超过 100 条记录的结果集。这是为了保留 Django 的惰性查询集评估。
Years later, a super hack to cache SELECT statements inside a single Django request. You need to execute the
patch()
method from early on in your request scope, like in a piece of middleware.The patch() method replaces the Django internal execute_sql method with a stand-in called execute_sql_cache. That method looks at the sql to be run, and if it's a select statement, it checks a thread-local cache first. Only if it's not found in the cache does it proceed to execute the SQL. On any other type of sql statement, it blows away the cache. There is some logic to not cache large result sets, meaning anything over 100 records. This is to preserve Django's lazy query set evaluation.
这个使用 python dict 作为缓存(不是 django 的缓存),并且非常简单和轻量级。
同样的情况也可以通过线程本地存储来实现。
我不知道这种方法有任何缺点,请随时在评论中添加它们。
This one uses a python dict as the cache (not the django's cache), and is dead simple and lightweight.
The same can be probably implemented with threadlocal storage.
I am not aware of any downsides of this approach, feel free to add them in the comments.
您始终可以手动进行缓存。
You can always do the caching manually.
@href_ 给出的答案非常棒。
以防万一你想要更短的东西也可能做到这一点:
然后让收藏夹像这样调用它:
此方法利用 lru 缓存 并将其与时间戳相结合,我们确保缓存不会保存任何内容超过几秒钟。如果您需要在短时间内多次调用成本高昂的函数,这可以解决问题。
这不是使缓存失效的完美方法,因为有时它会丢失最近的数据:
int(..2.99.. / 3)
后跟int(..3.00..) / 3)
。尽管有这个缺点,它在大多数命中中仍然非常有效。另外,您还可以在请求/响应周期之外使用它,例如 celery 任务或管理命令作业。
Answer given by @href_ is great.
Just in case you want something shorter that could also potentially do the trick:
Then get favourites calling it like this:
This method makes use of lru cache and combining it with timestamp we make sure that cache doesn't hold anything for longer then few seconds. If you need to call costly function several times in short period of time this solves the problem.
It is not a perfect way to invalidate cache, because occasionally it will miss on very recent data:
int(..2.99.. / 3)
followed byint(..3.00..) / 3)
. Despite this drawback it still can be very effective in majority of hits.Also as a bonus you can use it outside request/response cycles, for example celery tasks or management command jobs.