HBase模式案例研究:列表数据
HBase模式案例:列表数据
以下是用户 dist-list 中关于一个相当常见问题的的交流:如何处理 Apache HBase 中的每个用户列表数据。
问题:
我们正在研究如何在 HBase 中存储大量(每用户)列表数据,并且我们试图弄清楚哪种访问模式最有意义。一种选择是将大部分数据存储在一个密钥中,所以我们可以有如下的内容:
<FixedWidthUserName><FixedWidthValueId1>:"" (no value)
<FixedWidthUserName><FixedWidthValueId2>:"" (no value)
<FixedWidthUserName><FixedWidthValueId3>:"" (no value)
我们的另一个选择是完全使用如下内容:
<FixedWidthUserName><FixedWidthPageNum0>:<FixedWidthLength><FixedIdNextPageNum><ValueId1><ValueId2><ValueId3>...
<FixedWidthUserName><FixedWidthPageNum1>:<FixedWidthLength><FixedIdNextPageNum><ValueId1><ValueId2><ValueId3>...
每行将包含多个值。所以在一种情况下,读取前三十个值将是:
scan { STARTROW => 'FixedWidthUsername' LIMIT => 30}
而在第二种情况下会是这样:
get 'FixedWidthUserName\x00\x00\x00\x00'
一般的使用模式是只读取这些列表的前30个值,并且很少的访问会深入到列表中。一些用户在这些列表中总共有30个值,并且一些用户将拥有数百万(即幂律分布)。
单值格式似乎会占用 HBase 上的更多空间,但会提供一些改进的检索/分页灵活性。是否有任何显着的性能优势能够通过获取和扫描的页面进行分页?
我最初的理解是,如果我们的分页大小未知(并且缓存设置恰当),那么执行扫描应该会更快,但如果我们始终需要相同的页面大小,则扫描速度应该更快。我听到不同的人告诉了我关于表现的相反事情。我假设页面大小会相对一致,所以对于大多数使用情况,我们可以保证我们只需要固定页面长度的情况下的一页数据。我还会假设我们将不经常更新,但可能会插入这些列表的中间(这意味着我们需要更新所有后续行)。
答案:
如果我理解正确,你最终试图以“user,valueid,value”的形式存储三元组,对吗?例如:
"user123, firstname, Paul",
"user234, lastname, Smith"
(但用户名是固定宽度,而 valueids 是固定宽度)。
而且,您的访问模式符合以下要求:“对于用户 X,列出接下来的30个值,以valueid Y开头”。是对的吗?这些值应该按 valueid 排序返回?
tl、dr 版本是,你可能应该为每个用户+值添加一行,除非你确定需要,否则不要自行构建复杂的行内分页方案。
您的两个选项反映了人们在设计 HBase 模式时常见的问题:我应该选择“高”还是“宽”?您的第一个模式是“高(tall)”:每行代表一个用户的一个值,因此每个用户的表中有很多行;行键是 user + valueid,并且会有(可能)单个列限定符,表示“值(value)”。如果您希望按行键来扫描排序顺序中的行, 这是很好的。你可以在任何用户 + valueid 开始扫描,阅读下一个30,并完成。你放弃的是能够在一个用户的所有行周围提供事务保证,但它听起来并不像你需要的那样。
第二个选项是“宽”:使用不同的限定符(其中限定符是valueid)将一堆值存储在一行中。简单的做法是将一个用户的所有值存储在一行中。我猜你跳到了“分页”版本,因为你认为在单行中存储数百万列会对性能造成影响,这可能是也可能不是真的; 只要你不想在单个请求中做太多事情,或者做一些事情,比如扫描并返回行中的所有单元格,它不应该从根本上变坏。客户端具有允许您获取特定的列的片段的方法。
请注意,这两种情况都不会从根本上占用更多的磁盘空间; 您只是将部分识别信息“移动”到左侧(在行键中,在选项一中)或向右(在选项2中的列限定符中)。在封面下,每个键/值仍然存储整个行键和列名称。
正如你注意到的那样,手动分页版本有很多复杂性,例如必须跟踪每个页面中有多少内容,如果插入新值,则重新洗牌等。这看起来要复杂得多。在极高的吞吐量下它可能有一些轻微的速度优势(或缺点!),而要真正知道这一点的唯一方法就是试用它。如果您没有时间来构建它并进行比较,我的建议是从最简单的选项开始(每个用户 + valueid)。开始简单并重复!
更多建议: