• mongodb倒排索引


    这周主要都花时间搞mongodb上了,业务场景是上游产出几个城市的全量道路code值,每个城市的数据量大概在100w~200w之间,每条数据对应好几个feature,形如:

    {
        code: 0,
        featureList: [{
         caseId: 'xxxxxx', feature1:
    '', feature2: '', feature3: '', ... }] }

    希望达到的效果:

    1、通过选定不同feature的值,过滤得到对应的数据

    2、支持过滤得到不含选定feature的数据

    之前尝试过给每种 feature 做索引,但效率还是很慢(实际上是索引写错了地方...);然后考虑了一下组合索引,但由于查询条件多变,feature 之间排列组合全都做组合索引太麻烦了;最后,炎哥建议使用倒排索引

    以前学mongdb是随便百度了一个菜鸟教程,只知道给mongodb加索引的方法,关于索引的细节、原理都不太清楚,于是决定仔细看看mongodb官网上关于索引的介绍。

    虽然全英文啃起来比较吃力与费时,但收获太多了,如:mongodb有个Index Intersection的特性,大概就是通过建单索引,组合查询能自动组合索引,加快查询效率,完全解决了我遇到的关于组合索引问题;mongodb能建自己的空间索引等等。比起查询各种中文博客,还是直接翻官方文档来的好。

    关于倒排索引,根据这篇博客,做了一些实践。本想利用 Aggregation Pipeline 建立倒排索引文档,结果半天不知道咋生成新文档,思考了一下感觉聚合还是更适合用来统计、找数据。随后使用 Map-Reduce 方法,一开始跑出结果后欣喜万分,但在随后与交叉索引对比查询效率时发现倒排数据有问题。搞了半天在官方文档中发现:

    先前参考的博客reduce代码有问题... 而且博客里给出的是使用mongoose的nodejs代码,mongo内置SpiderMonkey引擎,支持JavaScript脚本,我写的MapReduce代码如下:

    db = db.getSiblingDB('code');        // 库名
    
    var mapFunction = function () {
        var feature = this.featureList,
            caseId = feature.CaseId;
    
        for (var key in feature) {
            if (key !== 'CaseId') {
                emit(key, {                      // 用于null查询
                    ids: [caseId]
                });
                key = key + '_' + feature[key];
                emit(key, {
                    ids: [caseId]
                });
            }
        }
    };
    
    var reduceFunction = function (feature, caseId) {
        var ids = [];
        caseId.forEach(function (val) {
            ids = ids.concat(val.ids);  // 这里注意reduce函数会调用多次,某一次的输出结果可能会变成下一次输入的一部分,所以要用concat
        });
        return {
            ids
        };
    };
    
    var cols = db.getCollectionNames();
    for (var i = 0; i < cols.length; i++) {
        if (cols[i].indexOf('_') !== -1 && cols[i].indexOf('invert') === -1) { 
            db[cols[i]].mapReduce(mapFunction, reduceFunction, {
                out: {merge: cols[i] + '_invert'}
            });
        }
    }
            

    利用mongodb使用js脚本只需要写好js脚本后执行:

    mongo xxx.js

    这样大大方便了自动化灌库过程,之前我还傻乎乎的登上mongo shell,一句一句的输命令。现在只需要写一个bash脚本就能实现自动化灌库+建索引等等。不过没有console.log的话也不知道命令运行的状态这点有点坑

    最后跑MapReduce时挂了,提示文档太大。mongodb默认单文档最大不超过16M:

    想了想,如果200w条数据都含有同一个feature,那么这个feature倒排索引得到的文档大小=200 * 10000 * caseId(大概26个字节) / 1024 / 1024 = 50M。目前想到的方法是分表,MapReduce过程中通过scope选项注入变量start/end,每次map过程start递增,当start>=end时结束;下次令start=end,end += numLimit,继续执行MapReduce。不过分表还是无法应用null查询,难道把200w的caseId捞回来再利用$nin来查么?鉴于后来发现之前是将索引建错了地方,修改后的查询效率,在不查null时还算令人满意,就把这个优化暂时放一边了。之后有时间在搞吧,现在需求太多......

    本来想遍历数据建立null索引,如:第一条数据没有feature1的话就建立一个feature1_null字段,后来发现遍历数据添加字段的效率只有500条/s左右,100w条数据需要跑30分钟,不太能令人满意,就也先放一边:

    db = db.getSiblingDB('code');
    
    let features = {
        ...
    };
    
    let cols = db.getCollectionNames();
    for (let i = 0; i < cols.length; i++) {
        if (cols[i].indexOf('_') !== -1) {
            let col = cols[i],
                cursor = db[col].find(),
                docs = cursor.toArray();
    
            while (docs.length) {
                docs.forEach(function (doc) {
                    let feature = doc.feature,
                        local_features = [];
                    for (let key in feature) {
                        doc[key + '_' + feature[key]] = 1;
                        local_features.push(key);
                    }
                    for (let key in features) {
                        if (local_features.indexOf(key) === -1) {
                            doc[key + '_null'] = 1;
                        }
                    }
                    db[col].update({_id: doc._id}, doc);
                });
                if (cursor.hasNext()) {
                    cursor.next();
                    docs = cursor.toArray();
                } else {
                    break;
                }
            }
    
            for (let key in features) {
                features[key].forEach(function (val) {
                    let index = {};
                    index[key + '_' + val] = 1;
                    db[col].createIndex(index);
                });
    
                let index = {};
                index[key + '_null'] = 1;
                db[col].createIndex(index);
            }
            db[col].createIndex({finalCode: 1});
        }
    }
  • 相关阅读:
    C# Bitmap类型与Byte[]类型相互转化
    博客园添加个人Github链接
    C# Exception has been thrown by the target of an invocation(调用的目标已抛出异常) 解决办法
    C# 使用Renci.SshNet连接SSH远程服务器
    Oracle 查询当前数据库版本信息
    Oracle ORA-12569: TNS:包校验和失败
    Oracle Rollup()函数
    Oracle 字符串补零
    DataGridView 表格排序后颜色丢失
    DataGridView 实现最后一列的宽度自适应
  • 原文地址:https://www.cnblogs.com/cqq626/p/8017155.html
Copyright © 2020-2023  润新知