使用Django优化Twitter的typeahead.js远程数据性能

4
我有一个大约包含120万个名称的数据库。当你输入某人的名字时,我使用Twitter的typeahead.js远程获取自动完成建议。在我的本地环境中,在你停止输入后,结果大约需要1-2秒才会出现(在输入时不会出现自动完成),而在Heroku上部署的应用程序上(仅使用一个dyno),则需要2-5秒以上。
我想知道为什么只有在你停止输入并且延迟几秒钟后才显示建议,是因为我的代码没有优化吗?
页面上的脚本:
<script type="text/javascript">
$(document).ready(function() {
  $("#navPersonSearch").typeahead({
    name: 'people',
    remote: 'name_autocomplete/?q=%QUERY'
  })
    .keydown(function(e) {
        if (e.keyCode === 13) {
            $("form").trigger('submit');
        }
    });
});
</script> 

键按下代码片段是因为如果不加它,当按下回车键时,我的表单无法提交。我的Django视图:
def name_autocomplete(request):
    query = request.GET.get('q','')
    if(len(query) > 0):
        results = Person.objects.filter(short__istartswith=query)
        result_list = []
        for item in results:
            result_list.append(item.short)
    else:
        result_list = []

    response_text = json.dumps(result_list, separators=(',',':'))
    return HttpResponse(response_text, content_type="application/json")

我的Person模型中的短字段也被索引了。有没有办法提高我的typeahead的性能?


你能否使用浏览器的分析工具(Chrome开发者工具或Firebug)来查看a)请求何时被发出,b)请求花费了多长时间? - Christian Ternus
是的...就像我之前说的那样,我的typeahead本地版本的请求大约需要1-2秒。在Heroku上,最少需要2秒,最多可能需要8-9秒。长时间的请求相比于较短的请求有非常长的“等待”阶段。 - dl8
这段代码对你来说能用吗? - sandeepnegi
4个回答

3

我认为这个问题和Django没有直接关系,但我可能是错的。我可以提供一些针对这种情况的通用建议:

(我猜测问题出在第4或第5点)

1)你的机器到Heroku平均ping的延迟是多少?如果很高,那会带来一些额外的开销。虽然不多,但当与你所提到的8-9秒相比时,肯定不算什么。请注意,使用https时惩罚将更大。

2)检查remote数据集中waitLimitFnrateLimitWait 的值。它们是否为默认值?

3)很可能是与数据库/数据集有关的问题。首先要检查的是连接到数据库需要多长时间(你使用连接池吗?)。

4)第二件事:查询所需的时间。我打赌问题出在这点或下一点。添加调试打印,或使用NewRelic(即使是免费计划也可以)。查看生成的查询并确保它被索引。让你的数据库解释执行这样一个查询的执行计划,并确保它使用了索引。

5)第三件事:结果是否很大?例如,如果你将“J”指定为查询条件,那么可能会有很多答案。只是获取它们并将它们传输到客户端需要时间。在这种情况下:

5.1)为你的数据集指定一个minLength。将其设置为至少3,如果可以4更好。

5.2)限制你的数据库查询返回的结果集。让它最多返回10个结果。

6)虽然我不是Django专家,但请确保你在Django中使用模型的方式不会使其首先将整个表加载到内存中。只是这样说而已。

希望对你有所帮助。


对于您提出的第1-2点,ping和值都是正常的。3.我正在使用Heroku处理与数据库的连接。4.我对测试这些内容不熟悉,所以我会去了解一下。5.是的,我最近限制了输入的大小和返回的内容,但仍然非常缓慢。感谢您的建议,我会继续努力改进。 - dl8

1
        results = Person.objects.filter(short__istartswith=query)
        result_list = []
        for item in results:
            result_list.append(item.short)

也许不是你缓慢的唯一原因,但从性能角度来看这很糟糕:永远不要在django queryset上循环。要从django queryset中组装列表,您应该始终使用values_list。在这种特定情况下:

        results = Person.objects.filter(short__istartswith=query)
        result_list = results.values_list('short', flat=True)

这样做可以直接从数据库获取所需的单个字段,而不是获取整个表行,从中创建一个Person实例,最后再读取单个属性。

0

您可以使用django haystack,您的服务器端代码将大致如下:

def autocomplete(request):
sqs = SearchQuerySet().filter(content_auto=request.GET.get('q', ''))[:5]  # or how many names you need
suggestions = [result.first_name for result in sqs]
# you have to configure typeahead how to process returned data, this is a simple example
data = json.dumps({'q': suggestions})  
return HttpResponse(data, content_type='application/json')

1
调试了一个类似的问题,我发现了一个错误。在第3行,应该是:suggestions = [result.object.first_name for result in sqs] - Ryan Walton

0
Nitzan提到了很多可以提高性能的主要要点,但与他不同的是,我认为这可能与Django直接相关(至少是服务器端)。
一个快速测试的方法是将你的name_autocomplete方法更新为以Typeahead期望的格式返回10个随机生成的字符串。(我们希望它们是随机的,这样Typeahead的缓存不会影响任何结果)。
我怀疑你会发现Typeahead现在运行得非常快,一旦你输入的字符串达到minLength,你应该开始看到结果出现。
如果是这种情况,那么我们需要查找可能导致查询变慢的原因,我的Python技能几乎为零,所以对此无能为力,抱歉!
如果不是这种情况,我建议考虑记录$('#navPersonSearch')调用typeahead:initializedtypeahead:opened时是否出现任何奇怪的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接