本文将大量用户和业务在使用多元索引时候遇到的问题总结为最佳实践提供给大家。
索引字段类型
多元索引中,不同类型字段的索引物理结构设计不同:
- Keyword 字段类型底层使用 FST+倒排链,因此在 Keyword 字段类型上做等值查询 TermQuery 速度快。
- Long 类型字段底层使用 BKD-tree,因此在 Long 类型上做范围查询 RangeQuery 速度快。
虽然 Keyword 也支持范围查询,Long 也支持等值查询,但是性能会差很多,数据量越大性能差距越明显,因此在 Tablestore 的宽表(主表)中导数据时候要注意提前规划好字段类型。
一些常见的容易用错的设计,说明如下:
- Type、Status 等枚举类型的值是0,1,2三种,虽然是数字,但是保存为字符串并索引为 Keyword 类型,对其进行 TermQuery 或 TermsQuery (类似 SQL 中 In 操作) 查询时候速度非常快。
- UserId 是一个长整形数字,但是 UserId 基本上都是等值查询 TermQuery,那么 UserId 需要用字符串存储。
- isDeleted 等状态,可能是0、1这种取值,需要保存为字符串或者 bool 类型。
如果已经上线的业务使用错了,又不方便修改存量业务的数据类型,可以考虑使用 “虚拟列” + “动态修改 schema” 进行字段类型纠正来达到加速的目的,动态修改 schema 功能来提供对原有业务的平滑升级,虚拟列来提供字段类型纠正。
主键设计
Tablestore 的主表根据分区键进行 Range 范围分区,主键的设计会影响多元索引的同步速度和部分场景下的查询水平拓展。
- 主键需要尽可能的离散,比如用 md5 进行哈希处理。一些常见的反例就是使用自增 id、当前时间戳进行当做分区键,可以参考 表格存储表设计 。
- 如果需要在主表上根据主键前缀进行批量数据的拉取,可以进行一些特殊的主键设计,然后查询时候直接在主表上进行 GetRange 查询,可以快速拉取数据
- 如果使用多元索引中经常使用某一个字段进行等值查询 TermQuery,例如淘宝 App 中每个查询 Query 基本都会携带 UserID=xxx,那么推荐将 UserId 放到主键中,具体优化详见本文“索引路由优化”这一章节。
主键设计对索引同步速度有一定的影响,对主键的要求是写入要尽可能分散到主表的不同分区,因此如果写入 TPS 较高,需要尽可能分散在主表多个分区中,避免出现写单分区、写尾部分区等问题。多元索引目前仅支持从主表中异步写入到索引中,实时同步的架构升级还在设计中。
错误例子:
- 用户拼接“UserID+商品ID”当分区键,且商品ID是自增的,如果某个 UserID 是大用户,大用户写的 TPS 太高的话,会发生写尾部问题,永远写的是 Tablestore 主表的最后一个分区,这样写入对同步延时和查询都不太友好。可以考虑把商品ID进行 md5 处理,如果不需要在主表上根据 UserID 进行范围查询,那么分区键直接设置为 md5(商品ID),如果需要在主表上根据 UserID 进行批量查询,那么分区键就是 UserID+md5(商品ID)。
索引预排序
如果在多元索引中大多数查询 Query 都是按照某一个模式进行排序的,那么设置预排序能够节省一些排序时间。当命中的数据越多的情况下,预排序带来的性能优化效果会越好。默认情况下,多元索引按照主表的所有主键进行预排序。目前该功能仅支持使用 SDK 创建预排序的多元索引,参考:创建多元索引 。
索引路由优化
如果查询时候一定会带着某一个字段,例如淘宝 App 中每个查询 Query 基本都会携带 UserID=xxx,这时候就推荐使用路由优化。多元索引的数据分布默认情况下是根据主表的所有主键进行 Hash 分区,因此查询时候会访问索引引擎的所有分区,索引加上路由后,可以改变数据分布,根据路由键(而不是之前的所有主键)将数据进行 Hash 分区,因此一个确定的路由键一定会落在明确的一个或几个分区上。
如何使用路由参考:多元索引路由字段的使用 ,使用路由 (Routing) 优化可以带来如下好处:
- 显著减少长尾查询:没有使用路由前,每个查询都会访问索引引擎的每个分区,根据木桶效应,整体查询延时取决于最慢的那个分区,因此假如有一个分区有毛刺或者网络卡一下,会造成整体查询请求变慢。
- 支持更高的 QPS 查询能力:携带路由的请求仅访问索引引擎的部分分区,不会访问所有分区,因此不会有读放大,对集群的资源消耗较少,可以支持更高的 QPS。
- 带路由的多元索引设计合理的话,理论上没有规模上限。
常见误区:
- 用户 UserID 只有2个,但是表中有20亿行数据,那么意味着一个UserID 就有10亿数据,那么加路由后会导致索引分区过大,遇到这种情况可以联系多元索引研发进行评估和进行特殊方案处理。一般情况下,推荐路由键(如上述 UserID )的值要尽可能丰富,同一个路由键下的总数据要不要太多(如不要超过1亿),如果数据太多,可以考虑将多个不变的字段拼接为路由键。
- 如果用户设置 UserID 为路由键,但是遇到了 Query 中不指定 UserID 的场景,那么查询时候会访问引擎里所有的分区,不影响查询结果的完整性。
查询优化
如果查询 Query 特别复杂,例如条件过多、嵌套太深、Terms 查询中的元素过多等,查询延时很可能会比较高,因此推荐用户精简 Query,没有必要的条件尽可能地去掉。除此之外,目前服务端会自动进行查询改写和查询优化,一般情况下不需要用户特别关注。如果发现查询延时高,可以联系多元索引研发进行查询优化。
货币、价格之类的浮点数
由于 Tablestore 只支持普通的 Double,暂时不支持 BigDecimal 类型,但是业务方涉及到金钱之类的字段需要非常精准,因此这类字段推荐用 Long 进行存储,例如:5元3角2分存为53200
全文索引和模糊查询
MatchQuery 和 MatchPhraseQuery 是专门为分词 Text 类型字段的全文索引场景设计的查询。在Keyword类型字段上,MatchQuery 和 TermQuery 可能查询结果一致,但是 MatchQuery 会多一些额外的分词处理流程,性能相对较差,因此不要在 Keyword 字段类型上误用 MatchQuery。
对于通配符查询(WildcardQuery)中查询模式为 *word*
的场景,即任意子串查询需求,您可以使用模糊分词方式(模糊分词和短语匹配查询 MatchPhraseQuery 组合使用)来实现性能更好的模糊查询,具体参考 模糊查询。
翻页和Token编码
多元索引推荐使用 token 翻页方式 进行深度翻页,假如需要对 token (类型是byte[])进行持久化,可以使用 Base64 编码为字符串再进行存储。如果直接进行字符串编码,例如 new String(token)
会造成 token 内容丢失。
逻辑字段和物理字段映射
该章节主要解决“用户需要个性化的列名而多元索引支持的最多索引字段个数不够用”的问题。假如系统中有1k个用户,每个用户拥有个性化的列名。假如每个用户需要使用多元索引10个字段,那么总共需要10*1k=1万个字段,而当前多元索引不支持这么多字段,接下来使用“逻辑字段和物理字段映射”的思路来解决,让所有用户可以共享多元索引的一些字段。具体如下:
索引设计:假设用户只需要 Keyword、Long 这2种数据类型,然后再多元索引中提前创建索引,这个索引中包含200个字段(不同类型的字段数量可以根据业务需要个性化设置)和其它必备的非个性化字段,索引中固定的字段名如下
- keyword_1,keyword_2,....,keyword_100
- long_1,long_2,...,long_100
- 其它业务必要字段
准备一个 meta 表,Tablestore 的 Table 表或其它数据库表都可以,这张表的内容如果量不大的话最好缓存在内存中,和上面的索引的关系如下:
- “用户1”有 Keyword 类型字段 a、b、c,那么在 meta 表中对“用户1”记录一行数据:字段a->索引keyword_1,字段b->索引keyword_2, 字段c->索引keyword_3,有 Long 类型字段类推。
- “用户2”有 Keyword 类型字段 b、c、d,那么在 meta 表中对“用户2”记录一行数据:字段b->索引keyword_1,字段c->索引keyword_2, 字段d->索引keyword_3,有 Long 类型字段类推。
数据写入和查询需要根据 meta 表的映射来进行写入和查询。
- 数据写入:根据 meta 映射表,将物理字段写入到逻辑列中
- 数据查询:先根据 meta 映射表将用户查询字段进行转化,举例:“用户2”想查 b=4 && d=5,则转化为 keyword_1=4 && keyword_3=5。
大批量导数据前准备
- 第一次创建表格存储的 Table 后,在导数据之前,如果数据特别多,例如超过10亿行,可以联系研发进行 Table 的预分区,让导入速度更快。
- 数据量太大的情况下,推荐先在主表导完数据再创建多元索引,可以显著提升存量数据的索引构建速度。
分表问题
多元索引目前推荐一个索引200亿行以内数据,但不是意味着最大是200亿行,如果超过200亿的话可以联系多元索引研发进行一起评估和设计。举个例子:某用户最大的 log 表当前61亿,一年增长21亿,3~5年内不超过200亿,因此可以不用分表。如果存量数据超过200亿或有潜在超过200亿的可能性,增长很快,可以考虑分表,具体设计可以联系多元索引研发一起评估和设计,同时可以避免数据量特别大的时候一些潜在问题。