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

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容