目前还是笔记,待整理为学习心得。

=======================================

什么是schema design?除了简单的表结构的create和update,更重要的是,如何适当的利用HBase提供的功能以适应复杂的业务需求,同时支持快速的写入/查询、使存储空间尽可能小。

更改表结构是否需要停服,何时生效?

由于table里唯一需要提前设定的就是column family,故这里表结构的更改也就是指column family及其相关属性。

online schema changes are supported in the 0.92.x codebase, but the 0.90.x codebase requires the table to be disabled.

When changes are made to either Tables or ColumnFamilies (e.g., region size, block size), these changes take effect the next time there is a major compaction and the StoreFiles get re-written.

column families的个数

  • HBase stores data in HDFS at $HBASE_HOME/TableName/regionId/ColumnFamily/HFiles.

如上所示,由于目前HBase的flush和compaction是基于region server的,即只要有一个column family需要flush,其相邻的cf也都会被flush,会造成不必要的IO负担。所以,建议一个table只建立一个column family,除非两组数据的读写真是完全的分离的(那为什么他们在一个table里?)

如果HBase后续改变了自己的flush、compaction机制,那么这条设计原则也需要随之改变了。毕竟一个cf,也会带来不少弊端,可能对于性能、压缩都略有影响,表结构的修改也会较为频繁。

当cf个数大于1时,还需要注意由于cf间数据稀疏程度不一,导致数据量相差较大时,由于region的划分是根据rowkey进行的,从而带来scan效率降低的问题。Where multiple ColumnFamilies exist in a single table, be aware of the cardinality (i.e., number of rows). If ColumnFamilyA has 1 million rows and ColumnFamilyB has 1 billion rows, ColumnFamilyA’s data will likely be spread across many, many regions (and RegionServers). This makes mass scans for ColumnFamilyA less efficient.

共用HBase集群的弊端之一

除了正常的资源竞争外,还可能由于其他使用方不恰当的rowkey设计导致的流量过载,使与其部署在同一region server的我们的regions不可用;反之亦然。

keyvalue存储数据结构

The KeyValue format inside a byte array is:

  • keylength
  • valuelength
  • key
  • value

 

The Key is further decomposed as:

  • rowlength
  • row (i.e., the rowkey)
  • columnfamilylength
  • columnfamily
  • columnqualifier
  • timestamp
  • keytype (e.g., Put, Delete, DeleteColumn, DeleteFamily)

 

KeyValue instances are not split across blocks. For example, if there is an 8 MB KeyValue, even if the block-size is 64kb this KeyValue will be read in as a coherent block. For more information, see the KeyValue source code.

 

即,“key”其实是由rowkey、columnfamily、column qualifier、timestamp等组成的!每一个column存储,都会附带这个庞大的“key”。该结构对rowkey设计的理解,至关重要!

rowkey设计原则

To prevent hotspotting on writes, design your row keys such that rows that truly do need to be in the same region are, but in the bigger picture, data is being written to multiple regions across the cluster, rather than one at a time.

全局是均匀分散的,而需要相邻的数据其rowkey是连续的。

过长的rowkey会带来巨大的内存浪费

要深入了解这一点,需要了解HFile和Region Server内存索引的数据结构,我目前还没有看完。但必须记住的是,rowkey尽可能短,与mysql设计不一样,这里不一定要明确表义,例如可以用u代替user。当然也可以适当提高block size,从而使冗余存储的内容和比例下降。

目前猜测,影响到Region server 内存的原因,是由于每个region server都需要知道所有region的分布情况,所以需要将所有HFile的index数据加载到内存里。同时,虽然values是按照column family分别存储的,但每个cell都会保存自己的rowkey等信息,即rowkey会被重复很多很多次。

Block size的配置

Over here we are not talking about hdfs block size but HFile block size which is by default set to 64kb. Each HFile has a byte offset marking its starting and ending position. Smaller the block size the better it is for random gets. Block size should be determine based on the use case, for example If the use case does random gets we should make the block size small but not too small so that one row spans over multiple blocks. Also there is an overhead involved in creating and maintain block indexes.  If the use case involves scans instead of gets then it is advisable to set it to zero such that we have as few as possible blocks.

基于最小化disk访问的原则,较小的block size,较利于random read;相对而言,较大的block size,较利于scan。但同时,由于block的管理也是有成本的,所以不可能把它设置的过小,默认64kb。

rowkey和column family的关系

关系型数据库里,很容易理解,一行里可以包含多列,所以可以认为自增id、列名都是在一个table的范围内的。

但HBase里,其说明是:

Rowkeys are scoped to ColumnFamilies. Thus, the same rowkey could exist in each ColumnFamily that exists in a table without collision.

Rowkeys的有效性是被局限在Columnfamilies里的,这是因为HBase是Column(Family) based存储的。即,同一个table不同CF可以有相同的rowkey,效果其实等同于一个rowkey对应的数据可以由多个CF组成的values组成。

rowkey取值空间与split的方式

Bytes.split是默认的split方法,它是按照ASCII码来分片的,即 ‘0’ is byte 48, and ‘f’ is byte 102。所以,如果rowkey的取值范围是000000 – ffffff之类的,就会导致分片的不均匀以及hotspot问题。因为按照ASCII码表,bytes 58 to 96在这个rowkey取值范围是不会有值的。

为了避免这些问题,在设计rowkey时,需要了解其取值范围和分布情况,并选择或自己开发合适的spilt method。

use timestamp as part of rowkey or version?

TODO

 opentsdb表结构解读

  1. 全站仅适用一个table,即tsdb
  2. 只有一个column family,即t
  3. Row Key – Row keys are byte arrays comprised of the metric UID, a base timestamp and the UID for tagk/v pairs: <metric_uid><timestamp><tagk1><tagv1>[...<tagkN><tagvN>]。采用自己设计的rowkey格式,包含了一些富信息。其中timestamp是采用秒精度计算,但仅映射到hour粒度,从而使相邻时间的数据尽可能相邻存储,从而提高查询效率(猜测其业务场景比较多顺序查找或scan)。
  4. column qualifier:也采用了自己设计的数据结构,包含了一些富信息,分为2bytes和4bytes两种。
  5. column values:按照qualifier format flags指示的,存储1-8bytes。

 

参考文档

http://hbase.apache.org/book/rowkey.design.html

https://communities.intel.com/community/itpeernetwork/datastack/blog/2013/11/10/discussion-on-designing-hbase-tables

http://phoenix.apache.org/salted.html

https://issues.apache.org/jira/browse/HBASE-11682

http://blog.sematext.com/2012/04/09/hbasewd-avoid-regionserver-hotspotting-despite-writing-records-with-sequential-keys/

http://opentsdb.net/docs/build/html/user_guide/backends/hbase.html

https://issues.apache.org/jira/browse/HBASE-3551

http://hbase.apache.org/book/regions.arch.html#keyvalue

Leave a Reply