原文地址:http://www.datastax.com/dev/blog/basic-rules-of-cassandra-data-modelingsql
选择一个正确的数据模型是Cassandra使用中最难的部分(译者也这么认为)。若是你有关系型数据库开发经验,你会以为CQL看起来都很类似(和MySQL等),可是你使用它的方式会很是的不一样。这篇文章的目的就是解释当你在设计一个Cassandra数据库的时候须要牢记在内心的一些基本规则。若是你遵照这些规则,你会获得很好的拿来就能用的性能提高。更好的是,你的性能将会随着你的集群的节点增长而线性增加。数据库
来自有关系型数据库背景的开发常常会尝试把他们在关系型数据库的设计规则经验放到Cassandra来用。为了不浪费时间在那些不适用Cassandra的规则上,我须要指出一下非目标:网络
在Cassandra中写入不是免费的,可是很是廉价。Cassandra在写入吞吐量方面作了高度的优化,几乎全部的写入性能都是平等的(计数器、轻量事务和在list中插入数据除外)。若是你额外的写入可让你改进读的性能,这通常是一种好的设计。读取是一种更昂贵而且更困难的东西。数据结构
反范式设计和冗余数据是Cassandra的设计核心。不要担忧这个问题。相比CPU,内存,磁盘IO,网络IO,磁盘的储存空间是很是廉价的,而Cassandra正是遵循这个思路来设计的。为了有更好的读取性能,你每每须要重复数据。负载均衡
另外Cassandra不提供JOIN ,你不会想在分布式系统中用这些特性。数据库设计
你的数据模型中有2个很是高优先级的目标分布式
还有一些小规则你须要记住,可是这两个最重要。因此多数状况下,我会把这2个规则做为关注重点。固然你还有一些花哨的技巧可使用,可是你最好知道如何评测它的效果。工具
你但愿集群中的全部的节点都有大体差很少数量的数据。Cassandra作这个很容易,但不是全自动的。行基于分区主键被分布到集群的各个位置。因此你须要选择一个好的主键,我将简单解释一下。post
分区是一组数据共享一个分区key。当你执行一个读取的查询,你但愿读取尽可能少的分区来获取你须要的数据。性能
为什么如此重要?由于每一个分区可能在不一样的节点上。代理将会构建许多命令去不一样的节点去执行以知足你的请求。这增长了大量的前置操做而且致使各类延迟的状况。并且,即便在单个节点的状况下,因为数据的存储方式致使读取多个分区的数据也比单个分区要慢的多。
你须要尽可能减小读取的时候须要的分区数量,为何不把数据放在一个大分区?你须要干掉规则1,它说要让数据分布在整个集群。
事实是,这两个规则确实常常冲突,因此你须要平衡他们。
最小话查询分区的办法就是让你的数据结构适应你的查询语句。不要依据关系来设计(译注:没错,你的数据会更加抽象化,且很差理解)。基于查询设计。下面是如何作:
尽可能尝试去肯定你有哪些语句须要执行。这里可能包含大量的注意事项你可能一开始没有考虑到,你须要考虑:
任何一个查询请求的改变都会影响到最优数据模型的设计。
事实上这意味着你可能须要为每一个查询创建一张表(译注:也就是说你一开始会有不少表,他们数据会很重复)。若是你须要应付多个查询,那么你须要更多的表。
换句话说,每一个表须要高度匹配查询语句须要的答案。若是你须要不一样的答案,你须要不一样的表。这是你如何优化读操做。
记住,重复数据没有问题。你的许多表可能重复了不少数据。
为了展现如何进行一个好的设计,我将带你经过设计解决一个简单的问题。
核心需求,咱们有许多用户,我想找到他们。
决定要执行哪些查询。咱们但愿能够经过用户名,或者用户的email来查询他们。任何一个查询,咱们都须要获得他们的全部信息。
尝试建立一个表来知足查询需求,而且只须要用到1个分区。由于咱们须要获得用户的全部信息,这须要2张表。
CREATE TABLE users_by_username ( username text PRIMARY KEY, email text, age int ) CREATE TABLE users_by_email ( email text PRIMARY KEY, username text, age int )
如今咱们来确认一下是否符合规则:
数据分布均匀?每一个用户使用它们本身的分区,因此是的。
最小化分区读取?每一个用户咱们只须要读取一个分区,因此是的。
如今咱们来尝试对“非 - 目标”进行优化,而后又有了下面的设计方式
CREATE TABLE users ( id uuid PRIMARY KEY, username text, email text, age int ) CREATE TABLE users_by_username ( username text PRIMARY KEY, id uuid ) CREATE TABLE users_by_email ( email text PRIMARY KEY, id uuid )
这个数据模型一样将数据分布到全部的节点,可是有个问题,咱们须要读取2个分区。一个是users_by_username/users_by_email 而后是 users表。因此读取的成本大致上是以前的2倍。
核心需求:用户被分到不一样的组中,咱们但愿读取分组的全部用户。
决定要执行哪些查询。咱们但愿获取确切分组的用户的全部信息,不关心排序。
尝试建立一个表来知足查询需求,而且只须要用到1个分区。咱们如何让分组分不到不一样的分区中?咱们能够设计这样的分区主键:
CREATE TABLE groups ( groupname text, username text, email text, age int, PRIMARY KEY (groupname, username) )
注意到主键中有2个部分,groupname,这个是分区主键,username,被称做clustering key (集群主键)。这会使得每一个groupname在一个分区中。在一个特定的groupname中,行是按照username排序的。读取分组数据很是简单:
SELECT * FROM groups WHERE groupname = ?
这知足了最小化查询分区的要求,由于咱们只须要读取1个分区。可是它并无在数据均匀分布上作的很好。若是咱们考虑有成千上万的小分组,每一个分组有几百人,咱们将获得一个至关于均匀分布的模型。可是若是有一个分组有1百万用户,全部的负担都会被一个节点承担。
若是咱们但愿负载均衡,有一些策略咱们能够采用。最基本的是添加另外一个字段到主键中作成一个复合分区主键,下面是例子:
CREATE TABLE groups ( groupname text, username text, email text, age int, hash_prefix int, PRIMARY KEY ((groupname, hash_prefix), username) )
新的HASH字段hash_prefix,保存了用户名的hash的前缀。好比第一个字节对4取模。和groupname一块儿,这两个字段组合成了一个复合分区主键。和单个分区不一样,如今它分布到4个分区中了。咱们的数据更加均匀,可是咱们须要读取4次的分区。这是一个规则冲突的例子。你须要在它们间找到一个合适的平衡。
若是你有不少的读操做,并且分组不会太大,那么将取模的值从4改成2是不错的选择。若是你有较少的读取,可是单个分组会增加到很大,将4改成10会更好。
还有一些其余方法能够分割分区,我将会在下面的例子中介绍。
在我继续以前,让我总结一下这个数据模型:咱们屡次重复了用户信息,每一个group一次,你可能但愿像这样重建模型来减小重复的数量:
CREATE TABLE users ( id uuid PRIMARY KEY, username text, email text, age int ) CREATE TABLE groups ( groupname text, user_id uuid, PRIMARY KEY (groupname, user_id) )
显然,这最小化了重复。可是咱们须要读取多少分区呢?若是分组是1000个用户,咱们须要读取1001个分区。这和从一个分区读取数据相比是100倍的差距。若是要求读优先,那这正不是一个好的设计。另外一方面,若是读不频繁,可是修改(update)是频繁的,这个模型仍是有道理的。当你在设计数据库的时候,必定要肯定你的读/写频率。
例3:用户加入分组的时间
假设咱们继续以前分组的例子,可是新增一个需求,读取分组中新增的前X个用户。
咱们能够用和以前有点类似的表:
CREATE TABLE group_join_dates ( groupname text, joined timeuuid, username text, email text, age int, PRIMARY KEY (groupname, joined) )
这里咱们使用timeuuid(和时间戳很像,可是不会冲突)做为clustering column 。在一个分组中,行将按照用户加入的时间顺序排序。这容许咱们获取最新的用户信息,像下面这样。
SELECT * FROM group_join_dates WHERE groupname = ? ORDER BY joined DESC LIMIT ?
这是很是高效的,咱们在同一个分区中顺序查询数据。为了不老是要用到order by joined desc,这会让查询效率下降,咱们能够修改clustering order:
CREATE TABLE group_join_dates ( groupname text, joined timeuuid, username text, email text, age int, PRIMARY KEY (groupname, joined) ) WITH CLUSTERING ORDER BY (joined DESC)
如今咱们能够执行更高效的查询了
SELECT * FROM group_join_dates WHERE groupname = ? LIMIT ?
咱们以前的例子中,咱们有个问题就是若是有一个分组太大,如何把数据均匀的分配到全部节点中。在那个例子里咱们随机的分割了数据到同的分区(取模)。可是这个例子不一样,咱们能够利用咱们对查询模板的认识来分区:用时间分区。
好比咱们用date来分区(date应该相似于:2016-05-19,因此天天有个分区)
CREATE TABLE group_join_dates ( groupname text, joined timeuuid, join_date text, username text, email text, age int, PRIMARY KEY ((groupname, join_date), joined) ) WITH CLUSTERING ORDER BY (joined DESC)
咱们再次使用了复合分区主键,可是此次咱们用了加入时间。天天都有一个新的分区。当查询最近的X个用户时,会先找今天的分区,而后昨天,前天,直到咱们有X个用户。咱们可能会在获得limit个用户以前,查找多个分区。
为了减小分区的查询,咱们须要为分区指定一个时间范围,这样你就只须要查询1-2个分区。好比咱们平均天天有新3个用户大体上,而后咱们按4天一分区,这样,你就能够在1-2个分区查完10个最新用户。
这里提到的最基本的数据模型规则涵盖了现有的全部版本的Cassandra,而且在未来的版本中应该也是同样的。一些其余的小的数据模型问题,好比如何处理墓碑(删除的数据),同样是须要考虑的,可是这些可能在未来的Cassandra版本中可能会改变。
除了这里提到的基本策略,一些Cassandra华丽的功能,像集合,用户自定义数据结构,静态字段,同样能够在读的时候减小分区的使用。在设计的时候不要忘了考虑这些选择。
但愿我在大家处理不一样的数据库设计的时候已经给予了一些有用的工具。若是你想了解更多,我建议阅读:Datastax’s free, self-paced online data modeling course (DS220) (译者:这是一个英文视频教程)
https://academy.datastax.com/courses/ds220-data-modeling?dxt=blogposting 一帆风顺!