美文网首页
记一次多表同时搜索同一字段的优化过程

记一次多表同时搜索同一字段的优化过程

作者: 爱林林爱生活 | 来源:发表于2017-01-10 14:38 被阅读0次

0x01背景

在广告监控模型中,一个推广计划(campaign)对应于爱奇艺网页中的恐怖电影系列,一个推广组(adgroup)对应于恐怖电影系列的美国的恐怖电影,然后一个创意(ad)则对应于具体的某部电影上所投放的广告,然而,这个ad可能会呈现在网页上的位置,于是会有一个广告位(adunit)来记录这个ad的具体位置等信息,这个adunit会属于某个频道(channel, 比如frame)内,这个channel会隶属于某个媒体(media, 比如优酷, 爱奇艺等, 区别于网站),所有的这几个模型都会有一个字段name。他们的从属关系如下所示:

ad    →   adgroup   →  campaign    →  account
↑↓           ↑↓
   a d u n i t         → channel  →  media   

其中→ 为多对一的关系, ↑↓ 为多对多的关系。现在要以adgroup为中心,搜索campaign, adgroup, adunit, channel, media 中同时拥有keyword1或者keyword2的字段, 并且如果某个adgroup中被搜索到的字段越多,则排序越靠前。

0x02第一版实现

在搞清楚需求后,我很快就写出了如下语句

Adgroup.objects.filter(Q(name__contains=keyword1) | Q(name__contains=keyword2).filter(Q(adgroup__campaign__name__contains=keyword1) | Q(adgroup__campaign__name__contains=keyword2).filter(Q(adgroup__account__name__contains=keyword1) | Q(adgroup__account__name__contains=keyword2)

才搜索了三层, queryset就已经写得又臭又长,而且还扩展性很不好。当我写完这条语句并且测试后, 发现在几百条数据中所搜两个关键词却需要5秒以上,打开orm的debug模式后发现,orm生成了很多子查询。

0x03依赖于django-haystack搜索

django-haystack是django的一个模块化搜索解决方案, 后端可以插入 Solr, Elasticsearch, Whoosh, Xapian等,他会对指定的model的数据建立索引,从而实现快速搜索, 然而对Q对象的支持很不好。在看完文档之后并实现一遍之后,并不能实现

0x04分组与打分机制

考虑到被搜索到的字段越多,则排序越靠前。那么如果在某个model中被搜索到一个字段,则记一分,每多被搜索到一个字段,则加一分,那么排序就很好解决了, 只需要按照分数排序就好。同时, 由于adunit和adgroup为多对多关系,并不方便直接在adgroup上反向获取adunit, channel, media。那么我们可以分开两次搜索,第一次在adgroup上执行搜索与打分, 第二次以adunit为主,进行搜索和打分,最后以adgroup为维度,进行分组,把分数相同的放在一组并去重,就可以实现需求啦。优化后测试妹子再也不会吐槽我写的程序慢啦:)

代码实例如下:

cg_q_obj = _get_q_obj('campaign__name')
cg_range_q_obj = get_range_q_obj(cg_q_obj, search_range, 'schedulelist')
p_adgroup_args = filter(None, [cg_q_obj, cg_range_q_obj])
adgroup_list = AdGroup.objects.filter(
    **p_adgroup).filter(*p_adgroup_args).select_related(
        'campaign').prefetch_related('adunit_set')
adgroup_group = groupby(
    adgroup_list, key=lambda x: _get_score(x.campaign.name))
queue = list()
for rank, adgroup_list in adgroup_group:
    for adgroup in adgroup_list:
        for adunit in adgroup.adunit_set.all():
            adunit.rank = rank
            queue.append(adunit)
if q_obj:
    m_q_obj = _get_q_obj('channel__media__name')
    c_q_obj = _get_q_obj('channel__name')
    q_obj = q_obj | m_q_obj | c_q_obj
    adunit_list = adunit_list.filter(q_obj).prefetch_related(
        adgroup_prefetch)
    adunit_group = groupby(
        adunit_list, key=lambda x: get_adunit_score(_get_score, x))

    for rank, adunit_list in adunit_group:
        for adunit in adunit_list:
            adunit.rank = rank
            queue.append(adunit)
adunit_set = OrderedSet(sorted(queue, key=sort_func, reverse=True))
adunit_id_list = map(lambda x: x.id, adunit_set)
adunit_list = Adunit.objects.filter(id__in=adunit_id_list).select_related(
    'channel', 'channel__media').prefetch_related(adgroup_prefetch)

def get_score(search_list, name):
    return sum([1 if search in name else 0 for search in search_list])

def get_q_obj(search_list, name='name', reverse=False):
    name = '__'.join((name, 'contains'))
    q_obj = Q()
    for search in search_list:
        q_obj |= Q(**{name: search})
    if reverse:
        q_obj = [~Q(**{name: search}) for search in search_list]
    return q_obj

相关文章

网友评论

      本文标题:记一次多表同时搜索同一字段的优化过程

      本文链接:https://www.haomeiwen.com/subject/kevkbttx.html