本文為 Design Data Intensive Applications 的書摘 + 個人心得。
資料模型 (Data Model) 是所有軟體開發中最重要的環節,每個資料表示層級要如何向更低層級表達資料項目?
看起來有點饒舌,資料表示層級舉例來看:
- 應用程式端 (Application) 諸如金流、物流、訂單、貨物資料等等,這些項目都是表達了現實世界中你的應用程式會使用到的資料項目。
- 資料模型端 (Data Model) 當你決定了資料項目後,在來就要想用什麼 data model 來表達,例如 JSON、XML document、relation database 或者 graph model。
- 儲存與檢索端 (Storage and Retrieval) 該用哪個 database 儲存,每種 database 都有多種查詢、操作資料的方法,如 Hash Index、B-Tress 等等,這部份在 Day 6 會講到。
- 硬體端 (Hardware) 硬體工程師的世界,通常我們只決定要用多少系統資源執行。
每一層都是在向下一層隱藏該層的複雜性,提供給他們乾淨的 Data Model,今天的主題就是講 Data Model 中的 Relation Model 跟 Document Model,就是常聽到的 SQL 跟 NoSQL。
SQL – Rleational Model
關聯式資料模型 (Relational Model) 加上 SQL 跟 RDBMS 的推出,這主宰了軟體開發領域長達 25-30 年之久,
Relational model 最早 Edgar Codd 在 1970 年提出的學術文章:資料是組織在關係裡面的 (可視為 table in SQL),每一個關係是未經排序的 tuple 集合 (可視為 row in SQL)。
然後在 1980 年中,relational database management systems (RDBMS) 和 SQL 成為一套標準化的工具,讓人們能用特定結構性的語法去查詢資料。
NoSQL – Document model
NoSQL 一詞不代表特定的任一技術,它代表了分散式、非關聯式資料,開始有革命感覺是來自 2009 年在亞特蘭大舉行的會議 ,NoSQL 不是反對 SQL 之意,比較精準的詞應是 Not Only SQL。
NoSQL 有下述幾項特點
- 一個需要比 Relational Database 有更棒擴充能力且易於執行。
- 有特定的查詢語言且不太支援 Realtional Model。
- 對 Realtional Database 的 schema 限制感到失望,渴望能更動態調整 Data Model 。
Relational Model 和 Document Model
上圖是 Bill Gates 的 LinkedIn Profile 用 reational schema 的方式來表達,這張圖的重點是我們用 Relational Model 來表達一個人可能會有多個
工作經歷、教育背景以及聯絡方式,就是 position、education、contact_info 這 3 個 table,這 3 個 table 都存了 user_id 這個 foreign key 這個參照來找到 user 是誰。
像 Profile 這種資料也常用 Document Model 的方式來表達,以 JSON 來呈現的話長這樣:
{
"user_id": 251,
"first_name": "Bill",
"last_name": "Gates",
"summary": "Co-chair of the Bill & Melinda Gates... Active blogger.",
"region_id": "us:91",
"industry_id": 131,
"photo_url": "/p/7/000/253/05b/308dd6e.jpg",
"positions": [
{
"job_title": "Co-chair",
"organization": "Bill & Melinda Gates Foundation"
},
{
"job_title": "Co-founder, Chairman",
"organization": "Microsoft"
}
],
"education": [
{
"school_name": "Harvard University",
"start": 1973,
"end": 1975
},
{
"school_name": "Lakeside School, Seattle",
"start": null,
"end": null
}
],
"contact_info": {
"blog": "http://thegatesnotes.com",
"twitter": "http://twitter.com/BillGates"
}
}
最大的差別就是 Self-Contained
,工作經歷、教育、聯絡方式都包含在整個 LinkedIn Profile 之中,用 XML 呈現也是一樣意思,但 JSON 比 XML 簡單和好理解太多了XD
Relations (資料關係)
one to many
一對多的闗係就是上面提到的,一個人可能會有多個
工作經歷、教育背景以及聯絡方式,把這關係轉成 Tree 結構的話就更好理解了。
many to one
多對一的關係,繼續用 Bill Gates 的 LinkedIn Profile 來看,我們的居住地區跟職務都是用 ID 來表達,而不是直接寫 “Greater Seattle Area” 和 “Philanthropy”,為什麼呢?
"region_id": "us:91",
"industry_id": 131,
最主要的原因就是為了 標準化 (Normalization),維護一個表的話代表居住地或職務一樣的人都會有相同的資料,修改方便也能統一格式,找資料也更快,雖然 ID 只對資料庫有意義就是了,但 去重複 就是 Relational Database 中標準化的關鍵想法。
標準化 (normalization) 的反面就是 去標準化 (Denormalization),也就是在 Document Database 裡常用到的,不會用額外的 table 儲存資料,每個 LinkedIn Profile 的居住地區跟職務都會存成:
{
"region_id": {
"conutry": "US",
"area": "Greater Seattle Area"
},
"industry_id": "Philanthropy"
}
many to many
依舊繼續用 Bill Gates 的 LinkedIn Profile 來看,若我們想讓 Profile 加上 2 個新功能:
- 顯示 Microsoft 公司和學校的基本資料,如下圖:
- user 可以為另一個 user 寫推薦,推薦裡可以看到推薦者 user 的 Profile,若推薦者的 Profile 有資料變更,被推薦者看到的 推薦者 Profile 內容也需要更新,所以被推薦者的 Profile 應該要存推薦者的 user 參照。
下圖是加上 2 個新功能後所需要的多對多關係示意圖:
虛線可被存成 Document Model,在這種情況下,公司、學校、推薦者都需要儲存參照,然後做多次查詢是變的必要了。
Relational vs. Document database
這裡有許多可以比較的點,像容錯能力和如何處理並行執行緒,但最主要的差異點就是 document model 的 schema 彈性較高,且 local 環境下執行效率也比較好,relational morel 則是比較支援 JOIN 及多對一、多對多關係。
誰讓程式變的比較簡單?
如果你的軟體用的資料項目是符合 Tree 那樣的一對多關係,Document Model 會比較簡單,最大的差異點就是不用像 relational model 那樣分成多個 table,也代表不用理會每個 table 各別的 schema 是如何,也降低應用端的程式複雜度。
但如果你的軟體用資料項目是傾向多對多關係,Document Model 可能就不是首選了,有鑑於 document model 對 join 的支援很貧乏,減少 JOIN 的可能方法之一就是做 denormalization (去標準化),或者查詢多次,但如此就會提高應用端的程式複雜度,JOIN 在這就是比較好的選擇了,把多次查詢合成一次,這就是把複雜度移往 database 端的例子。
很難說誰讓程式變的簡單了,要基於應用端資料項目的關係為何,有時使用 graph data model 還可能讓程式更簡單。
Document Model 的 Schema 彈性
Document database 的 schema 是只有在你讀取資料並需要對它做解釋時才會用到,稱做 schema-on-read,可以把它視為一種隱含的 schema,相反的 Relational database 的 schema 就稱做 schema-on-write,這就是明確的 schema 定義了,在你寫入資料時會確保資料符合 schema 所定義的格式。
schema-on-read 很像動態程式語言 (執行時檢查),像 Python、PHP,schema-on-write 則是像靜態程式語言 (編譯時檢查),像 Java、C,你無法斷定說哪種類型的程式語言較棒,同理也是得如此這樣看 data model 的 schema 處理方式。
但是 NoSQL 會紅也不是沒有道理的,schema-on-read 在某種程度上帶來非常大的彈性,舉例來說,目前你的 user table 裡,完整的 name 是儲存在一個欄位裡,然後你需要把 name
切成 first_name
和 last_name
2 個欄位儲存,在 Document database 裡,你可以直接在新資料儲存裡加上這 2 個欄位,然後只要新增下面這段 code,舊的資料在讀取時也就有這 2 個欄位了。
if (user && user.name && !user.first_name) {
// Documents written before Dec 8, 2013 don't have first_name
user.first_name = user.name.split(" ")[0];
}
但在 Realtional database 該怎麼做呢?你得要變更 data schema 新增欄位然後 update 舊資料,
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
不用說 alter table 是一種很昂貴的操作,database 可能會變慢也有可能短期內無法提供服務,update 操作亦同。
schema-on-read 的一大優點就是你不用保持所有的資料都是同一種資料架構,例如你的資料來源是外部系統,你無法控制對方什麼時候改變資料型態,在這情況下,Relational database 的 schema 造成的傷害會比幫助多,之後會談到如何做到 document database 的 schema 進化 (evolution)。
查詢時的資料區域性 (Data locality for queries)
documnet 在儲存時通常會存成連續的字串,像 JSON、XML 或變體的 binary 字串 (MongoDB 的 BSON),如果你的軟體是需要一整個 documnet 做事情的,例如產生網頁,這時 storage locality (儲存區域性) 就有優勢了,不用像圖 2-1 那樣,多個 table 代表會有更多的 index 查找跟更多的查詢時間。
這裡值得提出的一點是把相關的資料為了 locality 原因而 group 起來並不侷限在 Documnet Model 而已,像 BigTable 的資料模型 column-family 就是類似概念 (用在 HBase 和 Cassandra),未來會提到更多。
逐漸互相靠近的 relational 與 document database
現在 MySQL 和 PostgreSQL 都開始支援 JSON 和 XML 這種 documnet 類型的資料型態,在 document database 裡,RethinkDB 在 query 中支援了 relational-join , MongoDB 則是用 lookup 的方式做到 join 的情景,但一定比 realtional database 來的慢。
未來 database 一定是往混合的道路走,工程師就有更好的工具完成資料模型的設計了。