一、问题描述
原来的日志系统使用的是ES作为底层存储,后来因为数据量大了之后,出现了写入存在阻塞和查询效率变低的问题。后来决定切换到Doris数据库。
Doris的优势根据公开资料来看,它在写入性能、查询效率和存储成本上,都优于ES。比如下面这个对比图:
但是真正上了生产环境之后才发现,各有各的坑。
首先我们遇到的问题就是默认情况下对中文日志的检索效果远远不如ES。具体比如说,搜索“预订下单接口”,搜索结果出来还出现了同时包含“预订”“下单”“接口”的日志信息,也就是说这一句话被分词后进行了分别匹配。比如说:
这个结果也被搜索了出来,这显然不是我们期望看到的结果。但是在ES上进行同样搜索的时候就不存在这个问题。
二、解决探索
从理论上分析导致这个搜索结果有两个原因:
- 是入库阶段对文本进行分词操作的时候,按照较细粒度的中文词拆分后入的库;
- 是在检索的时候是简单的按照是否包含词来判断匹配情况,而没有按照短语进行匹配。
针对第一个原因的探究,我们首先来看看Doris是如何分词入库的。
1.查看Doris官方文档我们得知,在表创建时,可以通过指定索引的倒排索引属性值,来对索引的分词和短语支持进行设置的,官方文档:倒排索引 - Apache Doris
其中最关键的就是parser、support_phrase、parser_mode三个参数。
2.查看我们现有的表结构可以获知目前我们的表是这么设置这些参数的:
CREATE TABLE `logs` (`alllogs` text NULL COMMENT '日志',INDEX idx_alllogs (`alllogs`) USING INVERTED PROPERTIES("support_phrase" = "false", "char_filter_pattern" = ".,", "parser" = "unicode", "lower_case" = "true", "char_filter_replacement" = " ", "char_filter_type" = "char_replace") COMMENT 'idx_alllogs'
) ENGINE=OLAP ......
parser:在这里的用处就是指定索引的分词器,这里我们原来设置的是unicode。
support_phrase:使用了false,也就是关闭了短语支持。同时parser_mode就没有必要设置了。
3.我们来看看parser的三个分词器english、chinese、unicode效果上有什么差异:
SELECT TOKENIZE('预订下单接口报错 at com.test.com.CheckOneTwo.doCheck', '"parser"="english"');
--运行结果:["at", "com", "test", "com", "checkonetwo", "docheck"]SELECT TOKENIZE('预订下单接口报错 at com.test.com.CheckOneTwo.doCheck', '"parser"="chinese"');
--运行结果:["预订", "下单", "接口", "报错", "com", "test", "com", "CheckOneTwo", "doCheck"]SELECT TOKENIZE('预订下单接口报错 at com.test.com.CheckOneTwo.doCheck', '"parser"="unicode"');
--运行结果:["预", "订", "下", "单", "接", "口", "报", "错", "com.test.com.checkonetwo.docheck"]
可以看到三种结果都各有各的问题:english直接忽略了中文,chinese虽然按中文词的粒度进行拆分但是又把包名进行了单次拆分,而unicode虽然把包名当成了整体但是却把中文拆成了单个字。
而如果我们在ES中使用IK分词器它的分词结果会是:
"预订","订下","订","下单","接口","报错","com.test.com.checkonetwo.docheck","com","test","com","checkonetwo","docheck"
显然从分词效果来看IK分词是最好的,但是遗憾的是Doris目前的版本并不支持IK分词(Doris官方计划2025年年内支持)。
仅仅基于目前的条件,parser设置为chinese或者unicode都可以。而对效果影响最大的应该是短语搜索的支持。
4.support_phrase设置为true之后,就支持短语搜索了,具体的修改过程如下:
SHOW INDEX FROM logs;
--查看表大小
SHOW DATA FROM logs;
--查看索引任务
show BUILD INDEX WHERE TableName="logs";--删除索引
ALTER TABLE logs DROP INDEX idx_alllogs;--尝试1:改为support_phrase=true, parser=chinese。
ALTER TABLE logs ADD INDEX idx_alllogs(`alllogs`) USING INVERTED PROPERTIES("support_phrase" = "true", "parser" = "chinese", "lower_case" = "true") COMMENT 'idx_alllogs';--尝试2:改为support_phrase=true, parser=unicode。
ALTER TABLE logs ADD INDEX idx_alllogs(`alllogs`) USING INVERTED PROPERTIES("support_phrase" = "true", "parser" = "unicode", "lower_case" = "true") COMMENT 'idx_alllogs';--重建索引
BUILD INDEX idx_alltext ON logs_message_crs;
这里我测试了support_phrase设置为true之后,分别设置chinese和unicode两个分词器的索引,观察了下存储代价。
logs表在support_phrase设置为false时,表大小3.9G。
改为support_phrase=true、 parser=chinese,表大小变为了8.9G。
改为support_phrase=true 、parser=unicode,表大小变为了8.0G。
可见短语支持让表空间几乎大了一倍。根据我们生产环境同样数据与ES的横向对比得出一个结论:要Doris实现与ES同样的中文搜索效果,可能在存储空间大小上Doris并无优势。
接下来我们针对上面的第二个原因进行探究,也就是查询需要使用短语查询。
查询的时候有四种查法。
1. LIKE:跟MySQL差不多,如果使用LIKE "%keyword%"进行匹配,大部分时候是用不到索引的,会进行全表扫描,查询效率极低,所以不推荐。
2.MATCH_ANY:"keyword1 keyword2",多个搜索关键字时,只要包含其中任意一个就会命中。值得注意的是如果keyword是多个中文短语,那么会被拆成词语后进行匹配。比如说“预定信息 错误异常”,会把只包含“预定”的记录也会匹配出来。也就是这里并没有用上短语匹配。
3.MATCH_ALL:"keyword1 keyword2",多个搜索关键字时,包含全部关键字才会命中。同样值得注意的是是如果keyword是多个中文短语,那么会被拆成词语后进行匹配。比如说“预定信息 错误异常”,它会把同时包含“预定” “信息” “错误” “异常”的匹配出来,是拆分后进行匹配的。同样也没有用上短语匹配。
4.MATCH_PHRASE:这个才是我们需要用到的短语匹配的能力。当我们需要MATCH_PHRASE "预定订单错误"时,他会把"预定订单错误"当成一个短语整体进行匹配,而不是拆分成单个词进行匹配。但是这里又有一个新的问题需要注意,就是多个关键词匹配的问题,如果我们需要搜索同时包含“预定信息 错误异常”的日志,那么使用MATCH_PHRASE "预定信息 错误异常"将无法得到我们想要的结果,因为它会把"预定信息 错误异常"当成一个整体,而不是以空格为切分。那多个关键字如何匹配呢,其中一个简单的办法就是使用多个MATCH_PHRASE进行匹配,比如说:MATCH_PHRASE "预定信息" AND MATCH_PHRASE "错误异常",这样就能达到预期的效果。
三、结论
好了,以上就是整个调优和分析的过程,总结一下如何解决Doris的中文检索效果,有如下几个关键点:
1.打开support_phrase=true,使其索引支持短语检索。但是要注意这里将会带来额外的存储开销。
2.parser设置为chinese或者unicode,使其支持中文分词。
3.使用MATCH_PHRASE进行短语匹配。
希望上面的内容对你有所帮助,如果你还遇到其他问题,欢饮留言区讨论。
参考材料:为什么 Apache Doris 是比 Elasticsearch 更好的实时分析替代方案?-腾讯云开发者社区-腾讯云
Doris如何做到将查询性能提升100倍 - 墨天轮
Doris 超全索引实战教程 - 墨天轮
深入剖析 Doris 倒排索引(上):原理与应用全解析_doris 分词器-CSDN博客
倒排索引 - Apache Doris