Sphinx 索引更新机制

󰃭 2016-05-24

导读

在我之前的几篇文章里, 已经对sphinx 的索引创建和服务启动做了讲解, 并调用了测试脚本做了一些测试。那么随之而来的一个问题, 索引我们已经创建好了, 如果有数据更新或者删除, 我们应该怎么做才能在索引中体现出这些更新呢

如何平滑地重建索引

之前在索引创建的时候, 如下

sudo -u sphinx indexer -c sphinx_test.conf --all

或者

	sudo -u sphinx indexer -c sphinx_test.conf index_names

这种方式创建索引只能在索引服务未启动的时候创建。

那么我们应该如何在不停止索引服务的情况下建立索引呢?很简单, 在索引创建的时候加一个 --rotate 命令行参数即可。

其原理是新的索引在创建的时候,老的索引并不删除, 依旧提供服务, 在新索引创建完成后, 再依次使用新索引替代老索引, 从而实现索引更新

索引更新机制

那么索引创建好了以后, 我们的内容有更新了,或者有删除了, 那么这些更新和删除, 怎么在索引中体现出来呢

全量索引重建

第一种方法即全量重建索引。 这种方式我们可以通过把索引全量重新创建一遍, 即可将索引全部更新。

这种方式在数据量较小的情况下还是比较简单直接的, 因为即便是几万甚至十几万的数据, 基本上全量索引重建一遍也就十几秒最多几十秒的事。

但如果数据量达到了百万甚至更多的时候, 这种方式就没那么友好了, 问题提现在一下几点

  1. 每次索引重建非常耗时, 从数据库中读取数据时阻塞数据库的其他业务查询, 导致数据库卡死
  2. 占用资源太大, 每次索引更新时, 系统不仅仅要生成新的索引数据, 还要维护老的索引, 这样在索引重建时最大就会有两份索引存在, 不仅仅占用大量磁盘, 还占用了很多内存空间, 且每次大量的索引重建也会导致cpu飙升
  3. 大部分时间, 要更新的数据与老数据相比, 比重起始很小, 但为了这部分跟新, 把整个索引全部重建往往划不来。

这就引入了一个新的概念, 增量索引

增量索引

增量索引, 顾名思义, 是新增的数据量的索引, 这个新增不仅仅是表面的新增数据, 也包括了数据的更新,删除(这里只能是标记删除)

我们首先创建一个全量的索引article, 这个索引包含了已有的所以索引, 然后我们在创建一个增量索引article_delta, 这个索引每次创建都基于上次全量索引的创建时间,这部分索引量很小, 适合频繁的创建,那么我们每次查询的时候, 指定两个索引: article,article_delta, 索引服务会优先从delta中或查找数据, 如果没有再从article中查找

实现如下

source article:src_db{
    sql_query = select id,title, content, cid, unix_timestamp(ctime) ctime,\
                unix_timestamp(mtime) mtime from article

    sql_query_post = replace into sysconfig (varname, info, value) select \
                     "article_delta","article delta time node", max(mtime) from article

    sql_attr_uint = cid
    sql_field_string = title
    sql_field_string = content
    sql_attr_timestamp = ctime
    sql_attr_timestamp = mtime
}

source article_delta:article
{
    sql_query = select id,title, content, cid, unix_timestamp(ctime) ctime,\
                unix_timestamp(mtime) mtime from article where mtime > \
                (select value from sysconfig where varname = "article_delta")
}

index article{
    source = article
    docinfo = extern
    stopwords =
    min_prefix_len    = 0
    min_infix_len     = 0
    min_word_len      = 1
    path = /data/sphinx/test/article
    mlock = 1
    charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, _, $, U+FF21..U+FF3A->a..z,A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101,U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109,U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F,U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117,U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D,U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135,U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C,U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144,U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B,U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153,U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159,U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161,U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167,U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F,U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175,U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C,U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F,U+05D0..U+05EA, U+0531..U+0556->U+0561..U+0586, U+0561..U+0587, U+0621..U+063A, U+01B9,U+01BF, U+0640..U+064A, U+0660..U+0669, U+066E, U+066F, U+0671..U+06D3, U+06F0..U+06FF,U+0904..U+0939, U+0958..U+095F, U+0960..U+0963, U+0966..U+096F, U+097B..U+097F,U+0985..U+09B9, U+09CE, U+09DC..U+09E3, U+09E6..U+09EF, U+0A05..U+0A39, U+0A59..U+0A5E,U+0A66..U+0A6F, U+0A85..U+0AB9, U+0AE0..U+0AE3, U+0AE6..U+0AEF, U+0B05..U+0B39,U+0B5C..U+0B61, U+0B66..U+0B6F, U+0B71, U+0B85..U+0BB9, U+0BE6..U+0BF2, U+0C05..U+0C39,U+0C66..U+0C6F, U+0C85..U+0CB9, U+0CDE..U+0CE3, U+0CE6..U+0CEF, U+0D05..U+0D39, U+0D60,U+0D61, U+0D66..U+0D6F, U+0D85..U+0DC6, U+1900..U+1938, U+1946..U+194F, U+A800..U+A805,U+A807..U+A822, U+0386->U+03B1, U+03AC->U+03B1, U+0388->U+03B5, U+03AD->U+03B5,U+0389->U+03B7, U+03AE->U+03B7, U+038A->U+03B9, U+0390->U+03B9, U+03AA->U+03B9,U+03AF->U+03B9, U+03CA->U+03B9, U+038C->U+03BF, U+03CC->U+03BF, U+038E->U+03C5,U+03AB->U+03C5, U+03B0->U+03C5, U+03CB->U+03C5, U+03CD->U+03C5, U+038F->U+03C9,U+03CE->U+03C9, U+03C2->U+03C3, U+0391..U+03A1->U+03B1..U+03C1,U+03A3..U+03A9->U+03C3..U+03C9, U+03B1..U+03C1, U+03C3..U+03C9, U+0E01..U+0E2E,U+0E30..U+0E3A, U+0E40..U+0E45, U+0E47, U+0E50..U+0E59, U+A000..U+A48F, U+4E00..U+9FBF,U+3400..U+4DBF, U+20000..U+2A6DF, U+F900..U+FAFF, U+2F800..U+2FA1F, U+2E80..U+2EFF,U+2F00..U+2FDF, U+3100..U+312F, U+31A0..U+31BF, U+3040..U+309F, U+30A0..U+30FF,U+31F0..U+31FF, U+AC00..U+D7AF, U+1100..U+11FF, U+3130..U+318F, U+A000..U+A48F,U+A490..U+A4CF
    ngram_len         = 1
    ngram_chars       = U+3000..U+2FA1F
}


index article_delta: article
{
    source  = article_delta
    path    = /data/sphinx/test/article_delta
}

在上面, 我们为了保存全量索引时最新数据的时间节点给每次增量作为基准点, 特意创建了一个sysconfig表, 这样, 每次全量更新索引后, 后面的增量索引都能基于全量索引最新数据的时间节点

这样, 我们每隔几分钟甚至一分钟就重建一次增量索引, 就能保证新数据的更新, 而每隔一两个礼拜更新一次, 可以保证增量数据一直比较小, 这样增量索引就能很快的创建了

索引合并

还有一种方式索引合并, 这种方式实际上也是基于增量索引的, 每次也只创建增量索引, 而这个增量索引是基于上一次的增量索引时的最新数据的更新, 这样一段段的创建增量索引, 而每次创建好增量索引后, 就将其与主索引合并, 这样, 也能保证索引数据的更新。

这里我没有实例, 只写个模型, 做个参考

source main {
	sql_query = ......
}

source delta {
   sql_query = ...... where mtime > (select value from sysconfig where varname = "article_delta")
   slq_query =  replace into sysconfig (varname, info, value) select \
            "article_delta","article delta time node", max(mtime) from article
}

index main{
.......
}

index delta{
.......
}

每次增量索创建时

sudo -u sphinx indexer -c sphinx_test.conf delta --rotate && sudo -u sphinx indexer -c sphinx_test.conf --merge main delta --rotate

这里就说完了sphinx的常见的两种增量索引更新的方式,当然sphinx还有rtindex(实时索引), 允许我们通过sql直接更新索引数据, 但不常见, 暂且不说了

后面我们会说一下这种增量索引更新的时候常见的一个坑(已经标记删除的索引数据怎么被查出来了)以及如何将这个坑填上