2
0
Fork 0
mirror of https://github.com/Vonng/ddia.git synced 2026-06-21 00:47:05 +08:00

improve ch3 translation, update zh-tw content

This commit is contained in:
Gang Yin 2026-02-11 13:48:32 +08:00
parent 330f3f3b27
commit 181eb7970d
4 changed files with 60 additions and 60 deletions

View file

@ -68,17 +68,17 @@ SELECT posts.*, users.* FROM posts
如果由於某些特殊事件導致帖子速率激增,我們不必立即進行時間線交付——我們可以將它們排隊,並接受帖子在粉絲的時間線中顯示會暫時花費更長時間。即使在這種負載峰值期間,時間線仍然可以快速載入,因為我們只是從快取中提供它們。
這種預先計算和更新查詢結果的過程稱為 *物化*,時間線快取是 *物化檢視* 的一個例子(我們將在 [待補充連結] 中進一步討論這個概念)。物化檢視加速了讀取,但作為回報,我們必須在寫入時做更多的工作。對於大多數使用者來說,寫入成本是適度的,但社交網路還必須考慮一些極端情況:
這種預先計算和更新查詢結果的過程稱為 *物化*,時間線快取是 *物化檢視* 的一個例子(我們將在 [待補充連結] 中進一步討論這個概念)。物化檢視加速了讀取,但作為交換,我們必須在寫入時做更多的工作。對於大多數使用者來說,寫入成本是適中的,但社交網路還必須考慮一些極端情況:
* 如果使用者關注非常多的賬戶,並且這些賬戶釋出很多內容,該使用者的物化時間線將有很高的寫入率。然而,在這種情況下,使用者實際上不太可能閱讀其時間線中的所有帖子,因此可以簡單地丟棄其時間線的一些寫入,只向用戶顯示他們關注的賬戶的帖子樣本 [^5]。
* 當擁有大量粉絲的名人賬戶釋出帖子時,我們必須做大量工作將該帖子插入到他們數百萬粉絲的每個首頁時間線中。在這種情況下,丟棄一些寫入是不可接受的。解決這個問題的一種方法是將名人帖子與其他人的帖子分開處理:我們可以透過將名人帖子單獨儲存並在讀取時與物化時間線合併,來節省將它們新增到數百萬時間線的工作。儘管有這些最佳化,在社交網路上處理名人仍然需要大量基礎設施 [^6]。
* 當擁有大量粉絲的名人賬戶釋出帖子時,我們必須做大量工作將該帖子插入到他們數百萬粉絲的每個首頁時間線中。在這種情況下,丟棄一些寫入是不可接受的。解決這個問題的一種方法是將名人帖子與其他人的帖子分開處理:我們可以透過將名人帖子單獨儲存並在讀取時與物化時間線合併,來節省將它們新增到數百萬時間線的工作。儘管有這些最佳化,處理社交網路上的名人仍然需要大量基礎設施 [^6]。
## 描述效能 {#sec_introduction_percentiles}
大多數關於軟體效能的討論都考慮兩種主要的度量型別:
響應時間
: 從使用者發出請求到收到所請求答案的經過時間。測量單位是秒(或毫秒,或微秒)。
: 從使用者發出請求到收到請求應答所經過的時間。測量單位是秒(或毫秒,或微秒)。
吞吐量
: 系統正在處理的每秒請求數,或每秒資料量。對於給定的硬體資源分配,存在可以處理的 *最大吞吐量*。測量單位是"每秒某物"。
@ -156,7 +156,7 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
{{< figure src="/fig/ddia_0206.png" id="fig_tail_amplification" caption="圖 2-6. 當需要幾個後端呼叫來服務請求時,只需要一個慢的後端請求就可以減慢整個終端使用者請求。" class="w-full my-4" >}}
百分位數通常用於 *服務級別目標*SLO*服務級別協議*SLA作為定義服務預期效能和可用性的方式 [^27]。例如SLO 可能設定服務的中位響應時間小於 200 毫秒且第 99 百分位低於 1 秒的目標,以及至少 99.9% 的有效請求導致非錯誤響應的目標。SLA 是一份合同,規定如果不滿足 SLO 會發生什麼(例如,客戶可能有權獲得退款)。這至少是基本想法;實際上,為 SLO 和 SLA 定義良好的可用性指標並不簡單 [^28] [^29]。
百分位數通常用於 *服務級別目標*SLO*服務級別協議*SLA作為定義服務預期效能和可用性的方式 [^27]。例如SLO 可能設定服務的中位響應時間小於 200 毫秒且第 99 百分位低於 1 秒的目標,以及至少 99.9% 的有效請求產生非錯誤響應的目標。SLA 是一份合同,規定如果不滿足 SLO 會發生什麼(例如,客戶可能有權獲得退款)。這至少是基本想法;實際上,為 SLO 和 SLA 定義良好的可用性指標並不簡單 [^28] [^29]。
--------
@ -197,28 +197,28 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
容錯總是限於某些型別的某些數量的故障。例如,系統可能能夠容忍最多兩個硬碟驅動器同時故障,或最多三個節點中的一個崩潰。如果所有節點都崩潰,沒有什麼可以做的,這沒有意義容忍任何數量的故障。如果整個地球(及其上的所有伺服器)被黑洞吞噬,容忍該故障將需要在太空中進行網路託管——祝你獲得批准該預算專案的好運。
反直覺地,在這種容錯系統中,透過故意觸發故障來 *增加* 故障率是有意義的——例如,在沒有警告的情況下隨機殺死單個程序。這稱為 *故障注入*。許多關鍵錯誤實際上是由於錯誤處理不當造成的 [^38];透過故意引發故障,你確保容錯機制不斷得到鍛鍊和測試,這可以增加你對故障自然發生時將被正確處理的信心。*混沌工程* 是一門旨在透過故意注入故障等實驗來提高對容錯機制的信心的學科 [^39]。
反直覺地,在這種容錯系統中,透過故意觸發故障來 *增加* 故障率是有意義的——例如,在沒有警告的情況下隨機殺死單個程序。這稱為 *故障注入*。許多關鍵錯誤實際上是由於錯誤處理不當造成的 [^38];透過故意引發故障,你確保容錯機制不斷得到鍛鍊和測試,這可以增加你對故障自然發生時將被正確處理的信心。*混沌工程* 是一門旨在透過故意注入故障等實驗來提高對容錯機制的信心的學科 [^39]。
儘管我們通常更喜歡容忍故障而不是預防故障,但在預防比治療更好的情況下(例如,因為不存在治療方法)。安全問題就是這種情況:如果攻擊者已經破壞了系統並獲得了對敏感資料的訪問,該事件無法撤消。然而,本書主要涉及可以治癒的故障型別,如以下部分所述。
儘管我們通常更喜歡容忍故障而不是預防故障,但在預防比治療更好的情況下(例如,因為不存在治療方法)。安全問題就是這種情況:如果攻擊者已經破壞了系統並獲得了對敏感資料的訪問,該事件無法撤消。然而,本書主要涉及可以恢復的故障型別,如以下部分所述。
### 硬體與軟體故障 {#sec_introduction_hardware_faults}
當我們想到系統失效的原因時,硬體故障很快就會浮現在腦海中:
* 大約 2-5% 的磁性硬碟驅動器每年發生故障 [^40] [^41];在擁有 10,000 個磁碟的儲存叢集中,我們因此應該期望平均每天有一個磁碟故障。最近的資料表明磁碟變得更可靠,但故障率仍然很顯著 [^42]。
* 大約 0.5-1% 的固態硬碟SSD每年發生故障 [^43]。少量位錯誤會自動糾正 [^44],但不可糾正的錯誤大約每年每個驅動器發生一次,即使在相當新的驅動器中(即,經歷很少磨損);這個錯誤率高於磁性硬碟驅動器 [^45]、[^46]。
* 大約 2-5% 的硬碟驅動器每年發生故障 [^40] [^41];在擁有 10,000 個磁碟的儲存叢集中,我們因此應該認為平均每天有一個磁碟故障。最近的資料表明磁碟變得更可靠,但故障率仍然很顯著 [^42]。
* 大約 0.5-1% 的固態硬碟SSD每年發生故障 [^43]。少量位錯誤會自動糾正 [^44],但不可糾正的錯誤大約每年每個驅動器發生一次,即使在相當新的驅動器中(即,經歷很少磨損);這個錯誤率高於硬碟驅動器 [^45]、[^46]。
* 其他硬體元件如電源、RAID 控制器和記憶體模組也會發生故障,儘管頻率低於硬碟驅動器 [^47] [^48]。
* 大約千分之一的機器有一個 CPU 核心偶爾計算錯誤的結果,可能是由於製造缺陷 [^49] [^50] [^51]。在某些情況下,錯誤的計算會導致崩潰,但在其他情況下,它會導致程式簡單地返回錯誤的結果。
* RAM 中的資料也可能被損壞要麼是由於宇宙射線等隨機事件要麼是由於永久性物理缺陷。即使使用糾錯碼ECC的記憶體超過 1% 的機器在給定年份遇到不可糾正的錯誤,這通常會導致機器崩潰和受影響的記憶體模組需要更換 [^52]。此外,某些病理記憶體訪問模式可以以高機率翻轉位 [^53]。
* 整個資料中心可能變得不可用(例如,由於停電或網路配置錯誤)甚至被永久摧毀(例如,由火災、洪水或地震 [^54])。太陽風暴,當太陽噴射大量帶電粒子時,會在長距離電線中感應出大電流,可能會損壞電網和海底網路電纜 [^55]。儘管這種大規模故障很少見,但如果服務不能容忍資料中心的丟失,它們的影響可能是災難性的 [^56]。
* 大約千分之一的機器有一個 CPU 核心偶爾計算錯誤的結果,可能是製造缺陷 [^49] [^50] [^51]造成的。在某些情況下,錯誤的計算會導致崩潰,但在其他情況下,它會導致程式簡單地返回錯誤的結果。
* RAM 中的資料也可能被損壞要麼是由於宇宙射線等隨機事件要麼是由於永久性物理缺陷。即使使用糾錯碼ECC的記憶體也有超過 1% 的機器在給定年限內遇到不可糾正的錯誤,這通常會導致機器崩潰和受影響的記憶體模組需要更換 [^52]。此外,某些病態的記憶體訪問模式可能會以很高的機率翻轉位元位 [^53]。
* 整個資料中心可能變得不可用(例如,由於停電或網路配置錯誤)甚至被永久摧毀(例如,由火災、洪水或地震 [^54])。太陽風暴,當太陽噴射大量帶電粒子時,會在長距離電線中感應出大電流,可能會損壞電網和海底網路電纜 [^55]。儘管這種大規模故障很少見,但如果服務不能容忍資料中心的丟失,它們的影響可能是災難性的 [^56]。
這些事件足夠罕見,你在處理小型系統時通常不需要擔心它們,只要你可以輕鬆更換變得有故障的硬體。然而,在大規模系統中,硬體故障發生得足夠頻繁,以至於它們成為正常系統執行的一部分。
這些事件足夠罕見,你在處理小型系統時通常不需要擔心它們,只要你能夠輕鬆地更換出現故障的硬體。然而,在大規模系統中,硬體故障發生得足夠頻繁,以至於它們成為正常系統執行的一部分。
#### 透過冗餘容忍硬體故障 {#tolerating-hardware-faults-through-redundancy}
我們對不可靠硬體的第一反應通常是向各個硬體元件新增冗餘,以降低系統的故障率。磁碟可以設定為 RAID 配置(將資料分佈在同一臺機器的多個磁碟上,以便故障磁碟不會導致資料丟失),伺服器可能有雙電源和可熱插拔的 CPU資料中心可能有電池和柴油發電機作為備用電源。這種冗餘通常可以使機器不間斷執行多年。
當元件故障獨立時,冗餘最有效,即一個故障的發生不會改變另一個故障發生的可能性。然而,經驗表明,元件故障之間通常存在顯著的相關性 [^41] [^57] [^58];整個伺服器機架或整個資料中心的不可用仍然比我們希望的更頻繁地發生。
當元件故障獨立時,冗餘最有效,即一個故障的發生不會改變另一個故障發生的可能性。然而,經驗表明,元件故障之間通常存在顯著的相關性 [^41] [^57] [^58];整個伺服器機架或整個資料中心的不可用仍然比我們預期的更頻繁地發生。
硬體冗餘增加了單臺機器的正常執行時間;然而,如 ["分散式與單節點系統"](/tw/ch1#sec_introduction_distributed) 中所討論的,使用分散式系統有一些優勢,例如能夠容忍一個數據中心的完全中斷。出於這個原因,雲系統傾向於較少關注單個機器的可靠性,而是旨在透過在軟體級別容忍故障節點來使服務高度可用。雲提供商使用 *可用區* 來識別哪些資源在物理上位於同一位置;同一地方的資源比地理上分離的資源更可能同時發生故障。
@ -242,27 +242,27 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
### 人類與可靠性 {#id31}
人類設計和構建軟體系統,保持系統執行的操作員也是人類。與機器不同,人類不只是遵循規則;他們的力量是創造性和適應性地完成工作。然而,這一特徵也導致不可預測性,有時會導致失效的錯誤,儘管有最好的意圖。例如,一項對大型網際網路服務的研究發現,操作員的配置更改是中斷的主要原因,而硬體故障(伺服器或網路)僅在 10-25% 的中斷中發揮作用 [^70]。
人類設計和構建軟體系統,保持系統執行的操作員也是人類。與機器不同,人類不只是遵循規則;他們的優勢是創造性和適應性地完成工作。然而,這一特徵也導致不可預測性,有時會導致失效的錯誤,即使本意是好的。例如,一項對大型網際網路服務的研究發現,操作員的配置更改是中斷的主要原因,而硬體故障(伺服器或網路)僅在 10-25% 的中斷中發揮作用 [^70]。
將這些問題標記為"人為錯誤"並希望透過更嚴格的程式和規則合規性來更好地控制人類行為來解決它們是很誘人的。然而,責怪人們的錯誤是適得其反的。我們所說的"人為錯誤"實際上不是事件的原因,而是人們在社會技術系統中盡力做好工作的問題的症狀 [^71]。通常,複雜系統具有緊急行為,元件之間的意外互動也可能導致故障 [^72]。
人們很自然地傾向於將這類問題歸咎於“人為錯誤”,並希望透過更嚴格的程式和規則遵守來更好地控制人為行為從而解決問題。然而,將錯誤歸咎於人是適得其反的。我們所說的“人為錯誤”並非事件的真實原因,而是人們盡力工作時,社會技術系統中存在問題的徵兆 [^71]。通常,複雜系統具有緊急行為,元件之間的意外互動也可能導致故障 [^72]。
各種技術措施可以幫助最小化人為錯誤的影響,包括徹底測試(手寫測試和對大量隨機輸入的 *屬性測試*[^38]、快速回滾配置更改的回滾機制、新程式碼的逐步推出、詳細和清晰的監控、用於診斷生產問題的可觀測性工具(參見 ["分散式系統的問題"](/tw/ch1#sec_introduction_dist_sys_problems)),以及鼓勵"正確的事情"並阻止"錯誤的事情"的精心設計的介面。
各種技術措施可以幫助最小化人為錯誤的影響,包括徹底測試(手寫測試和對大量隨機輸入的 *屬性測試*[^38]、快速回滾配置更改的回滾機制、新程式碼的漸進部署、詳細和清晰的監控、用於診斷生產問題的可觀測性工具(參見 ["分散式系統的問題"](/tw/ch1#sec_introduction_dist_sys_problems)),以及鼓勵"正確的事情"並阻止"錯誤的事情"的精心設計的介面。
然而,這些事情需要時間和金錢的投資,在日常業務的務實現實中,組織通常優先考慮創收活動而不是增加其抵禦錯誤的韌性的措施。如果在更多功能和更多測試之間有選擇,許多組織可以理解地選擇功能。鑑於這種選擇,當可預防的錯誤不可避免地發生時,責怪犯錯誤的人是沒有意義的——問題是組織的優先事項
然而,這些事情需要時間和金錢的投資,在日常業務的務實現實中,組織通常優先考慮創收活動而不是增加其抵禦錯誤的韌性措施。如果要在更多功能和更多測試之間做出選擇,許多組織會很自然地選擇功能。鑑於這種選擇,當可預防的錯誤不可避免地發生時,責怪犯錯誤的人是沒有意義的——問題在於組織的事項優先順序
越來越多的組織正在採用 *無責備事後分析* 的文化:事件發生後,鼓勵相關人員充分分享發生的事情的細節,而不用擔心懲罰,因為這允許組織中的其他人學習如何在未來防止類似的問題 [^73]。這個過程可能會發現需要改變業務優先順序、需要投資於被忽視的領域、需要改變相關人員的激勵措施,或者需要引起管理層注意的其他一些系統性問題。
越來越多的組織正在採用 *無責備事後分析* 的文化:事件發生後,鼓勵相關人員充分分享發生的事情的細節,而不用擔心懲罰,因為這允許組織中的其他人學習如何在未來防止類似的問題 [^73]發生。這個過程可能會發現需要改變業務優先順序、需要投資於被忽視的領域、需要改變相關人員的激勵措施,或者需要引起管理層注意的其他一些系統性問題。
作為一般原則,在調查事件時,你應該對簡單化的答案持懷疑態度。"鮑勃在部署該更改時應該更加小心"是沒有成效的,但"我們必須用 Haskell 重寫後端"也不是。相反,管理層應該藉此機會從每天與之合作的人的角度瞭解社會技術系統如何工作的細節,並根據這些反饋採取措施改進它 [^71]。
作為一般原則,在調查事件時,你應該對簡單化的答案持懷疑態度。"鮑勃在部署該更改時應該更加小心"是沒有意義的,"我們必須用 Haskell 重寫後端"也一樣。相反,管理層應該藉此機會從每天與之合作的人的角度瞭解社會技術系統如何工作的細節,並根據這些反饋採取措施改進它 [^71]。
--------
> [!TIP] 可靠性有多重要?
可靠性不僅僅適用於核電站和空中交通管制——更平凡的應用程式也應該可靠地工作。業務應用程式中的錯誤會導致生產力損失(如果數字報告不正確,還會有法律風險),電子商務網站的中斷可能會在收入和聲譽損害方面造成巨大成本。
可靠性不僅僅適用於核電站和空中交通管制——更普通的應用程式也應該可靠地工作。業務應用程式中的錯誤會導致生產力損失(如果資料被不正確地報告,還會有法律風險),而電子商務網站的中斷會因收入損失和聲譽受損而產生巨大的成本。
在許多應用程式中,幾分鐘甚至幾小時的臨時中斷是可以容忍的 [^74],但永久資料丟失或損壞將是災難性的。考慮一位家長在你的照片應用程式中儲存他們孩子的所有照片和影片 [^75]。如果該資料庫突然損壞,他們會有什麼感覺?他們會知道如何從備份中恢復嗎?
作為不可靠軟體如何傷害人們的另一個例子,考慮郵局地平線醜聞。在 1999 年至 2019 年期間,管理英國郵局分支機構的數百人因會計軟體顯示其賬戶短缺而被判盜竊或欺詐罪。最終變得清楚,許多這些短缺是由於軟體中的錯誤,許多定罪已被推翻 [^76]。導致這一可能是英國曆史上最大的司法不公的是,英國法律假設計算機正確執行(因此,計算機產生的證據是可靠的),除非有相反的證據 [^77]。軟體工程師可能會嘲笑軟體可能無錯誤的想法,但這對那些因不可靠的計算機系統而被錯誤監禁、宣佈破產甚至自殺的人來說,這是很少的安慰。
作為不可靠軟體如何傷害人們的另一個例子,可以參考郵局“Horizon”醜聞。在 1999 年至 2019 年期間,管理英國郵局分支機構的數百人因會計軟體顯示其賬戶財務漏洞而被判盜竊或欺詐罪。最終真相水落石出,許多這些財務漏洞是由於軟體中的錯誤,許多定罪已被推翻 [^76]。導致這一可能是英國曆史上最大的司法不公的是,英國法律假設計算機正確執行(因此,計算機產生的證據是可靠的),除非有相反的證據 [^77]。軟體工程師可能會嘲笑軟體可能無錯誤的想法,但這對那些因不可靠的計算機系統而被錯誤監禁、宣佈破產甚至自殺的人來說,這幾乎沒什麼安慰。
在某些情況下,我們可能選擇犧牲可靠性以降低開發成本(例如,在為未經證實的市場開發原型產品時)——但我們應該非常清楚何時走捷徑並牢記潛在的後果。
@ -279,16 +279,16 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
原因是可伸縮性不是一維標籤:說"X 是可伸縮的"或"Y 不伸縮"是沒有意義的。相反,討論可伸縮性意味著考慮諸如以下問題:
* "如果系統以特定方式增長,我們有什麼選擇來應對增長?"
* "我們如何增計算資源來處理額外的負載?"
* "我們如何增計算資源來處理額外的負載?"
* "基於當前的增長預測,我們何時會達到當前架構的極限?"
如果你成功地使你的應用程式受歡迎,因此處理越來越多的負載,你將瞭解你的效能瓶頸在哪裡,因此你將知道需要沿著哪些維度進行伸縮。那時是開始擔心可伸縮性技術的時候。
### 描述負載 {#id33}
首先,我們需要簡潔地描述系統上的當前負載;只有這樣我們才能討論增長問題(如果我們的負載翻倍會發生什麼?)。通常這將是吞吐量的度量:例如,對服務的每秒請求數、每天到達多少千兆位元組的新資料,或每小時購物車結賬的數量。有時你關心某個變數數的峰值,例如 ["案例研究:社交網路首頁時間線"](/tw/ch2#sec_introduction_twitter) 中同時線上使用者的數量。
首先,我們需要簡潔地描述系統上的當前負載;只有這樣我們才能討論增長問題(如果我們的負載翻倍會發生什麼?)。通常這將是吞吐量的度量:例如,對服務的每秒請求數、每天到達多少千兆位元組的新資料,或每小時購物車結賬的數量。有時你關心某個變數數的峰值,例如 ["案例研究:社交網路首頁時間線"](/tw/ch2#sec_introduction_twitter) 中同時線上使用者的數量。
通常還有其他影響訪問模式並因此影響可伸縮性要求的負載統計特徵。例如,你可能需要知道資料庫中的讀寫比率、快取的命中率或每個使用者的資料項數量(例如,社交網路案例研究中的粉絲數量)。也許平均情況對你很重要,或者也許你的瓶頸由少數極端情況主導。這一切都取決於你特定應用程式的細節。
通常還有其他負載統計特徵,會影響訪問模式,從而影響可擴充套件性要求。例如,你可能需要知道資料庫中的讀寫比率、快取的命中率或每個使用者的資料項數量(例如,社交網路案例研究中的粉絲數量)。也許平均情況對你很重要,或者也許你的瓶頸由少數極端情況主導。這一切都取決於你特定應用程式的細節。
一旦你描述了系統上的負載,你就可以調查當負載增加時會發生什麼。你可以從兩個方面來看待它:
@ -303,7 +303,7 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
增加服務硬體資源的最簡單方法是將其移動到更強大的機器。單個 CPU 核心不再變得顯著更快,但你可以購買一臺機器(或租用雲實例)具有更多 CPU 核心、更多 RAM 和更多磁碟空間。這種方法稱為 *縱向伸縮**向上擴充套件*
你可以透過使用多個程序或執行緒在單臺機器上獲得並行性。屬於同一程序的所有執行緒都可以訪問相同的 RAM因此這種方法也稱為 *共享記憶體架構*。共享記憶體方法的問題是成本增長速度快於線性:具有兩倍硬體資源的高階機器通常成本遠遠超過兩倍。由於瓶頸,兩倍大小的機器通常可以處理不到兩倍的負載。
你可以透過使用多個程序或執行緒在單臺機器上獲得並行性。屬於同一程序的所有執行緒都可以訪問相同的 RAM因此這種方法也稱為 *共享記憶體架構*。共享記憶體方法的問題是成本增長速度快於線性:具有兩倍硬體資源的高階機器通常成本遠遠超過兩倍。由於瓶頸,兩倍大小的機器通常只能處理不到兩倍的負載。
另一種方法是 *共享磁碟架構*,它使用幾臺具有獨立 CPU 和 RAM 的機器,但將資料儲存在機器之間共享的磁碟陣列上,這些機器透過快速網路連線:*網路附加儲存*NAS*儲存區域網路*SAN。這種架構傳統上用於本地資料倉庫工作負載但爭用和鎖定的開銷限制了共享磁碟方法的可伸縮性 [^81]。
@ -321,15 +321,15 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
可伸縮性的一個良好通用原則是將系統分解為可以在很大程度上相互獨立執行的較小元件。這是微服務背後的基本原則(參見 ["微服務與無伺服器"](/tw/ch1#sec_introduction_microservices))、分片([第 7 章](/tw/ch7))、流處理([待補充連結])和無共享架構。然而,挑戰在於知道在哪裡劃分應該在一起的事物和應該分開的事物之間的界限。微服務的設計指南可以在其他書籍中找到 [^84],我們在 [第 7 章](/tw/ch7) 中討論無共享系統的分片。
另一個好原則是不要讓事情變得比必要的更複雜。如果單機資料庫可以完成工作,它可能比複雜的分散式設定更可取。自動伸縮系統(根據需求自動新增或刪除資源)很酷,但如果你的負載相當可預測,手動伸縮的系統可能會有更少的操作意外(參見 ["操作:自動或手動再平衡"](/tw/ch7#sec_sharding_operations))。具有五個服務的系統比具有五十個服務的系統更簡單。良好的架構通常涉及方法的務實混合。
另一個好原則是不要讓事情變得比必要的更複雜。如果單機資料庫可以完成工作,它可能比複雜的分散式設定更可取。自動伸縮系統(根據需求自動新增或刪除資源)很酷,但如果你的負載相當可預測,手動伸縮的系統可能會有更少的操作意外(參見 ["操作:自動或手動再平衡"](/tw/ch7#sec_sharding_operations))。具有五個服務的系統比具有五十個服務的系統更簡單。良好的架構通常涉及多種方案的務實混合。
## 可維護性 {#sec_introduction_maintainability}
軟體不會磨損或遭受材料疲勞,因此它不會像機械物體那樣以同樣的方式損壞。但應用程式的要求經常變化,軟體執行的環境發生變化(例如其依賴項和底層平臺),並且它有需要修復的錯誤。
軟體不會磨損或遭受材料老化,因此它不會像機械物體那樣損壞。但應用程式的需求經常變化,軟體執行在變化的環境中(例如其依賴項和底層平臺),並且它有需要修復的錯誤。
人們普遍認為,軟體的大部分成本不在其初始開發中,而在其持續維護中——修復錯誤、保持其系統執行、調查故障、將其適應新平臺、為新用例修改它、償還技術債務和新增新功能 [^85] [^86]。
然而,維護也很困難。如果系統已成功執行很長時間,它可能使用今天不多工程師理解的過時技術(如大型機和 COBOL 程式碼);關於系統如何以及為何以某種方式設計的機構知識可能已經隨著人們離開組織而丟失;可能需要修復其他人的錯誤。此外,計算機系統通常與它支援的人類組織交織在一起,這意味著此類 *遺留* 系統的維護既是人的問題,也是技術問題 [^87]。
然而,維護也很困難。如果系統已成功執行很長時間,它可能使用如今很少有工程師理解的過時技術(如大型機和 COBOL 程式碼);隨著人員的離開,關於系統如何以及為何以某種特定方式設計的制度性知識可能已經丟失了;可能需要修復其他人的錯誤。此外,計算機系統通常與它支援的人類組織交織在一起,這意味著此類 *遺留* 系統的維護既是人的問題,也是技術問題 [^87]。
如果我們今天建立的每個系統都足夠有價值以長期生存,它有一天將成為遺留系統。為了最小化需要維護我們軟體的未來幾代人的痛苦,我們應該在設計時考慮維護問題。儘管我們不能總是預測哪些決定可能會在未來造成維護難題,但在本書中,我們將注意幾個廣泛適用的原則:
@ -340,7 +340,7 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
: 透過使用易於理解、一致的模式和結構來實施它,並避免不必要的複雜性,使新工程師容易理解系統。
可演化性Evolvability
: 使工程師將來容易對系統進行更改,隨著需求變化而適應和擴充套件它以用於未預料的用例
: 使工程師將來容易對系統進行更改,隨著需求變化而適應和擴充套件它以用於未預料的使用場景
### 可運維性:讓運維更輕鬆 {#id37}
@ -348,7 +348,7 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
事實上,有人提出 “良好的運維通常可以解決糟糕(或不完整)軟體的侷限性,但再好的軟體碰上糟糕的運維也難以可靠地執行” [^60]。
在由數千臺機器組成的大規模系統中,手動維護將是不合理地昂貴的,自動化是必不可少的。然而,自動化可能是一把雙刃劍:
總會有邊緣情況(如罕見的故障場景)需要運維團隊的手動干預。由於無法自動處理的情況是最複雜的問題,更大的自動化需要一個 **更** 熟練的運維團隊來解決這些問題 [^88]。
總會有邊際場景(如罕見的故障場景)需要運維團隊的手動干預。由於無法自動處理的情況是最複雜的問題,更大的自動化需要一個 **更** 熟練的運維團隊來解決這些問題 [^88]。
此外,如果自動化系統出錯,通常比依賴操作員手動執行某些操作的系統更難排除故障。出於這個原因,更多的自動化並不總是對可操作性更好。
然而,一定程度的自動化很重要,最佳點將取決於你特定應用程式和組織的細節。
@ -356,8 +356,8 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
良好的可操作性意味著使常規任務變得容易,使運維團隊能夠將精力集中在高價值活動上。
資料系統可以做各種事情來使常規任務變得容易,包括 [^89]
* 允許監控工具檢查系統的關鍵指標,並支援可觀測性工具(參見 ["分散式系統的問題"](/tw/ch1#sec_introduction_dist_sys_problems))以深入瞭解系統的執行時行為。各種商業和開源工具可以在這提供幫助 [^90]。
* 避免對單個機器的依賴(允許在系統整體續不間斷執行的同時關閉機器進行維護)
* 允許監控工具檢查系統的關鍵指標,並支援可觀測性工具(參見 ["分散式系統的問題"](/tw/ch1#sec_introduction_dist_sys_problems))以深入瞭解系統的執行時行為。各種商業和開源工具可以在這方面提供幫助 [^90]。
* 避免對單個機器的依賴(允許在系統整體續不間斷執行的同時關閉機器進行維護)
* 提供良好的文件和易於理解的操作模型("如果我做 XY 將會發生"
* 提供良好的預設行為,但也給管理員在需要時覆蓋預設值的自由
* 在適當的地方自我修復,但也在需要時給管理員手動控制系統狀態
@ -365,8 +365,8 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
### 簡單性:管理複雜度 {#id38}
小型軟體專案可以有令人愉快地簡單和富有表現力的程式碼,但隨著專案變大,它們通常變得非常複雜且難以理解。
這種複雜性減慢了需要在系統上工作的每個人,進一步增加了維護成本。陷入複雜性的軟體專案有時被描述為 *大泥* [^91]。
小型軟體專案可以有令人愉悅的、簡單而富有表現力的程式碼,但隨著專案變大,它們通常變得非常複雜且難以理解。
這種複雜性減慢了需要在系統上工作的每個人的效率,進一步增加了維護成本。陷入複雜性的軟體專案有時被描述為 *大泥* [^91]。
當複雜性使維護困難時,預算和時間表經常超支。在複雜軟體中,進行更改時引入錯誤的風險也更大:
當系統對開發人員來說更難理解和推理時,隱藏的假設、意外的後果和意外的互動更容易被忽視 [^69]。
@ -381,10 +381,10 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
不幸的是,這種區別也有缺陷,因為本質和偶然之間的邊界隨著我們工具的發展而變化 [^94]。
我們管理複雜性的最佳工具之一是 **抽象**。良好的抽象可以在乾淨、易於理解的外觀後面隱藏大量實現細節。良好的抽象也可以用於各種不同的應用程式。
這種重用不僅比多次重新實現類似的東西更有效,而且還導致更高質量的軟體,因為抽象元件中的質量改進使所有使用它的應用程式受益。
這種重用不僅比多次重新實現類似的東西更有效,而且還能提高軟體質量,因為抽象元件中的質量改進使所有使用它的應用程式受益。
例如,高階程式語言是隱藏機器程式碼、CPU 暫存器和系統呼叫的抽象。SQL 是一種隱藏複雜的磁碟和記憶體中資料結構、來自其他客戶端的併發請求以及崩潰後不一致性的抽象。
當然,在用高階語言程式設計時,我們仍在使用機器程式碼;我們只是不 *直接* 使用它,因為程式語言抽象使我們免於考慮它。
例如高階程式語言是隱藏機器碼、CPU 暫存器和系統呼叫的抽象。SQL 是一種隱藏磁碟和記憶體中的複雜資料結構、來自其他客戶端的併發請求以及崩潰後不一致性的抽象。
當然,在用高階語言程式設計時,我們仍在使用機器碼;我們只是不 *直接* 使用它,因為程式語言抽象使我們不必考慮它。
應用程式程式碼的抽象,旨在降低其複雜性,可以使用諸如 *設計模式* [^95] 和 *領域驅動設計*DDD[^96] 等方法建立。
本書不是關於此類特定於應用程式的抽象,而是關於你可以在其上構建應用程式的通用抽象,例如資料庫事務、索引和事件日誌。如果你想使用像 DDD 這樣的技術,你可以在本書中描述的基礎之上實現它們。
@ -396,10 +396,10 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
新平臺取代舊平臺、法律或監管要求發生變化、系統增長迫使架構變化等。
在組織流程方面,*敏捷* 工作模式為適應變化提供了框架。敏捷社群還開發了在頻繁變化的環境中開發軟體時有用的技術工具和流程,
例如測試驅動開發TDD和重構。在本書中我們尋在由具有不同特徵的幾個不同應用程式或服務組成的系統級別增加敏捷性的方法。
例如測試驅動開發TDD和重構。在本書中我們尋在由具有不同特徵的幾個不同應用程式或服務組成的系統級別增加敏捷性的方法。
你可以修改資料系統並使其適應不斷變化的需求的容易程度與其簡單性及其抽象密切相關:鬆散耦合、簡單系統通常比緊密耦合、複雜系統更容易修改。
由於這是一個如此重要的想法,我們將使用不同的詞來指代資料系統級別的敏捷性:*可演化性* [^97]。
你可以修改資料系統並使其適應不斷變化的需求的容易程度與其簡單性及其抽象密切相關:松耦合、簡單的系統通常比緊耦合、複雜的系統更容易修改。
由於這是一個如此重要的概念,我們將使用一個不同的詞來指代資料系統級別的敏捷性:*可演化性* [^97]。
使大型系統中的變化困難的一個主要因素是某些操作不可逆,因此需要非常謹慎地採取該操作 [^98]。
例如,假設你正在從一個數據庫遷移到另一個數據庫:如果在新資料庫出現問題時無法切換回舊系統,風險就會高得多,而如果你可以輕鬆返回。最小化不可逆性提高了靈活性。
@ -407,10 +407,10 @@ Akamai 最近的一項研究 [^24] 聲稱響應時間增加 100 毫秒將電子
## 總結 {#summary}
在本章中,我們研究了幾個非功能性需求的例子:效能、可靠性、可伸縮性和可維護性。
透過這些主題,我們還遇到了我們在本書其餘部分需要的原則和術語。我們從社交網路中首頁時間線如何實現的案例研究開始,這說明了規模上出現的一些挑戰。
透過這些主題,我們還遇到了我們在本書其餘部分需要的原則和術語。我們從社交網路中首頁時間線如何實現的案例研究開始,這說明了在規模擴大時出現的一些挑戰。
我們討論了如何衡量效能(例如,使用響應時間百分位數)、系統上的負載(例如,使用吞吐量指標),以及它們如何在 SLA 中使用。
可伸縮性是一個密切相關的概念:即,在負載增長時確保效能保持不變。我們看到了可伸縮性的一些一般原則,例如將任務分解為可以獨立執行的較小部分,我們將在以下章節中深入研究可伸縮性技術的技術細節。
可伸縮性是一個密切相關的概念:即,在負載增長時確保效能保持不變。我們看到了可伸縮性的一些通用性原則,例如將任務分解為可以獨立執行的較小部分,我們將在以下章節中深入研究可伸縮性技術的技術細節。
為了實現可靠性,你可以使用容錯技術,即使某個元件(例如,磁碟、機器或其他服務)出現故障,系統也可以繼續提供其服務。
我們看到了可能發生的硬體故障的例子,並將它們與軟體故障區分開來,軟體故障可能更難處理,因為它們通常是強相關的。

View file

@ -12,7 +12,7 @@ breadcrumbs: false
資料模型或許是開發軟體最重要的部分,因為它們有著深遠的影響:不僅影響軟體的編寫方式,還影響我們 **思考問題** 的方式。
大多數應用程式都是透過層層疊加的資料模型來構建的。對於每一層來說的關鍵問題是:如何用更低層次的資料模型來 **表示** 它?例如:
大多數應用程式都是透過層層疊加的資料模型來構建的。每一層的關鍵問題是:如何用更低層次的資料模型來 **表示** 它?例如:
1. 作為應用程式開發者,你觀察現實世界(其中有人員、組織、貨物、行為、資金流動、感測器等),並用物件或資料結構,以及操作這些資料結構的 API 來建模。這些結構通常是特定於應用程式的。
2. 當你想要儲存這些資料結構時,你用通用的資料模型來表達它們,例如 JSON 或 XML 文件、關係資料庫中的表,或者圖中的頂點和邊。這些資料模型是本章的主題。
@ -36,7 +36,7 @@ breadcrumbs: false
> 但更重要的是,它還隱藏了查詢引擎的實現細節,這使得資料庫系統可以在不需要更改任何查詢的情況下引入效能改進 [^1]。
>
> 例如,資料庫可能能夠跨多個 CPU 核心和機器並行執行宣告式查詢,而你無需擔心如何實現該並行性 [^2]。
> 在手寫演算法中,實現這種並行執行將需要大量的工作。
> 如果用手寫演算法,實現這種並行執行將需要大量工作。
--------
@ -129,7 +129,7 @@ NoSQL 運動的一個持久影響是 **文件模型** 的流行,它通常將
一些開發人員認為 JSON 模型減少了應用程式程式碼和儲存層之間的阻抗不匹配。然而,正如我們將在 [第 5 章](/tw/ch5#ch_encoding) 中看到的JSON 作為資料編碼格式也存在問題。缺乏模式通常被認為是一個優勢;我們將在 ["文件模型中的模式靈活性"](/tw/ch3#sec_datamodels_schema_flexibility) 中討論這個問題。
與 [圖 3-1](/tw/ch3#fig_obama_relational) 中的多表模式相比JSON 表示具有更好的 *區域性*(參見 ["讀寫的資料區域性"](/tw/ch3#sec_datamodels_document_locality))。如果你想在關係示例中獲取個人資料,你需要執行多個查詢(透過 `user_id` 查詢每個表)或在 `users` 表與其從屬表之間執行混亂的多路連線 [^8]。在 JSON 表示中,所有相關資訊都在一個地方,使查詢既更快又更簡單。
與 [圖 3-1](/tw/ch3#fig_obama_relational) 中的多表模式相比JSON 表示具有更好的 *區域性*(參見 ["讀寫的資料區域性"](/tw/ch3#sec_datamodels_document_locality))。如果你想在關係示例中獲取個人資料,你需要執行多個查詢(透過 `user_id` 查詢每個表)或在 `users` 表與其從屬表之間執行複雜的多表連線 [^8]。在 JSON 表示中,所有相關資訊都在一個地方,使查詢既更快又更簡單。
從使用者個人資料到使用者職位、教育歷史和聯絡資訊的一對多關係暗示了資料中的樹形結構,而 JSON 表示使這種樹形結構變得明確(見 [圖 3-2](/tw/ch3#fig_json_tree))。
@ -146,10 +146,10 @@ NoSQL 運動的一個持久影響是 **文件模型** 的流行,它通常將
在前一節的 [示例 3-1](/tw/ch3#fig_obama_json) 中,`region_id` 被給出為 ID而不是純文字字串 `"Washington, DC, United States"`。為什麼?
如果使用者介面有一個用於輸入地區的自由文字欄位,將其儲存為純文字字串是有意義的。但是,擁有標準化的地理區域列表並讓使用者從下拉列表或自動完成中選擇有其優勢:
如果使用者介面有一個用於輸入地區的自由文字欄位,將其儲存為純文字字串是有意義的。但是,擁有標準化的地理區域列表並讓使用者從下拉列表或自動補全中選擇也有其優勢:
* 跨個人資料的風格和拼寫一致性
* 避免歧義如果有幾個同名的地方(如果字串只是 "Washington",它是指 DC 還是州?)
* 不同個人資料之間的風格和拼寫保持一致
* 避免歧義如果有幾個同名的地方(如果字串只是 "Washington",它是指 DC 還是州?)
* 易於更新 —— 名稱只儲存在一個地方,因此如果需要更改(例如,由於政治事件而更改城市名稱),可以輕鬆地全面更新
* 本地化支援 —— 當網站被翻譯成其他語言時,標準化列表可以被本地化,因此區域可以用檢視者的語言顯示
* 更好的搜尋 —— 例如,搜尋美國東海岸的人可以匹配此個人資料,因為區域列表可以編碼華盛頓位於東海岸的事實(這從字串 `"Washington, DC"` 中並不明顯)
@ -198,7 +198,7 @@ db.users.aggregate([
#### 社交網路案例研究中的反正規化 {#denormalization-in-the-social-networking-case-study}
在 ["案例研究:社交網路頁時間線"](/tw/ch2#sec_introduction_twitter) 中,我們比較了正規化表示([圖 2-1](/tw/ch2#fig_twitter_relational))和反正規化表示(預計算的物化時間線):這裡,`posts` 和 `follows` 之間的連線太昂貴了,物化時間線是該連線結果的快取。將新帖子插入關注者時間線的扇出過程是我們保持反正規化表示一致的方式。
在 ["案例研究:社交網路頁時間線"](/tw/ch2#sec_introduction_twitter) 中,我們比較了正規化表示([圖 2-1](/tw/ch2#fig_twitter_relational))和反正規化表示(預計算的物化時間線):這裡,`posts` 和 `follows` 之間的連線太昂貴了,物化時間線是該連線結果的快取。將新帖子插入關注者時間線的扇出過程是我們保持反正規化表示一致的方式。
然而X前 Twitter的物化時間線實現實際上並不儲存每個帖子的實際文字每個條目實際上只儲存帖子 ID、釋出者的使用者 ID以及一些額外的資訊來識別轉發和回覆 [^11]。換句話說,它是(大約)以下查詢的預計算結果:
@ -529,7 +529,7 @@ RETURN person.name
答案是肯定的,但有一些困難。你在圖查詢中遍歷的每條邊實際上都是與 `edges` 表的連線。在關係資料庫中,你通常事先知道查詢中需要哪些連線。另一方面,在圖查詢中,你可能需要遍歷可變數量的邊才能找到你要查詢的頂點 —— 也就是說,連線的數量不是預先固定的。
在我們的示例中,這發生在 Cypher 查詢中的 `() -[:WITHIN*0..]-> ()` 模式中。一個人的 `LIVES_IN` 邊可能指向任何型別的位置:街道、城市、地區、地區、州等。一個城市可能在一個地區 `WITHIN`,一個地區在一個州 `WITHIN`,一個州在一個國家 `WITHIN`,等等。`LIVES_IN` 邊可能直接指向你要查詢的位置頂點,或者它可能在位置層次結構中相距幾個級別。
在我們的示例中,這發生在 Cypher 查詢中的 `() -[:WITHIN*0..]-> ()` 模式中。一個人的 `LIVES_IN` 邊可能指向任何型別的位置:街道、城市、district、地區region、州等。一個城市可能在`WITHIN`)某個地區,該地區在(`WITHIN`)某個州,該州在(`WITHIN`)某個國家,等等。`LIVES_IN` 邊可能直接指向你要查詢的位置頂點,或者它可能在位置層次結構中相距幾個級別。
在 Cypher 中,`:WITHIN*0..` 非常簡潔地表達了這個事實:它意味著"跟隨 `WITHIN` 邊,零次或多次"。它就像正則表示式中的 `*` 運算子。

View file

@ -229,7 +229,7 @@ breadcrumbs: false
許多應用程式讓使用者提交一些資料,然後檢視他們提交的內容。這可能是客戶資料庫中的記錄,或討論執行緒上的評論,或其他類似的東西。提交新資料時,必須將其傳送到主節點,但當用戶檢視資料時,可以從從節點讀取。如果資料經常被檢視但只是偶爾被寫入,這尤其合適。
使用非同步複製,存在一個問題,如 [圖 6-3](/tw/ch6#fig_replication_read_your_writes) 所示:如果使用者在寫入後不久檢視資料,新資料可能尚未到達副本。對使用者來說,看起來他們提交的資料丟失了,所以他們會理解地不高興。
使用非同步複製,存在一個問題,如 [圖 6-3](/tw/ch6#fig_replication_read_your_writes) 所示:如果使用者在寫入後不久檢視資料,新資料可能尚未到達副本。對使用者來說,看起來他們提交的資料丟失了,所以他們自然會不高興。
{{< figure src="/fig/ddia_0603.png" id="fig_replication_read_your_writes" caption="圖 6-3. 使用者進行寫入,然後從陳舊副本讀取。為了防止這種異常,我們需要寫後讀一致性。" class="w-full my-4" >}}
@ -303,7 +303,7 @@ Poons 先生
### 複製延遲的解決方案 {#id131}
在使用最終一致系統時,值得思考如果複製延遲增加到幾分鐘甚至幾小時,應用程式的行為如何。如果答案是"沒問題",那很好。然而,如果結果對使用者來說是糟糕的體驗,那麼設計系統以提供更強的保證(如寫後讀)很重要。假裝複製是同步的,而實際上它是非同步的,是以後出現問題的秘訣
在使用最終一致系統時,值得思考如果複製延遲增加到幾分鐘甚至幾小時,應用程式的行為如何。如果答案是"沒問題",那很好。然而,如果結果對使用者來說是糟糕的體驗,那麼設計系統以提供更強的保證(如寫後讀)很重要。明明是非同步複製,卻假裝它是同步的,這為日後出問題埋下了隱患
如前所述,應用程式可以提供比底層資料庫更強的保證——例如,透過在主節點或同步更新的從節點上執行某些型別的讀取。然而,在應用程式程式碼中處理這些問題很複雜且容易出錯。

View file

@ -12,7 +12,7 @@ breadcrumbs: false
数据模型或许是开发软件最重要的部分,因为它们有着深远的影响:不仅影响软件的编写方式,还影响我们 **思考问题** 的方式。
大多数应用程序都是通过层层叠加的数据模型来构建的。对于每一层来说的关键问题是:如何用更低层次的数据模型来 **表示** 它?例如:
大多数应用程序都是通过层层叠加的数据模型来构建的。每一层的关键问题是:如何用更低层次的数据模型来 **表示** 它?例如:
1. 作为应用程序开发者,你观察现实世界(其中有人员、组织、货物、行为、资金流动、传感器等),并用对象或数据结构,以及操作这些数据结构的 API 来建模。这些结构通常是特定于应用程序的。
2. 当你想要存储这些数据结构时,你用通用的数据模型来表达它们,例如 JSON 或 XML 文档、关系数据库中的表,或者图中的顶点和边。这些数据模型是本章的主题。
@ -36,7 +36,7 @@ breadcrumbs: false
> 但更重要的是,它还隐藏了查询引擎的实现细节,这使得数据库系统可以在不需要更改任何查询的情况下引入性能改进 [^1]。
>
> 例如,数据库可能能够跨多个 CPU 核心和机器并行执行声明式查询,而你无需担心如何实现该并行性 [^2]。
> 在手写算法中,实现这种并行执行将需要大量的工作。
> 如果用手写算法,实现这种并行执行将需要大量工作。
--------
@ -129,7 +129,7 @@ NoSQL 运动的一个持久影响是 **文档模型** 的流行,它通常将
一些开发人员认为 JSON 模型减少了应用程序代码和存储层之间的阻抗不匹配。然而,正如我们将在 [第 5 章](/ch5#ch_encoding) 中看到的JSON 作为数据编码格式也存在问题。缺乏模式通常被认为是一个优势;我们将在 ["文档模型中的模式灵活性"](/ch3#sec_datamodels_schema_flexibility) 中讨论这个问题。
与 [图 3-1](/ch3#fig_obama_relational) 中的多表模式相比JSON 表示具有更好的 *局部性*(参见 ["读写的数据局部性"](/ch3#sec_datamodels_document_locality))。如果你想在关系示例中获取个人资料,你需要执行多个查询(通过 `user_id` 查询每个表)或在 `users` 表与其从属表之间执行混乱的多路连接 [^8]。在 JSON 表示中,所有相关信息都在一个地方,使查询既更快又更简单。
与 [图 3-1](/ch3#fig_obama_relational) 中的多表模式相比JSON 表示具有更好的 *局部性*(参见 ["读写的数据局部性"](/ch3#sec_datamodels_document_locality))。如果你想在关系示例中获取个人资料,你需要执行多个查询(通过 `user_id` 查询每个表)或在 `users` 表与其从属表之间执行复杂的多表连接 [^8]。在 JSON 表示中,所有相关信息都在一个地方,使查询既更快又更简单。
从用户个人资料到用户职位、教育历史和联系信息的一对多关系暗示了数据中的树形结构,而 JSON 表示使这种树形结构变得明确(见 [图 3-2](/ch3#fig_json_tree))。
@ -146,10 +146,10 @@ NoSQL 运动的一个持久影响是 **文档模型** 的流行,它通常将
在前一节的 [示例 3-1](/ch3#fig_obama_json) 中,`region_id` 被给出为 ID而不是纯文本字符串 `"Washington, DC, United States"`。为什么?
如果用户界面有一个用于输入地区的自由文本字段,将其存储为纯文本字符串是有意义的。但是,拥有标准化的地理区域列表并让用户从下拉列表或自动完成中选择有其优势:
如果用户界面有一个用于输入地区的自由文本字段,将其存储为纯文本字符串是有意义的。但是,拥有标准化的地理区域列表并让用户从下拉列表或自动补全中选择也有其优势:
* 跨个人资料的风格和拼写一致性
* 避免歧义如果有几个同名的地方(如果字符串只是 "Washington",它是指 DC 还是州?)
* 不同个人资料之间的风格和拼写保持一致
* 避免歧义如果有几个同名的地方(如果字符串只是 "Washington",它是指 DC 还是州?)
* 易于更新 —— 名称只存储在一个地方,因此如果需要更改(例如,由于政治事件而更改城市名称),可以轻松地全面更新
* 本地化支持 —— 当网站被翻译成其他语言时,标准化列表可以被本地化,因此区域可以用查看者的语言显示
* 更好的搜索 —— 例如,搜索美国东海岸的人可以匹配此个人资料,因为区域列表可以编码华盛顿位于东海岸的事实(这从字符串 `"Washington, DC"` 中并不明显)
@ -198,7 +198,7 @@ db.users.aggregate([
#### 社交网络案例研究中的反规范化 {#denormalization-in-the-social-networking-case-study}
在 ["案例研究:社交网络页时间线"](/ch2#sec_introduction_twitter) 中,我们比较了规范化表示([图 2-1](/ch2#fig_twitter_relational))和反规范化表示(预计算的物化时间线):这里,`posts` 和 `follows` 之间的连接太昂贵了,物化时间线是该连接结果的缓存。将新帖子插入关注者时间线的扇出过程是我们保持反规范化表示一致的方式。
在 ["案例研究:社交网络页时间线"](/ch2#sec_introduction_twitter) 中,我们比较了规范化表示([图 2-1](/ch2#fig_twitter_relational))和反规范化表示(预计算的物化时间线):这里,`posts` 和 `follows` 之间的连接太昂贵了,物化时间线是该连接结果的缓存。将新帖子插入关注者时间线的扇出过程是我们保持反规范化表示一致的方式。
然而X前 Twitter的物化时间线实现实际上并不存储每个帖子的实际文本每个条目实际上只存储帖子 ID、发布者的用户 ID以及一些额外的信息来识别转发和回复 [^11]。换句话说,它是(大约)以下查询的预计算结果:
@ -529,7 +529,7 @@ RETURN person.name
答案是肯定的,但有一些困难。你在图查询中遍历的每条边实际上都是与 `edges` 表的连接。在关系数据库中,你通常事先知道查询中需要哪些连接。另一方面,在图查询中,你可能需要遍历可变数量的边才能找到你要查找的顶点 —— 也就是说,连接的数量不是预先固定的。
在我们的示例中,这发生在 Cypher 查询中的 `() -[:WITHIN*0..]-> ()` 模式中。一个人的 `LIVES_IN` 边可能指向任何类型的位置:街道、城市、地区、地区、州等。一个城市可能在一个地区 `WITHIN`,一个地区在一个州 `WITHIN`,一个州在一个国家 `WITHIN`,等等。`LIVES_IN` 边可能直接指向你要查找的位置顶点,或者它可能在位置层次结构中相距几个级别。
在我们的示例中,这发生在 Cypher 查询中的 `() -[:WITHIN*0..]-> ()` 模式中。一个人的 `LIVES_IN` 边可能指向任何类型的位置:街道、城市、district、地区region、州等。一个城市可能在`WITHIN`)某个地区,该地区在(`WITHIN`)某个州,该州在(`WITHIN`)某个国家,等等。`LIVES_IN` 边可能直接指向你要查找的位置顶点,或者它可能在位置层次结构中相距几个级别。
在 Cypher 中,`:WITHIN*0..` 非常简洁地表达了这个事实:它意味着"跟随 `WITHIN` 边,零次或多次"。它就像正则表达式中的 `*` 运算符。