Basic Rules of Cassandra Data Modeling

原文地址:http://www.datastax.com/dev/blog/basic-rules-of-cassandra-data-modeling

选择一个正确的数据模型是Cassandra使用中最难的部分(译者也这么认为)。如果你有关系型数据库开发经验,你会觉得CQL看起来都很相似(和MySQL等),但是你使用它的方式会非常的不同。这篇文章的目的就是解释当你在设计一个Cassandra数据库的时候需要牢记在心里的一些基本规则。如果你遵守这些规则,你会得到很好的拿来就能用的性能提升。更好的是,你的性能将会随着你的集群的节点增加而线性增长。

非 - 目标

来自有关系型数据库背景的开发经常会尝试把他们在关系型数据库的设计规则经验放到Cassandra来用。为了避免浪费时间在那些不适用Cassandra的规则上,我需要指出一下非目标:

尽量减少写入次数

在Cassandra中写入不是免费的,但是非常廉价。Cassandra在写入吞吐量方面做了高度的优化,几乎所有的写入性能都是平等的(计数器、轻量事务和在list中插入数据除外)。如果你额外的写入可以让你改进读的性能,这一般是一种好的设计。读取是一种更昂贵并且更困难的东西。

最小化数据重复(译注:范式设计)

反范式设计和冗余数据是Cassandra的设计核心。不要担心这个问题。相比CPU,内存,磁盘IO,网络IO,磁盘的储存空间是非常廉价的,而Cassandra正是遵循这个思路来设计的。为了有更好的读取性能,你往往需要重复数据。

另外Cassandra不提供JOIN ,你不会想在分布式系统中用这些特性。

真 - 目标

你的数据模型中有2个非常高优先级的目标

  1. 让数据均匀分布在整个集群的每个节点中
  2. 最小化每次读取使用的分区数量

还有一些小规则你需要记住,但是这两个最重要。所以多数情况下,我会把这2个规则作为关注重点。当然你还有一些花哨的技巧可以使用,但是你最好知道如何评测它的效果。

规则1:让数据分布在整个集群的每个节点中

你希望集群中的所有的节点都有大致差不多数量的数据。Cassandra做这个很容易,但不是全自动的。行基于分区主键被分布到集群的各个位置。所以你需要选择一个好的主键,我将简单解释一下。

规则2:尽量减少分区读取

分区是一组数据共享一个分区key。当你执行一个读取的查询,你希望读取尽量少的分区来获取你需要的数据。

为何如此重要?因为每个分区可能在不同的节点上。代理将会构建许多命令去不同的节点去执行以满足你的请求。这增加了大量的前置操作并且导致各种延迟的情况。而且,即使在单个节点的情况下,由于数据的存储方式导致读取多个分区的数据也比单个分区要慢的多。

这是两个冲突的规则吗?

你需要尽量减少读取的时候需要的分区数量,为什么不把数据放在一个大分区?你需要干掉规则1,它说要让数据分布在整个集群。

事实是,这两个规则确实经常冲突,所以你需要平衡他们。

设计你的查询语句

最小话查询分区的办法就是让你的数据结构适应你的查询语句。不要依据关系来设计(译注:没错,你的数据会更加抽象化,且不好理解)。基于查询设计。下面是如何做:

第一步:决定要执行哪些查询语句

尽量尝试去确定你有哪些语句需要执行。这里可能包含大量的注意事项你可能一开始没有考虑到,你需要考虑:

  • 使用什么字段做分组
  • 使用什么字段来排序
  • 需要哪些过滤条件
  • 结果的唯一性
  • ...

任何一个查询请求的改变都会影响到最优数据模型的设计。

第二步:尝试创建一个表让你可以大致的从一个分区里读数据

事实上这意味着你可能需要为每个查询建立一张表(译注:也就是说你一开始会有很多表,他们数据会很重复)。如果你需要应付多个查询,那么你需要更多的表。

换句话说,每个表需要高度匹配查询语句需要的答案。如果你需要不同的答案,你需要不同的表。这是你如何优化读操作。

记住,重复数据没有问题。你的许多表可能重复了很多数据。

添加规则:例子

为了展示如何进行一个好的设计,我将带你通过设计解决一个简单的问题。

例1:用户查询

核心需求,我们有许多用户,我想找到他们。

步骤1:

决定要执行哪些查询。我们希望可以通过用户名,或者用户的email来查询他们。任何一个查询,我们都需要得到他们的所有信息。

步骤2:

尝试创建一个表来满足查询需求,并且只需要用到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倍。

例2: 用户分组

核心需求:用户被分到不同的组中,我们希望读取分组的所有用户。

步骤1:

决定要执行哪些查询。我们希望获取确切分组的用户的所有信息,不关心排序。

步骤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

一路顺风!

译 - Cassandra 数据建模的基本规则的更多相关文章

  1. Cassandra数据建模中最重要的事情:主键

    Cassandra数据建模中要了解的最重要的事情:主键 使用关系数据建模,您可以从主键开始,但是RDBMS中的有效数据模型更多地是关于表之间的外键关系和关系约束.由于Cassandra无法使用JOIN ...

  2. Cassandra数据建模

    1.  概述 Apache Cassandra将数据存储在表中,每个表都由行和列组成.CQL(Cassandra查询语言)用于查询存储在表中的数据.Apache Cassandra数据模型基于查询并针 ...

  3. NoSQL 数据建模技术(转)

    本文转载自:http://coolshell.cn/articles/7270.html ================================================ 全文译自墙外 ...

  4. NoSQL数据建模技术

    原文来自“NoSQL Data Modeling Techniques”,由酷壳网陈皓编译<NoSQL数据建模技术>.这篇文章看完之后,你可能会对NoSQL的数据结构会有些感觉.我的感觉是 ...

  5. 【翻译】ScyllaDB数据建模的最佳实践

    文章翻译自Scylla官方文档:https://www.scylladb.com/2019/08/20/best-practices-for-data-modeling/ 转载请注明出处:https: ...

  6. 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型

    第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...

  7. 如何在spark中读写cassandra数据 ---- 分布式计算框架spark学习之六

    由于预处理的数据都存储在cassandra里面,所以想要用spark进行数据分析的话,需要读取cassandra数据,并把分析结果也一并存回到cassandra:因此需要研究一下spark如何读写ca ...

  8. 《驾驭Core Data》 第三章 数据建模

    本文由海水的味道编译整理,请勿转载,请勿用于商业用途.    当前版本号:0.1.2 第三章数据建模 Core Data栈配置好之后,接下来的工作就是设计对象图,在Core Data框架中,对象图被表 ...

  9. 【mysql的设计与优化专题(1)】ER图,数据建模与数据字典

    需求分析是做项目中的极为重要的一环,而作为整个项目中的'血液'--数据,更是重中之重.viso,workbench,phpmyadmin等软件可以帮我们更好的处理数据分析问题. ER图 E-R方法是& ...

随机推荐

  1. C#中如果用await关键字来await一个为null的Task对象会抛出异常

    await & async模式是C#中一个很重要的特性,可以用来提高异步程序(多线程程序)的执行效率.但是如果尝试用await关键字来await一个为null的Task对象,会导致程序抛出Nu ...

  2. MySQL-5.5.32 配置文件优化详解

    目录 MySQL-5.5.32 配置文件优化详解 一.配置文件说明 2.my-medium.cnf 3.my-large.cnf 4.my-huge.cnf 5.my-innodb-heavy-4G. ...

  3. 基于jQuery的轮播焦点图图

    轮播焦点图 ——仿淘宝首页jquery轮播焦点图,我特意去taobao首页看了下它的轮播,好像有点相似,我不保证是我写的这样. 本例来源:站长之家http://sc.chinaz.com/jiaobe ...

  4. 开发Web版一对一远程直播教室只需30分钟 - 使用face2face网络教室

    转载自:https://blog.csdn.net/wo_shi_ma_nong/article/details/88110111 在“为网站开发远程直播教室的折腾过程及最终实现”中,介绍了如何使用f ...

  5. PHP单例模式理解

    话不多说,先上代码 <?php class User{ /* * 1.创建一个存放对象的私有化静态变量 * 2.私有化克隆方法 * 3.私有化构造方法 * 4.创建实例化对象的唯一入口 * * ...

  6. Leecode刷题之旅-C语言/python-389 找不同

    /* * @lc app=leetcode.cn id=389 lang=c * * [389] 找不同 * * https://leetcode-cn.com/problems/find-the-d ...

  7. vue项目持久化存储数据的实现代码

    方式一.使用localStorage在数据存储 1.要在浏览器刷新的时候重新存储起来 if (window.localStorage.getItem(authToken)) { store.commi ...

  8. (转载)Javascript异步编程的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排 ...

  9. ubuntu 设置环境变量

    两种方式设置环境变量: 1,添加到环境变量路径$PATH中 # 加到PATH末尾 export PATH=$PATH:/path/to/your/dir # 加到PATH开头 export PATH= ...

  10. (数据科学学习手札48)Scala中的函数式编程

    一.简介 Scala作为一门函数式编程与面向对象完美结合的语言,函数式编程部分也有其独到之处,本文就将针对Scala中关于函数式编程的一些常用基本内容进行介绍: 二.在Scala中定义函数 2.1 定 ...