这周主要都花时间搞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});
}
}

mongodb倒排索引的更多相关文章

  1. HBase、MongoDB、cassandra比较

    前言 传统数据库遇到的问题,数据量很大的时候无法存储:没有很好的备份机制:数据达到一定数量开始缓慢,很大的话基本无法支撑:因此我们需要探究更加合适的数据库来支撑我们的业务. HBase 什么是HBas ...

  2. 玩转mongodb(七):索引,速度的引领(全文索引、地理空间索引)

    本篇博文主要介绍MongoDB中一些常用的特殊索引类型,主要包括: 用于简单字符串搜索的全文本索引: 用于球体空间(2dsphere)和二维平面(2d)的地理空间索引. 一.全文索引 MongoDB有 ...

  3. 一些开源搜索引擎实现——倒排使用原始文件,列存储Hbase,KV store如levelDB、mongoDB、redis,以及SQL的,如sqlite或者xxSQL

    本文说明:除开ES,Solr,sphinx系列的其他开源搜索引擎汇总于此.   A search engine based on Node.js and LevelDB A persistent, n ...

  4. ElasticSearch——原始文档和倒排索引

    一.原始文档 如上图所示, 第二象限是一份原始文档,有title和content2个字段,字段取值分别为”我是中国人”和” 热爱共X产党”,这一点没什么可解释的.我们把原始文档写入Elasticsea ...

  5. mongodb 索引分类

    一. 普通索引篇 1.创建索引 创建索引:db.person.ensureIndex({"age":1}).这里我们使用了ensureIndex在age上建立了索引.“1”:表示按 ...

  6. 【翻译】MongoDB指南/聚合——聚合管道

    [原文地址]https://docs.mongodb.com/manual/ 聚合 聚合操作处理数据记录并返回计算后的结果.聚合操作将多个文档分组,并能对已分组的数据执行一系列操作而返回单一结果.Mo ...

  7. 【翻译】MongoDB指南/CRUD操作(四)

    [原文地址]https://docs.mongodb.com/manual/ CRUD操作(四) 1 查询方案(Query Plans) MongoDB 查询优化程序处理查询并且针对给定可利用的索引选 ...

  8. 【翻译】MongoDB指南/CRUD操作(三)

    [原文地址]https://docs.mongodb.com/manual/ CRUD操作(三) 主要内容: 原子性和事务(Atomicity and Transactions),读隔离.一致性和新近 ...

  9. 【翻译】MongoDB指南/CRUD操作(二)

    [原文地址]https://docs.mongodb.com/manual/ MongoDB CRUD操作(二) 主要内容: 更新文档,删除文档,批量写操作,SQL与MongoDB映射图,读隔离(读关 ...

随机推荐

  1. 我的Android进阶之旅------&gt;Android无第三方Jar包的源代报错:The current class path entry belongs to container ...的解决方法

    今天使用第三方Jar包afinal.jar时候.想看一下源码,无法看 然后像加入jar相应的源代码包.也无法加入相应的源代码,报错例如以下:The current class path entry b ...

  2. Android图片加载框架Picasso最全使用教程5

    在之前的四篇博客中,我们学习了所有的关于Picasso的主要方法,我们也对这个Picasso有了一个很深的认识,下面就主要对Picasso自身进行分析,这样的话,会让我们更了解Picasso的核心方法 ...

  3. 基于mondrain 的原理纠正特殊指标值

    原文地址:http://www.cnblogs.com/qiaoyihang/p/7348385.html 下面有两张表 数学试卷成绩 表1 学号 省份 批次 学校 试卷成绩 数学试卷小题成绩 表2 ...

  4. 在用 JavaScript 工作时,我们经常和条件语句打交道,这里有5条让你写出更好/干净的条件语句的建议。

    1.多重判断时使用 Array.includes 2.更少的嵌套,尽早 return 3.使用默认参数和解构 4.倾向于遍历对象而不是 Switch 语句 5.对 所有/部分 判断使用 Array.e ...

  5. beego——模板语法

    一.基本语法 go统一使用{{和}}作为左右标签,没有其它的标签符号. 使用"."来访问当前位置的上下文,使用"$"来引用当前模板根级的上下文,使用$var来访 ...

  6. Php DOMDocument 中的 formatOutput

    Nicely formats output with indentation and extra space 是否处理 缩进和多余的空白符

  7. mfc学习---文档视图架构

    MFC的AppWizard可以生成三种类型的应用程序:基于对话框的应用.单文档应用(SDI)和多文档应用(MDI).   一般情况下,采用文档/视结构的应用程序至少应由以下对象组成:       1. ...

  8. Linux 搭建 SVN

    一.yum 安装 subversion yum -y install subversion 二.创建svn版本库所在路径(建议放在opt.usr.home下) mkdir -p /usr/local/ ...

  9. Json日期格式 转化为 YYYY-MM-DD-hh-mm-ss

    function timeStamp2String(time) { var datetime = new Date(); datetime.setTime(time); var year = date ...

  10. Saltstack 命令行:批量覆盖指定文件

    master指定文件,覆盖到所有minion命令 salt-cp '*' /etc/hosts /etc/ -------------------------------------------- { ...