http://www.datastax.com/dev/blog/basic-rules-of-cassandra-data-modeling
Basic Rules of Cassandra Data Modeling
正确掌握cassandra的数据模型不是一件容易的事情,如果你有关系型数据库的背景,col会看上去熟悉一点,但是使用它的方式会很不同。这篇文章的主要目标是使设计cassandra的schema原则牢记于心。如果你遵从这些原则那么你将得到非常好的性能。更好的是,如果你在集群中增加节点,你将得到性能的线性增长。
不需要的目标
来自关系型数据库的开发者往往会把关系型数据模型的一些规则带到cassandra中。但是这些在cassandra中往往不适用。
最小化写原则
写在Casanova当中并不是免费的,但是它非常的廉价。Casanova在高吞吐量写上面有很好的性能,如果你更倾向于用额外的写来获得更好的读性能,那么这会是一个好的方式。因为读往往比写开销更大。
最小化的数据冗余规则
在cassandra中不要害怕数据冗余,硬盘资源往往少最便宜大,并且cassandra的架构正是围绕着这一个事实。为了得到更好的读效率,你往往需要数据冗余。
除此之外,cassandra不支持joins操作。
基本但目标
1,把数据均匀分布在集群当中
2,减少分区读取的个数
上面是两个最基本也是最重要的规则需要铭记在心。除此在外还有其他的一些技巧,但是这些技巧使用的时候你需要好好评估一下。
1,把数据均匀的分布在集群中
你希望在集群中每个节点包含大致相同的数据量,cassandra达成这一目标很简单。rows根据partition key(也就是表中的primary key的第一个字段)的hash来分布在集群中,因此首先要考虑的原则就是primary key。因此要想使数据均匀的分布在集群中,首先是要挑选一个合适的primary key。
2,减少分区读取的个数
分区是一些具有相同partition key的rows的集合。当你要执行一个查询操作,应该尽量少多读区分区的个数。每个分区可以驻留在不同到节点上。协调器通常需要把命令发送到不同到节点上的多个分区。这增大了负载和延迟。不仅如此,即使只在一个节点上,从多个分区读也比从一个分区读更加的开销大。
上面两个原则是有些冲突的,因此需要平衡。
数据模型的设计依赖于你的查询。
减少读取分区个数的查询操作的原则是使你的数据模型更加适合于你的查询。而不是按照关系型建模,不是按照对象的原则建模。
步骤一,决定你将要支持何种查询
比如说,
grouping by an attribute
ordering by an attribute
根据某些条件进行过滤
强制返回唯一的结构
步骤2,创建一个可以使你的查询只读取一个分区的表
在实际中,这通常意味着你的一个表只能去匹配一种查询模型,如果你有多种查询模型,那么你需要多个表
例子1,用户查询
步骤1,决定你需要支持哪种类型的查询
需要添加username或者是邮箱来查询user的详情
步骤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 User groups
我们需要查询一个组内的所有用户
步骤1,
我们需要查询一个组内的所有用户,用户但顺序并不关心。
步骤2,
可以使用一种组合的primary key(其中group name味partitioning key ,username为clustering key)
create table groups (
group name text,
username text,
email text,
age int,
PRIMARY KEY (groupname ,username)
)
上面的设计可以查询只读取一个分区,但是它并不能很好的使数据均匀的分布在集群中,如果你有几百万个小的group(每个组内有几百个user)它将会工作的很好(分布的很好),但是如果你的一个组内有几百万个user那么整个重担将由一个节点上(或一套副本)。
如果你想使负载分布的更加均匀一些,那么一个基本的技术是在primary key 增加一个列,组成合成的key,如:
create table groups (
gropuname text,
username text,
email text,
age int,
hash_prefix int,
PRIMARY KEY ((group name ,hash_prefix),username)
)
hash_prefix的值为usrname取hash的一个前缀。比如,它可以是usrname按4取模的第一个字节,这会把一个分区分为4个分区(因为余数可能说0,1,2,3嘛),那么上述多两个规则也就发生来冲突,因此如果你的读非常多,并且group不是那么大,可以把模由4变为2。另一方面,如果你的读很多并且组很大,那么可以把模由4变为10.
还有一种方法,可以分割partition。思考上面的例子我们冗余了user好多次(每一个组内一次),因此你可能会想减少这种冗余:
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次分区才能把分区内的数据全部拿出。当然查询特别不频繁的情景下可能就会不同。
Example 3: User Groups by Join Date
得到一个组内的最新说n个user
我们可以创建下面的表
CREATE TABLE group_join_dates (
groupname text,
joined timeuuid,
username text,
email text,
age int,
PRIMARY KEY (groupname, joined)
)
增加了一个timeuuid类型的字段,这样我们就可以根据用户加入组的时间进行排序了,查询sql如下:
SELECT * FROM group_join_dates
WHERE groupname = ?
ORDER BY joined DESC
LIMIT ?
除此之外,还可以在建表的时候采用更加高效的方式来查询,如下:
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 ?
当然,为什么防止一个组内的数据特别的多,还可以按时间分区如下(每天产生一个新分区)
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)
总结
除了上面说的这些原则,当然还有一些其他的问题,比如说 dealing with tombstones 但是这些问题可能会在cassandra将来的版本当中得到解决。
还有其他的一些特性,如collections user-defined types 和static columns都可以减少查询patitions的个数。