问题背景
做一个数据库表查看、标注与分析的功能。
\(Table\)是数据库1中的表的信息(information_schema.tables);\(Documentation\)是\(Table\)的数据字典文档,存储在本地文件中;\(Annotation\)是对\(Table\)的额外标注信息,存储在数据库2中。每一条\(Table\),最多关联到一条\(Documentation\)和一条\(Annotation\)。
现在想搜索\(Table\)。前端向后端提供3个参数,搜索关键词列表、当前页码、每页条数;后端的搜索逻辑是,如果一条完整数据(\(Table\)+\(Documentation\)+\(Annotation\))包含所有搜索关键词,则将\(Table\)加入搜索结果中。
\(Table\)的数量目前为6000+,要做到秒级搜索。
第一版实现
因为跨数据源,所以不能简单连表查询。
对于每个\(Table\),查出\(Documentation\)、\(Annotation\),然后将\(Table\)、\(Documentation\)、\(Annotation\)中要搜索的字段值取出来,用空格隔开拼接为字符串,形如"Table字段值 Documentation字段值 Annotation字段值",我们称之为\(SearchKey\)(搜索键)。如果每个关键词都包含在\(SearchKey\)中,则将\(Table\)加入搜索结果。
搜索时,先获取所有\(Table\),然后遍历每个\(Table\),获取\(SearchKey\)并判断是否加入搜索结果。
为了提高速度,用Redis缓存\(Table\)对应的\(SearchKey\)。
分析数据情况:
- \(Table\)只增、不删、不改,因此,搜索时要重新获取所有\(Table\),确保搜索到新\(Table\);不必考虑驱逐(evict)\(SearchKey\)的缓存。
- \(Documentation\)不增、不删、不改,因此,不必考虑驱逐\(SearchKey\)的缓存。
- \(Annotation\)增、删、改,因此,要在\(Annotation\)增、删、改之后建立、驱逐对应\(SearchKey\)的缓存,确保搜索到\(Annotation\)的最新信息。
实测结果:
- 实现了功能,支持同时按\(Table\)、\(Documentation\)、\(Annotation\)的字段搜索。
- 有性能问题,即使缓存已经全部完成,但每次搜索都要耗时30s左右,原因是6000+个\(Table\)遍历从Redis获取\(SearchKey\),每次耗时1~15ms,累计耗时长。
第二版实现
优化缓存策略。
获取所有\(Table\)后,构建\(SearchKeyMap\)(\(Table\)→\(SearchKey\)),然后将\(SearchKeyMap\)缓存,这样,下一次搜索时,只需要从Redis获取一次,提高传输效率。
为了确保搜索到新\(Table\),缓存\(SearchKeyMap\)时将\(Table\)列表的长度作为缓存键,如果新增了\(Table\),则\(SearchKeyMap\)不会命中缓存,会重新构建。
为了减少构建\(SearchKeyMap\)的时间,仍然保留单个\(SearchKey\)的缓存,仍然在\(Annotation\)增、删、改之后建立、驱逐单个\(SearchKey\)的缓存,但不同的是,还要同时驱逐\(SearchKeyMap\)的缓存。
实测结果:
- 性能提升明显,在缓存全部完成的情况下,搜索耗时降至1.3s。
- 仍然有性能问题,对一个\(Annotation\)做了增、删、改,会驱逐整个\(SearchKeyMap\)缓存,重建\(SearchKeyMap\)就又回到了遍历\(Table\)的情况,耗时30s左右。