本文是英文貼的翻譯,能夠直接查看英文原文。
選擇正確的數據模型正是使用Cassandra最困難的一部分。若是諸位有相關開發經驗,就會發現CQL雖然看起來很熟悉,可是使用起來卻徹底不一樣。
本文將說明設計Cassandra schema的基本準則。在項目中遵照它們不只有立刻可見的益處,並且能夠保證在從此擴展節點時Cassandra的性能保持線性增加。node
有關係型數據庫經驗的開發者常常會將以前的經驗帶入Cassandra。爲了不在這些並不重要的事(對Cassandra而言)上耗費時間,先指出一些無效準則:sql
雖然在Cassandra中寫數據並不徹底免費,可是很是很是廉價。Cassandra天生就是爲了高速率的寫而優化設計的。在Cassandra中幾乎老是應該使用冗餘的寫來提升讀取效率。請記住,讀比寫昂貴不少而且更難以調優。數據庫
別懼怕,Cassandra所作的事情就是複製數據。磁盤空間一般是最廉價的資源(比起CPU、memory、磁盤IO、網絡等等),整個Cassandra正是架構在這個事實之上。你經常須要複製數據來提高讀的效率。
另外Cassandra沒有JOINs語句(固然,在分佈式設計中你不會真的想使用它!)網絡
你的數據模型應遵循以下兩個高層級目標:
1. 讓數據在集羣(cluster)中儘量的均勻分佈
2. 從儘量少的分區(partition)中查找數據
上面的兩條是最最重要的準則。你應該首選要學會在項目中如何作好這兩點。架構
通常來講,但願集羣中的各個節點數據量儘量相等。在Cassandra中,數據經過分區鍵(partition key)來分區存儲。分區鍵即主鍵(PRIMARY KEY)的第一個元素。
分區鍵決定了數據存放在哪一個node上。因此數據可以均勻分佈的關鍵就在於:選擇一個好的主鍵。分佈式
分區包含的是有着相同分區鍵的數據行。當你發起一個查詢時,請從儘量少的分區中讀取數據。
這條準則的重要性在於各個分區可能位於不一樣的節點。協調者(coordinator)將向各個節點發起不一樣命令。這將帶來額外的負荷與延遲。
即便所要查詢的分區都位於一個節點,跨分區的查詢也要昂貴許多。post
既然應該從儘量少的分區中查詢數據,那爲什麼不將全部數據都存到一個分區中?由於這違反了第一條準則。
能夠看到這兩條準則會相互矛盾,因此實際項目中經常須要權衡。性能
實現最小化分區查找的方法就是使數據模型徹底切合你的查詢。不要從關係、對象等等來倒推模型,從查詢入手!
第一步. 肯定真正須要支持什麼查詢
試着決定你真正須要支持的查詢。儘可能考慮那些甚至一開始沒想到的需求。例如:
* 按照屬性分組
* 按照屬性排序
* 根據條件過濾
* 在結果集中去重
爲了追求效率,查詢條件的變化常常致使數據模型的變化。
第二步. 儘量的讓你的查詢只讀取一個分區
實踐中這經常意味着你須要爲每個查詢單獨創建一張表。換種說法,每張表意味着爲你不一樣的查詢事先準備好了答案。若是須要不一樣的答案,那就創建不一樣的表。這就是對讀的優化。
永遠記住,數據複製是常規操做。你的許多表可能包含重複的數據。學習
用一些小例子來看看上面這些準則的最佳實踐。優化
最頂層的需求是「咱們有一些用戶,而且須要查詢他們」。設計步驟以下:
1. 細化查詢
好比須要按用戶名或者email地址查詢。每一種查詢方式都須要獲得用戶的全部詳細信息。
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 )
這種數據模型確實是均勻分佈的,可是它的缺點是須要查詢兩個分區。:(
如今來改變一下示例一中的頂層需求:用戶是按組進行劃分的,咱們須要按照組來查找用戶
1. 細化查詢條件
咱們須要查找特定組中的全部用戶信息,不關心排序。
2. 按每次只查一個分區的目標來建表
如何將一個組放到一個分區中 ? 這須要用到複合主鍵:
CREATE TABLE groups ( groupname text, username text, email text, age int, PRIMARY KEY (groupname, username) )
這裏的主鍵包含組名和用戶名:組名做爲分區鍵,用戶名做爲聚合鍵(clustering key)。這樣一個組就徹底屬於同一個分區。
在特定組中,數據以用戶名爲序。查詢語句很簡單:
SELECT * FROM groups WHERE groupname = ?
很容易看出這種設計知足第二條準則。可是並非那麼知足第一條。若是咱們有着大量的小組數量,每一個裏面有着少許百用戶,這時數據纔會均勻分佈。
可是若是僅僅有着一個組,裏面包含着大量用戶,那麼這個組所在的節點(或該節點的複製組)將會承擔全部負載。
若是須要將數據更加均勻的分佈,咱們須要使用一些策略。首先須要在主鍵中增長一列,使用聯合分區鍵:
CREATE TABLE groups ( groupname text, username text, email text, age int, hash_prefix int, PRIMARY KEY ((groupname, hash_prefix), username) )
新增長的hash_prefix是用戶名的hash的前綴。好比,能夠是hash對4取模的第一個字節。hash_prefix和groupname組成了聯合分區鍵。這致使了一個組的全部用戶會分佈在四個分區中。
咱們的數據變得更均勻了,可是如今一個查詢將要跨越四個分區。這就上文所提到矛盾的例子。你須要從你的具體使用環境中找到平衡。
若是查詢十分頻繁,並且你的每一個分組不是特別大,將對4取模改成對2取模多是一個好的選擇。
反過來講,若是查詢不頻繁,而且要查詢的分組會特別大,將4改成10將會更合適。
在這個例子中,咱們在每一個分區中複製了相同用戶的全部信息。你可能會嘗試着這樣作來減小數據複製:
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個分區!若是讀取頻繁,那麼這種作法是極端不可取的。
另外一方面,若是按組讀取的頻率極低,可是更新用戶信息(用戶名)特別頻繁,此時這種作法倒還有可取之處。
記住設計數據模型時將讀/寫頻率考慮進去!
如今在上個例子中加入入組時間,一次讀取x個新入組用戶。新表以下:
CREATE TABLE group_join_dates ( groupname text, joined timeuuid, username text, email text, age int, PRIMARY KEY (groupname, joined) )
這裏使用了timeuuid(相似於timestamp,可是兩條記錄不會相同)做爲聚合列。在一個組(分區)中,數據按照用戶入組時間排列。查詢語句以下:
SELECT * FROM group_join_dates WHERE groupname = ? ORDER BY joined DESC LIMIT ?
這樣作能夠有效的保證查詢效率。可是老是使用ORDER BY並非最高效的。更高效的作法表結構以下:
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 ?
前一個例子中,由於在組變得過大時數據將不會均勻分佈,因此咱們用某種隨機方式來進行分區(用戶名hash)。在這個例子中,咱們能夠利用時間段來進行分區。好比:
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個用戶。這樣在獲得最終結果前可能要查詢多個分區。
爲了最小化查詢分區數量,應該嘗試選擇一個合適的時間範圍,使得你的查詢只會查到一個或兩個分區。好比說業務上常常要查的是10個最新的用戶,同時每一個組天天會增長三我的左右。
合適的作法是將4天做爲一個時間段,而不是一天。能夠截取時間戳到合適的長度。如:
now = time() four_days = 4 * 24 * 60 * 60 shard_id = now - (now % four_days)
本文的基本原則適用於目前全部的Cassandra版本,極可能也一樣適用於將來的版本。其餘的一些數據模型面臨的問題(如tombstone),固然也須要考慮,但不一樣的是它們不太可能出如今Cassandra的將來版本中。
還有Cassandra的其餘一些特性(如 集合、用戶自定義類型、 靜態列)也有助於減小查詢分區數目。設計時請別忘了它們。
想更深刻的學習,請看這裏。祝各位好運!!