【问题标题】:How to optimize this function for returning posts in a feed. Django如何优化此功能以在提要中返回帖子。姜戈
【发布时间】:2013-07-08 23:50:11
【问题描述】:

您好,我创建了一个函数,该函数将根据帖子的创建时间和投票数排名来返回帖子。这是一个饲料。我不确定这是否是一种有效的方法,并且我做对很重要,因为这可能会筛选数千个帖子。我在这里简化了一些,并简要解释了每个步骤。我目前关心的问题和整个函数的帖子(没有注释)都在后面。

开始时间参数是多少小时前才能获得帖子。例如,如果 startHours=6,则只会返回 6 小时前创建的帖子。

def rank(request, startHours): 

首先我按投票排序所有帖子

    unorderedPosts=Post.objects.order_by('-votes')

然后帖子被用户指定的类别排除

    if request.user.is_authenticated(): 
        preferences=request.user.categorypreference_set.filter(on=False)
        for preference in preferences:
                unorderedPosts=unorderedPosts.exclude(category_name=preference.category_name)

然后我创建 endHours,它总是比 startHours 参数提前 4 小时

    endHours=startHours+4         #4 hour time window

现在我对 unorderedPosts 进行切片,并仅获取从现在开始在 startHours 到 endHours 时间窗口中创建的那些。例如,如果 startHours=4,则仅返回 4 小时前但 8 小时前创建的帖子。

    posts=unorderedPosts.filter(created__gte=(timezone.now()-datetime.timedelta(hours=endHours)))

    posts=posts.exclude(created__gte=(timezone.now()-datetime.timedelta(hours=startHours)))

现在我创建一个循环,将时间窗口向后移动,直到找到至少一个具有适合时间窗口的创建日期的帖子。我设置了检查变量以防止无限循环(如果在 200 小时内没有找到任何帖子,则循环将退出)。

    count=posts.count()
    check=endHours
    while count<1 and endHours<(check+200):
        endHours+=4
        startHours+=4
        posts=unorderedPosts.filter(created__gte=(timezone.now()-datetime.timedelta(hours=endHours)))
        posts=posts.exclude(created__gte=(timezone.now()-datetime.timedelta(hours=startHours)))
        count=posts.count()
        if count>=1: return posts, endHours

    return posts

我最关心的是在开始时对所有帖子进行查询集。此功能旨在在小时间窗口内返回帖子,是否会通过对数据库中的所有帖子进行排名而不必要地减慢速度?我知道 django/python 查询集非常有效,但是对于包含数千个对象的集合进行排序对于此功能而言不会很麻烦吗?

如果这是一个问题,我怎样才能在保持一切可访问的同时提高效率?

这是整个事情的帖子。

def rank(request, startHours): 

    unorderedPosts=Post.objects.order_by('-upVote')

    if request.user.is_authenticated(): 
        preferences=request.user.categorypreference_set.filter(on=False)
        for preference in preferences: #filter by category preference
                unorderedPosts=unorderedPosts.exclude(category_name=preference.category_name)


    endHours=startHours+4     #4 hour time window

    posts=unorderedPosts.filter(created__gte=(timezone.now()-datetime.timedelta(hours=endHours)))

    posts=posts.exclude(created__gte=(timezone.now()-datetime.timedelta(hours=startHours)))

    count=posts.count()
    check=endHours

    while count<1 and endHours<(check+200):
        endHours+=4
        startHours+=4
        posts=unorderedPosts.filter(created__gte=(timezone.now()-datetime.timedelta(hours=endHours)))
        posts=posts.exclude(created__gte=(timezone.now()-datetime.timedelta(hours=startHours)))
        count=posts.count()
        if count>=1: return posts

   return posts

【问题讨论】:

    标签: python django feed django-queryset


    【解决方案1】:

    您主要关心的不是您需要担心的事情。检查When querysets are evaluated 上的文档 - 您可以无限期地定义和添加子句到查询集,并且它实际上不会针对数据库运行,直到您调用实际需要访问数据库的内容。

    需要多次查询的是迭代时间,直到您点击一个有帖子的窗口。如果您在一次通话中检查最新的created 时间,使用该时间计算您的窗口,然后根据该时间限制帖子,然后按投票数排序,您将获得更好的性能。

    类似:

    unorderedPosts = Post.objects.all()
    if request.user.is_authenticated(): 
        preferences=request.user.categorypreference_set.filter(on=False)
        for preference in preferences: #filter by category preference
            unorderedPosts=unorderedPosts.exclude(category_name=preference.category_name)
    latest_post_datetime = unorderedPosts.aggregate(Max('created'))['created__max']
    
    original_start_time = datetime.datetime.now() - datetime.timedelta(hours=startHours)    
    latest_post_day_start_time = datetime.datetime.combine(latest_post_datetime.date(), original_start_time.time())
    # a timedelta guaranteed to be less than 24 hours
    time_shift = latest_post_day_start_time - latest_post_datetime
    timewindow = datetime.timedelta(hours=4)
    if time_shift.days >= 0:
        extra_windows_needed = time_shift.seconds / timewindow.seconds 
    else:
        # negative timedeltas store negative days, then positive seconds; negate
        extra_windows_needed = -(abs(time_shift).seconds) / timewindow.seconds
    start_time = latest_post_day_start_time - (timewindow * (extra_windows_needed + 1))
    posts = unorderedPosts.filter(created__gte=start_time).order_by('-upVote')
    return posts
    

    只要您在窗口 (4) 中的小时数均匀地划分为一天,这里的数学计算就是正确的 - 否则计算正确的偏移量会变得更加棘手。基本上,您需要采用时间偏移模时间窗口长度,并且我正在利用这样一个事实,即如果您最终在同一个日历日结束,我知道天模四小时部分有效。

    此外,它不包括结束时间,因为您的原始逻辑并未在初始 startHours 期间强制执行结束时间。如果其中没有任何内容,它只会将开始时间移回原处,因此您无需担心出现太近的内容。

    此版本最多进行三个数据库查询 - 一个获取登录用户的类别偏好,一个获取latest_post_datetime,一个获取posts,确信至少有一个匹配的帖子。

    您还可以考虑进行分析,看看您的数据库后端是否能更好地使用子查询来排除不需要的类别:

    if request.user.is_authenticated():
        unorderedPosts = unorderedPosts.exclude(category_name__in=request.user.categorypreference_set.filter(on=False).values_list('category_name')
    

    正如__in lookup docs 所述,此处的性能因不同的数据库后端而异。

    【讨论】:

    • 您提出了一些很好的建议!我现在根据下一个最新帖子制作时间窗口,而不是遍历每个连续的窗口。我将排序的顺序切换为先做类别,然后是时间,最后是投票。我考虑尝试一个子查询,但我发现我的后端(mysql)在使用该方法时效果不佳。非常感谢您的洞察力!
    • 时间窗口的事情在很大程度上取决于您要做什么 - 例如,如果您正在寻找某个最小数量的帖子,而不管时间如何,还有其他方法可以做到这一点。而且,是的,子查询是命中注定的。
    猜你喜欢
    • 1970-01-01
    • 2016-05-02
    • 2018-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-17
    • 2017-02-07
    • 2020-09-11
    相关资源
    最近更新 更多