Phoenix 异步创建索引
当表数据量过大的时候,创建索引会报错,可以修改服务器端的 hbase.rpc.timeout
,默认是1分钟,可以自定义时间。也可以异步创建索引,通过在语句后面添加async
关键字。
需要注意的是:
- 异步创建索引只支持全局索引
- 执行async语句只是第一步,还需要通过执行jar包来保证索引真正的建立
1. 为什么只支持全局索引?
首先是本地索引和全局索引的一些概念和区别
- 本地索引
- 适合写多读少的情况
- 索引数据直接写在原表里,不会新建一张表。在
phoenix-sqlline
里执行!tables
的确会发现创建的本地索引表,但是那个只是一个映射,并不是单独存在的。由于索引数据直接写在表里,所以原表的数据量=原始数据+索引数据。 - 本地索引rowkey的设计规则: 原数据region的start key+”\x00”+二级索引字段1+”\x00”+二级索引字段2(复合索引)…+”\x00”+原rowkey。
- 索引数据和真实数据存放在同一台机器上,减少了网络传输的开销。同理,创建索引后的rowkey的最开始的部分是 原数据region的start key,这样在通过二级索引定位到数据后,可以在当前的region中直接找到数据,减少网络开销。减少网络开销,也意味着写入的速度会变快。但是多了一步通过rowkey查找数据的过程,所以读的过程就不如直接读取列族里的数据的速度快。
- 全局索引
适合读多写少的情况
索引数据会单独存在一张表里。
全局索引必须是查询语句中所有列都包含在全局索引中,它才会生效。
Select * 不会命中索引
select 具体的字段 from table where col …
col 必须是第一个主键或者是索引里包含的字段才会命中索引
如果索引表包含 a、b 三个字段,where 里有 a 和 c 两个字段,那么也不会走索引,因为c不在索引里,发现索引走不通,只能走全表为了命中索引,要把需要查询的字段通过 include 关键字来一起写入索引表里,也就是覆盖索引。
Phoenix 不能保证主表和索引表对应Region的本地化,所以也就无法根据索引表的结果再去查主表
写入数据的同时需要往索引表同步写数据,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗,所以写慢;但是查询的时候,如果命中了索引表,那就直接把数据带出来了,读会快。
综上,本地索引不是表,全局索引才是表,而async是针对表的一种方式,所以只能作用于全局索引
2. 如何执行async
- 首先是需要创建一个全局索引,同时使用 async
create index XXX on database.tablename(col1, col2) include(col3, col4) async
此时去看这个表,会发现 index_state
字段的值是 building,说明索引表还没创建好,这是因为 async 关键字会初始化一个mr作业,只是把创建索引的数据文件准备好,还没有正式开始
- 执行mr作业
1 | hbase org.apache.phoenix.mapreduce.index.IndexTool \ |
库名、表名、索引表名尽量都不要小写
这个命令执行后可能会报错,遇到 org.apache.phoenix.mapreduce.index.IndexTool 依赖的jar没法加载,那就可以换一个方式执行
1 | java -cp ./本地文件夹路径:/data1/cloudera/parcels/PHOENIX/lib/phoenix/phoenix-5.0.0-cdh6.2.0-client.jar org.apache.phoenix.mapreduce.index.IndexTool --schema 库名 --data-table 表名 --index-table 索引表名 --output-path hdfs路径指向一个文件件即可 |
本地文件夹里需要包含 hbase yarn hdfs 的配置文件
如果遇到 java.io.IOException: Can't get Master Kerberos principal for use as renewer
说明缺少yarn的配置文件
如果遇到 org.apache.hadoop.security.AccessControlException: Permission denied: user=phoenix, access=WRITE, inode="/user":hdfs:supergroup:drwxr-xr-x
需要在 hbase-site.xml
文件里添加 hbase.fs.tmp.dir
配置项,值是hdfs上一个有读写权限的目录路径。
原因:从 org.apache.phoenix.mapreduce.index.IndexTool 开始追代码,会找到 org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2,在配置mr作业的时候,configurePartitioner()
方法里 String hbaseTmpFsDir = conf.get("hbase.fs.tmp.dir", HConstants.DEFAULT_TEMPORARY_HDFS_DIRECTORY);
会去读取配置文件里的这个值,默认是 "/user/" + System.getProperty("user.name") + "/hbase-staging"
3. 附
- 查询执行计划,判断是否命中索引表
内容 | 含义 |
---|---|
CLIENT | 表明操作在客户端执行还是服务端执行,客户端尽量返回少的数据。若为 SERVER 表示在服务端执行。 |
FILTER BY expression | 返回和过滤条件匹配的结果。 |
FULL SCAN OVER tableName | 表明全表扫描某张业务表。 |
RANGE SCAN OVER tableName [ … ] | 表明代表范围扫描某张表,括号内代表 rowkey 的开始和结束。 |
ROUND ROBIN | 无 ORDER BY 操作时, ROUND ROBIN 代表最大化客户端的并行化。 |
x-CHUNK | 执行此操作的线程数。 |
PARALLEL x-WAY | 表明合并多少并行的扫描。 |
EST_BYTES_READ | 执行查询时预计扫描的总字节数。 |
EST_ROWS_READ | 执行查询时预计扫描多少行。 |
EST_INFO_TS | 收集查询信息的 epoch time |
- 在创建索引的过程中,发现了一个可能是版本bug的地方,已提官网issue,链接如下
问题:如果在创建本地索引时,有一个字段设置了default value,在生成的索引表里就只会显示默认值,不管是什么类型;如果这个类型是tinyint的话,还可能会造成之后主键的数据,原表的数据是对的,但是索引表是错的,如果命中了索引表,那么就返回的是错误的数据。
可能的解:RowKeyColumnExpression
和 RowKeyValueAccessor
两个类。
对表字段的修改,只能修改 char、varchar、decimal 等类型的长度,不可以直接修改类型,比如修改integer到bigint,会导致修改之后出现乱码,应该也是因为phoenix在对column index下标进行切分的时候,每个类型有自己的长度,随便修改类型,会导致数据转换错误
- union all 在使用时遇到问题
类似问题在 Encountered exception in sub plan [0] execution
描述:select user_id from (select user_id from table union all select user_id from table) where user_id in (select user_id from table)
这个sql执行后报 NullPointerException;换成 select t1.user_id from (select user_id from table union all select user_id from table)t1 inner join (select user_id from table)t2 on t1.user_id = t2.user_id
也会报错,
1 | Error: Encountered exception in sub plan [0] execution. (state=,code=0) |
参考链接写的那个方式,改成 select /*+ USE_SORT_MERGE_JOIN */ t1.user_id from (select user_id from table union all select user_id from table)t1 inner join (select user_id from table)t2 on t1.user_id = t2.user_id
就可以了,从官方文档上看,是将广播哈希连接替换成了排序合并连接。但是官网给的前提是当连接的两端都大于服务器端内存的容量时使用这一个hint,具体底层实现还不清楚。
附:
1 | PHOENIX_OPTS="-Dsun.security.krb5.principal=phoenix" |