Redisで全文検索

台風一過で天気が良いのでnodeとRedisのSETを転置インデックスに使った全文検索を作った.
wikipediaのタイトル一覧(約100万件)をtri-gramで分解しRedisに突っ込んだ結果,約800万件の転置インデックスを挿入するのにおよそ3分,サイズは700MB程度だった.
同じようなことをmongodbでもできるが挿入速度ではRedisが有利だろう.

転置インデックス作成

const redis = require('redis');
const byline = require('byline');
const fs = require('fs');
const client = redis.createClient();

const ngram = function(str, n) {
  n = n ? n : 3;
  var grams = [];
  for(var i = 0; i <= str.length - n; i++) {
    grams.push(str.substr(i, n).toLowerCase());
  }
  return grams;
};

if(process.argv.length < 3) {
  process.stderr.write('argv error\n');
  process.exit(1);
}
var rs = fs.createReadStream(process.argv[2], { encoding: 'utf8' });
var ls = byline.createLineStream(rs)
var titles = [];
var iis = [];
var index = 0;
var ic = 0;
ls.on('data', function (data, line) {
  titles.push('key-' + index);
  titles.push(data);
  var grams = ngram(data); // Tri-gram
  for(var i = 0; i < grams.length; i++) {
     iis.push(['sadd', grams[i], index]);
  }
  index++;

  if(titles.length > 100000) {
    ls.pause();
    client.mset(titles, function(err, res) {
      ls.resume();
    });
    titles = [];
  }

  if(iis.length > 500000) {
    ic += iis.length;
    ls.pause();
    client.multi(iis).exec(function(err, res) {
      ls.resume();
    });
    iis = [];
  }
});

ls.on('end', function () {
  client.mset(titles, function(err, res) {
    client.multi(iis).exec(function(err, res) {
      ic += iis.length;
      console.log(ic);
      client.end();
    });
  });
});

検索

const redis = require('redis');
const client = redis.createClient();
const byline = require('byline');
const fs = require('fs');
const ngram = function(str, n) {
  n = n ? n : 3;
  var grams = [];
  for(var i = 0; i <= str.length - n; i++) {
    grams.push(str.substr(i, n).toLowerCase());
  }
  return grams;
};

const search = function(str, n) {
  var grams = ngram(str, n);
  var t = new Date;
  client.sinter(grams, function(err, res) {
    res = res.map(function(e) { return 'key-' + e; });
    client.mget(res, function(err, res) {
      console.log(res);
      console.log('----------------------------------------');
      console.log('' + res.length + 'results(' + (new Date - t) + 'ms)');
      client.end();
    });
  });
};

if(process.argv.length < 3) {
  process.stderr.write('argv error\n');
  process.exit(1);
}

search(process.argv[2]);