- 调试指南
- 主键优化
- 常规优化
- 数据库设计优化
- 索引优化
- 查询优化
- 写入优化
- 更多优化
1. 调试指南
调整phoenix比较复杂,但是需要知道一些与它相关的工作原理,你可以有效的优化你的读写性能。最重要的性能影响因素是你的数据库设计,特别是它影响到hbase底层的row keys时。可以查看“常规优化”下面针对不同的预期数据访问样式做相对应的优化建议。随后的章节描述二级索引、碰撞和explain plan执行计划。
注意:Phoenix和Hbase可以很好的工作,当你的应用执行点查询或者小范围扫描时。这可以通过良好的主键设计实现(查看下文)。如果你的应用有一些必须要做全表扫描,Phoenix和Hbase不适合做这些工作。相反地,可以使用其他工具直接写入HDFS,使用柱状的代表工具例如Parquet。
2. 主键优化
底层的row key设计是最简单最重要的因素影响Phoenix的性能,在设计时,正确的使用它非常重要,如果你要更改它,你就必须重新写入数据和重建索引。
Phoenix的主键被连接起来,在Hbase的底层进行创建row key。主键约束的列应当和普通查询的样式一致的选择和排序,即选择最经常访问的列作为主键。把该点放在你的最核心的位置是最有效的优化方式。例如,如果你以包含人名ID的为开始主键,那么它会很容易的查询所有行关于特殊的人员信息。你可以在Hbase的row timestamp添加到主键中,这样可以改善扫描效率,通过跳过不在范围内的时间的扫描。
每个主键都需要成本,因为整个row key都会加入到内存和磁盘的每一块数据块中。row key越大,存储开销越大。
例如,设法将信息紧凑地存储在计划用于主键存储增量而不是完整时间戳的列中。
总结,最好的实践是设计好主键,将它加入到row key设计中,让你扫描更少的数据。
提示:当选择主键,在最需要优化的查询中过滤最频繁的列开始,如果你选择ORDER BY在你的查询中,请确认你的主键列是否匹配到你的ORDER BY的表达式。
主键单调递增
如果你的主键是单调递增,使用加盐帮助通过集群分布式写入,和改善并行能力,例如:
CREATE TABLE … ( … ) SALT_BUCKETS = N
最理想的性能是加盐桶的数据量约等于region server的数量。不要自动加盐。只有遇到热点数据时才自动加盐。加盐的缺点是它会增加读取成本,因为在查询数据时必须运行多个查询才能进行范围扫描。
3. 常规优化
按照该章节提供的一些常规优化在不同的场景下使用。
当数据进行随机访问
与任何随机读取工作负载一样,SSD可以提高性能,因为它们具有更快的随机寻道时间
当数据读负担大或者写负担大时?
读负担大时:
- 创建全局索引。它将会影响写入的性能,取决于一个索引有多少个列,因为索引得分别写入他们自己独立的表中。
- 使用多个索引提供快速的访问普通查询。
- 在为HBase指定机器时,不要在cpu颗数上节约;HBase需要它们。
写负担大时: - 预分裂表。它可以帮助切割表在预先定义的region或者键在单调递增时使用加盐去避免创建写入热点数据在小数量的节点。使用真实的数据类型而非原始的字节数据。
-创建本地索引。从本地索引读取数据会有性能损耗,所以提前性能测试是非常重要的。可以查看Pherf工具。
当列经常被访问时?
选择常用查询列作为主键。更多的信息,可以查看“Primary keys”在下面:
-创建附加的索引去支持常见的查询模式,包括大量访问的非主键字段。
数据只能追加(数据不可变)
-如果数据不可变或者只能追加,在创建表和索引的同时,使用IMMUTABLE_ROWS选项进行声明表和索引处于不可变,这样可以减少数据写入的时间成本。如果你需要对一个已经存在的表做不可变,你需要使用到ALTER TABLE trans.event SET IMMUTABLE_ROWS=true在创建之后。如果速度比数据完整性更重要,你可以使用DISABLE_WAL选项。注意:它可能会丢失数据在DISABLE_WAL,如果一个region服务器宕机时。
-设置UPDATE_CACHE_FREQUENCY选项如果你的元数据不经常发生改变,则设置成15分钟。这个属性决定多频繁的RPC去确认你看到的模式是最新的。
-如果数据不是松散的(超过50%以上的单元格是有值的),使用SINGLE_CELL_ARRAY_WITH_OFFSETS在Phoenix4.10中引入了数据编码方案,通过减少数据的大小,来获得更快速的性能。关于更多的资料,可以参考Phoenix的博客《列映射和不可变数据的编码》。
如果表很大?
-使用ASYNC关键字在你创建异步索引时候调用,通过MapReduce作业,你需要启动该任务;可以查看详细内容在此。
-如果数据太长以至于无法完全扫描表,使用主键创建一个复合行键使它更容易去返回数据的子集,或者促进skip scanning--Phoenix当查询在谓词中包含键集时,直接跳转到匹配的键。
事务是否必须?
事务是一种原子性的数据操作,即保证完全成功或完全失败。举个例子,如果你需要对表的数据进行交叉更新的话,你需要考虑你的数据事务。
-如果你需要使用,使用TRANSACTIONALoption。(同样也可查看http://phoenix.apache.org/transactions.html)。
块编码
必须使用压缩或编码。SNAPPY和FAST_DIFF都是不错的选择。
FAST_DIFF编码默认情况下,在所有Phoenix表上都是自动启用的,并且几乎总是通过允许更多的数据放入块缓存来提高整体的读取延迟和吞吐量。注意:FAST_DIFF编码会增加请求处理过程中产生的垃圾。
设置编码在表创建时。例子:
CREATE TABLE ...(...) DATA_BLOCK_ENCODING='FAST_DIFF'
4. 数据库设计优化
因为设计影响数据的写入在Hbase底层,Phoenix的性能依靠你的表、索引和主键的设计。
Phoenix和Hbase数据模型关系
Hbase存储数据在表中,而表又包含列簇和列成员。Hbase表中行数据由版本控制单元组成关联着对应一个或者多个列。Hbase的行数据包含着多个键值对数据,其中它们rowkey的属性是相同的。数据在Hbase表中是排序的通过rowkey进行。所有的访问都通过rowkey完成。Phoenix在Hbase之上创建了一个关系数据模型,强制主键约束,主键约束的列被组合起来成为底层Hbase表的row key。为此,了解PK的大小和数量的约束是非常重要的,因为row key副本中包含每一个单元格在底层的Hbase的表中。
列簇
如果有一些列访问的比其他更频繁,创建多个列簇去分离经常被访问的列。这将会改善性能因为Hbase的读仅是在一个指定的列簇中完成。
列
这里有一些常用的技巧应用在列上,无论它们是否是索引:
- 由于IO的成本,将varchar列压缩在1MB以下。在处理查询时,Hbase物化所有的单元格,在发送它们给客户端之前,客户端在将它们交给应用程序代码之前将它们进行全部接收。
- 在结构对象中,不要使用JSON,因为不是很紧凑。可以使用protobuf、avro、msgpack或者BSON。
- 考虑在存储之前使用快速LZ变体压缩数据,以减少延迟和I/O成本。
- 使用列映射特性(Phoenix 4.10)在使用非PK的Hbase数值列时,去替换直接使用列名称。将会改善Hbase在已排序的单元数据中进行查找的性能,添加特性通过减少表在磁盘中的使用大小全面提升性能和加速DDL操作,比如列重命名、元数据级别删除。可以查看更多的信息在列映射和不可变数据编码在Apache Phoenix博客中。
5. 索引优化
Phoenix索引是一个物理表,它存储主表中部分或全部数据的一个旋转副本,去提供指定的查询。当你发布一个查询,Phoenix会自动选择最佳的索引进行查询。主键索引是自动创建基于你选择的主键。你可以创建二级索引,根据索引将支持的预期查询指定包含哪些列。
也可以查看二级索引
二级索引
二级索引可以改善读性能,通过将普通的全表扫描,转换成范围扫描(以牺牲存储空间和写入速度作为代价)。二级索引可以追加或者删除在表被创建之后,不需要对现在的查询进行更改-达到查询只会运行的更快。少量的二级索引通常是足够的。根据你的需求,可以考虑创建覆盖索引或者函数索引,或者两者都创建。
如果你的表是非常大的,使用ASYNC关键字和**CREATE INDEX去异步创建索引(同步创建索引,可能导致整张表被锁住,无法对外提供服务)。在这种情况下,这个索引的创建需要通过MapReduce,这意味着客户机的在线或者离线都不会影响索引的创建和这个任务在必要时会自动完成重试工作。如果你需要手动的启动任务,你可以监控该任务就像你监控其他MapReduce任务一样。
例子:
create index if not exists event_object_id_idx_b on trans.event (object_id) ASYNC UPDATE_CACHE_FREQUENCY=60000;
可以查看Index Population更详细。
如果你由于某些原因不能创建异步索引,那么增加查询超时时间(phoenix.query.timeoutMs)在对于大表进行构建索引时。如果CREATE INDEX执行超时或者客户端掉线在执行未完成时,这个索引的创建将会被停止,必须重新执行它一遍。你可以监控索引表的创建--你可以看到分割时创建新的分区产生。你可以查询SYSTEM.STATS表,它将会随着拆分和压缩的发生而填充。你可以直接运行count(*)对索引表进行统计,尽管着会增加负载在你的系统上,因为必须进行全表扫描。
技巧:
- 创建本地索引在写负担重的用例上。
- 创建全局索引在读负担重的机器上。为了节约读时开销,可以考虑覆盖索引。
- 如果主键是单调递增,创建加盐分桶。这个加盐分桶不会被改变。所以设计它时必须控制未来的增长。加盐分桶可以帮助避免写热点数据问题,但是,由于读取时需要额外的扫描,因此可能会降低总体吞吐量。
- 设置定时任务去创建索引,使用ASYNC和CREATE INDEX去避免堵塞。
- 只创建你需要的索引。
- 限制索引数据,在经常更新的表上。
- 使用覆盖索引去转变表扫描成有效的点查询或者范围查询在索引表中替代主键表:CREAET INDEX index ON table(...) INCLUDE(...)。
6. 查询优化
非常重要的是了解哪些查询执行是在服务端还是客户端,因为这可以影响性能在网络IO上或者其他瓶颈上。如果你的查询在一个十亿行的表,则需要在服务器端进行尽可能多的计算,而不是在将十亿数据传输至客户端上处理。另一方面,一些查询必须在客户机上执行。例如,对多个区域服务器上数据进行排序,需要在客户机上聚合和重新排序。
读
- 避免joins查询,除非另外一端比较小,尤其是频繁查询。对于较大的连接join ,查看“提示”章节。
- 在WHERE子句中,筛选主键约束中的主导列。
- 在WHERE子句中使用IN或IN或IN过滤第一个前导列,可以实现跳过扫描优化(skip scan optimizations)。
- 在WHERE子句中的相等或比较(<或>)可以实现范围扫描优化( range scan optimizations)。
- 让Phoenix使用统计优化查询并行性。如果在生产中使用Phoenix 4.2或更高版本,这将自动带来好处。
也可以查看 https://phoenix.apache.org/joins.html
范围查询
如果您经常从旋转磁盘扫描大型数据集,那么最好使用GZIP(但是要注意写速度)。使用大量内核进行扫描,以利用可用的内存带宽。Apache Phoenix使得利用多个内核来提高扫描性能变得很容易。
对于范围查询,HBase块缓存没有提供太多优势。
大范围查询
针对大范围查询,考虑设置Scan.setCacheBlocks(flase)即使整个扫描可以放入块缓存。
如果你主要执行大范围的查询,你甚至可以考虑运行Hbase在更小的堆和块缓存上,从而只应用OS的缓存,这将缓解一些垃圾回收的问题。
点查询
对于点查找,缓存数据集是非常重要的,您应该使用HBase块缓存。
提示
提示允许你运行查询处理行为,并执行哪个索引,执行哪种类型的扫描,以及使用哪种类型的连接因素等。
- 当进行查询,如果您希望在查询包含不在索引中的列时强制执行全局索引,请提示。
- 如果有必要,你可以执行大量数据的Joins操作和USE_SORT_MERGE_JOIN执行,但是大的join将会是昂贵的操作在大数据量上。
- 如果右表的执行内存超过了限制,可以使用NO_STAR_JOIN提示。
详细的查看:提示。
执行计划
一个执行计划可以告诉你大量的关于一个查询是如何运行的。去生成一个执行计划运行查询和解释这个执行计划,请查看这里参考。
并行计算
你可以改善并行计算在执行UPDATE STATISTICS命令。该命令通过确定称为导柱的键来细分每个区域,这些键彼此之间距离相等,然后使用这些导柱将查询分解为多个并行扫描。默认打开统计信息。使用Phoenix 4.9,用户可以为每个表设置导柱宽度。最佳导柱宽度取决于许多因素,如集群大小、集群使用、每个节点的内核数量、表大小和磁盘I/O。
在Phoenix 4.12中,我们添加了一个新的配置Phoenix .use.stats.parallelization ,控制是否应该使用统计数据来驱动并行化。注意,仍然可以运行stats收集。收集的信息用于估计在生成解释时查询将扫描的字节数和行数。
7. 写入优化
更新数据使用upsert
当使用UPSERT VALUES去写入大数据量记录,请以较小的批量关闭自动提交和批处理记录(尝试100行开始进行调优性能)。
注意:使用默认的fat驱动,使用executeBatch()不会提供任何收益。相反的,通过多次执行upsert values多次使用commit()进行提交给集群。通过thin驱动,使用executeBatch()非常重要,因为将最小化客户机和查询服务器的rpc数量。
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
int batchSize = 0;
int commitSize = 1000; // number of rows you want to commit per batch.
try (Statement stmt = conn.prepareStatement(upsert)) {
stmt.set ... while (there are records to upsert) {
stmt.executeUpdate();
batchSize++;
if (batchSize % commitSize == 0) {
conn.commit();
}
}
conn.commit(); // commit the last batch of records
}
注意:因为Phoenix客户机需要保存未提交的行在内存中,当心commitSize不要设置过高。
更新数据使用upsert select
当使用UPSERT SEELCT 去写入一些数据在在处于简单的状态,开启自动提交和行数据会自动按批次进行根据phoenix.mutate.batchSize。这将会最小化的返回客户机的数据量,并且是更新多行数据最有效的方法。
删除数据
当删除大量数据集时,开启autoCommit在发布删除查询时,因为这样客户机不需要在删除所有键时去记住它们的row key。这样可以防止客户端缓冲受删除影响的行,这样Phoenix旧可以直接在区域服务器上删除它们,不必要将它们返回给客户端。
减少RPC交互
减少RPC交互,设置UPDATE_CACHE_FREQUENCY (4.7或者以上的版本)在你的表和索引,当创建它们(或者时发布ALTER TABLE/INDEX调用时,可以查看https://phoenix.apache.org/#Altering
)。
使用本地索引
如果使用Phoenix 4.8,可以考虑使用本地索引来最小化写的耗时。在本例中,二级索引的写操作将与基本表位于相同的区域服务器上。不过,这种方法确实涉及到读取方面的性能问题,所以一定要量化写入速度的提高和读取速度的降低。
8. 更多优化
有一些建议在底层的Hbase和JVM层,可以查看操作和性能配置选项在Apahce Hbase的参考指南中。
特殊案例
下面的小节是前面提到的Apache Hbase参考指南中调优建议的补充。
对于应用而言,快速失败比等待更好
除了上面提及到了Hbase调优之外,设置 phoenix.query.timeoutMs在hbase-site.xml中作用于客户端以毫秒为最大容忍度度等待时间。
对于应用可以容忍稍微过时的数据
除了上述提及的Hbase调优之外,设置 phoenix.connection.consistency = timeline 在 hbase-site.xml 设置所有客户端的连接。