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});
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM