多元索引最佳实践

本文从数据表设计、多元索引设计、多元索引使用三个方面介绍了使用多元索引的最佳实践。

数据表相关设计实践

主键设计

Tablestore数据表根据分区键进行Range范围分区,主键的设计会影响多元索引的同步速度和部分场景下的查询水平拓展。

  • 主键需要尽可能的离散,例如使用MD5进行哈希处理。常见的反例包括使用自增ID、当前时间戳作为分区键。关于主键设计的更多信息,请参见表设计

  • 如果需要在数据表上根据主键前缀进行批量数据的拉取,则可以进行一些特殊的主键设计,然后查询数据时直接在数据表上进行GetRange操作来快速拉取数据。具体操作,请参见读取数据

  • 如果要经常使用多元索引TermQuery查询某一字段的值,例如淘宝App中每个查询条件中均会包括指定UserId,则推荐将UserId设置为主键,具体优化操作请参见“索引路由优化”内容。

主键设计对多元索引的同步速度有一定的影响,对数据表主键的要求是写入数据时要尽可能分散到数据表的不同分区,因此如果写入TPS较高时,需要尽可能分散在数据表的多个分区中,避免出现写单分区、写尾部分区等问题。目前仅支持从数据表中异步写入数据到多元索引中。

在订单场景中,当用户拼接“UserId+商品ID”作为分区键且商品ID是自增的时,如果某个UserId的写TPS过高,则会发生写尾部分区问题,即写入的均为Tablestore数据表的最后一个分区,这样写入数据会影响多元索引的同步延时和查询性能。在此场景中可以将“商品ID”进度MD5处理,分区键设置的建议如下:

  • 如果无需在数据表上根据UserId进行范围查询,则建议直接使用“商品ID”进行MD5后处理的值作为分区键。

  • 如果要在数据表上根据UserId进行范围查询,则建议拼接“UserId”和“商品ID”进行MD5后处理的值作为分区键。

货币、价格的数据存储类型

Tablestore只支持Double类型,不支持BigDecimal类型。当实际业务中涉及到金额等要求非常精确的字段时,推荐使用Long类型进行存储,例如532分存储为53200。

分表存储

目前推荐一个多元索引的数据在200亿行以内。如果数据规模超过200亿行,请联系表格存储技术支持进行评估和设计。例如某用户最大的log表当前为61亿行,一年增长21亿行,3~5年内不会超过200亿行,因此可以不用分表存储。

当存量数据较大且增长很快时,可以使用分表存储,请联系表格存储技术支持进行分表评估和设计。

大批量导入数据前准备

首次创建Tablestore的数据表后,在导入数据前,如果数据较多,例如超过10亿行,请联系表格存储技术支持进行数据表的预分区,使数据导入速度更快。

数据量较大的情况下,推荐先将数据导入到数据表再创建多元索引,有利于提升存量数据的索引构建速度。

多元索引相关设计实践

索引字段类型规划

在多元索引中,不同类型字段的索引物理结构设计不同。请在数据表中写入数据时提前规划合适的字段类型。

  • Keyword字段类型底层实现使用FST(Finite State Transducers)算法+倒排链,因此使用Keyword字段类型进行精确查询(TermQuery)时速度快。

  • Long字段类型底层实现使用BKD-tree,因此使用Long字段类型进行范围查询(RangeQuery)时速度快。

常见索引字段类型设计示例如下:

  • Type、Status等枚举类型字段的取值为0、1、2三种,虽然字段取值为数字,但是通过在数据表中保存为字符串类型并在创建多元索引时索引为Keyword类型,您可以使用TermQuery或者多词精确查询(TermsQuery)(类似SQL中的in操作)对枚举类型字段进行快速查询。

  • UserId字段的取值为一个长整型数字,实际业务中通常使用UserId进行TermQuery,因此UserId需要在数据表中保存为字符串类型。

  • isDeleted等状态字段的取值可能为0或者1,因此需要在数据表中保存为字符串类型或者布尔类型。

如果实际业务中已使用了不合适的字段类型且不方便修改存量业务的数据类型,您可以使用虚拟列动态修改schema功能进行字段类型调整以达到加速查询的目的。其中动态修改schema功能用于实现对原有业务的平滑升级,虚拟列用于实现字段类型的调整。

索引预排序

使用多元索引查询数据时,如果大多数查询均按照某个模式进行排序时,则可以通过设置预排序来节省一些排序时间。

当命中的数据越多时,使用预排序带来的性能优化效果越好。默认情况下,多元索引按照数据表的所有主键进行预排序。目前该功能仅支持使用SDK创建预排序。具体操作,请参见创建多元索引

索引路由优化

使用多元索引查询数据时,如果均会带有某个字段时,例如淘宝App中每个查询均会带有UserId,则推荐使用索引路由优化。

多元索引的数据分布默认情况下是根据数据表的所有主键进行Hash分区,因此使用多元索引查询数据时会访问索引引擎的所有分区。如果为多元索引配置路由后,则可以改变数据分布,根据路由键(而不是之前的所有主键)将数据进行Hash分区,一个确定的路由键会落在明确的一个或多个分区上。使用路由键的好处如下:

  • 显著减少长尾查询:未使用路由键前,每个查询均会访问索引引擎的每个分区,整体查询延时取决于最慢的分区,因此如果有一个分区有毛刺或者网络卡顿,则会造成整体查询请求变慢。

  • 支持更高的QPS查询能力:带有路由的请求仅访问索引引擎的部分分区,因此不会有读放大,对集群的资源消耗较少,可以支持更高的QPS。

  • 如果多元索引的路由键设计合理,则多元索引的规模无上限。

关于使用路由键的具体操作,请参见多元索引路由字段的使用

使用路由键的常见问题如下:

  • 一般情况下,推荐路由键的值要尽量多样,同一个路由键下的总数据量不要太多(例如不要超过1亿行)。如果一个路由键下的数据太多,则建议将多个不变的字段拼接为路由键。

  • 如果用户设置UserId为路由键,但是遇到了Query中不指定UserId的场景,则查询数据时会访问引擎中的所有分区,不影响查询结果的完整性。

  • 如果用户查询QPS不高或者数据量小于2亿行,则无需使用索引路由优化。

逻辑字段和物理字段映射

当要使用的索引字段个数超过多元索引支持的最大索引字段个数时,您可以通过“逻辑字段和物理字段映射”方式使所有用户共享多元索引的一些字段。假设系统中有1000个用户,每个用户拥有个性化的列名,且需要使用多元索引的10个字段。具体设计步骤如下:

  1. 索引设计。

    假设用户只需要使用KeywordLong数据类型。创建一个多元索引,该多元索引包含200个字段(不同类型的字段数量请根据业务需要进行个性化设置)和其它业务必要的非个性化字段。多元索引中固定的字段名如下:

    • keyword_1,keyword_2,....,keyword_100

    • long_1,long_2,...,long_100

    • 其它业务必要的非个性化字段

  2. 准备一个meta映射表。如果meta映射表数据量不大,建议缓存到内容中。

    meta映射表中字段与已创建的多元索引字段的关系如下:

    • “用户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类型字段依次类推。

  3. 根据meta映射表的映射进行数据写入与查询。

    • 数据写入:根据meta映射表,将物理字段写入到逻辑列中。

    • 数据查询:根据meta映射表将用户查询字段进行转化,例如“用户2”要查询b=4&&d=5,则转化为keyword_1=4&&keyword_3=5。

多元索引使用实践

查询优化

如果查询Query特别复杂,例如条件过多、嵌套太深、TermsQuery中的元素过多等,则查询延时可能会较高,因此推荐用户精简Query,只保留必要的条件。

目前服务端会自动进行查询改写和查询优化,一般情况下无需关注。如果出现查询延时高的情况,请联系表格存储技术支持进行查询优化。

全文索引

匹配查询(MatchQuery)和短语匹配查询(MatchPhraseQuery)主要用于分词Text类型字段的全文索引场景。

Keyword类型字段上,MatchQueryTermQuery可能查询结果一致,但是MatchQuery会有额外的分词处理流程,性能相对较差,因此不建议在Keyword类型字段上误用MatchQuery。

模糊查询

对于通配符查询(WildcardQuery)中查询模式为*word*的场景,即“任意子串”查询需求,您可以使用模糊分词方式(模糊分词和短语匹配查询MatchPhraseQuery组合使用)来实现性能更好的模糊查询。具体操作,请参见模糊查询

翻页和Token编码

推荐使用Token翻页方式进行深度翻页。当要对Token(类型为byte[])进行持久化存储时,您可以使用Base64编码将Token编码为字符串后再进行存储。如果直接进行字符串编码,例如new String(token) ,则会造成token内容丢失。

关于Token翻页方式的更多信息,请参见排序和翻页