17條避坑指南:一份來自谷歌的數據庫經驗貼

程序員的成長之路

互聯網/程序員/技術/資料共享 

關注

閱讀本文大概需要 18.2 分鐘。

來自:https://medium.com/@rakyll/things-i-wished-more-developers-knew-about-databases-2d0178464f78

「ACID 有很多含義」、「每個數據庫具有不同的一致性和隔離性」、「嵌套事務可能有害」…… 這些都是谷歌雲工程師 Jaana Dogan 曾經踩過的坑。在這篇文章中,她總結了 17 條這樣的經驗教訓,希望爲剛接觸數據庫的小白提供一份避坑指南。目前,這一指南已在 medium 上收穫了 5k+ 贊。

絕大多數計算機系統都具有某種狀態,而且很可能還依賴於一個存儲系統。我對數據庫的知識也是逐漸累積起來的,但在累積的過程中,我們的設計錯誤曾導致過數據丟失和中斷問題。

在嚴重依賴數據的系統中,數據庫是系統設計的目標和權衡的核心。


儘管我們不可能忽略數據庫的工作方式,但應用開發者可以預見或實際經歷的問題往往都只是冰山一角。在本系列文章中,我將分享一些我專門找到的對不擅長數據庫領域的開發者很有用的見解:

  1. 如果 99.999% 的時間裏網絡沒有問題,那你確實很幸運。

  2. ACID 有很多含義。

  3. 每個數據庫具有不同的一致性和隔離性。

  4. 當你無法搞定鎖時,就使用樂觀鎖。

  5. 除了髒讀和數據丟失,還存在其它異常。

  6. 我的數據庫和我在排序方面並不總是一致的。

  7. 應用層面的分片可以存在於該應用之外。

  8. AUTOINCREMENT 可能有害。

  9. 過時的數據可能有用而且是無鎖的。

  10. 任何時鐘源之間都會發生時鐘偏移。

  11. 延遲(latency)有很多含義。

  12. 評估每個事務的性能需求。

  13. 嵌套事務可能有害。

  14. 事務不應維持應用狀態。

  15. 查詢計劃器能提供有關數據庫的一切信息。

  16. 在線遷移可能很複雜,但卻可以實現。

  17. 數據庫顯著增長時會引入不可預測性。

如果 99.999% 的時間裏網絡沒有問題,那你確實很幸運。

人們至今仍在論辯如今的網絡連接技術有多可靠以及由於網絡中斷而導致系統停機的情況有多頻繁。可行的研究很有限,而且這些研究往往由擁有使用定製硬件的專用網絡的大型組織以及特定人員所主導。

憑藉 99.999% 的服務可用性,谷歌僅把 Spanner(谷歌散佈在全球的數據庫)出現的問題中的 7.6% 歸因於網絡連接,儘管該公司稱其專用網絡是這種可用性背後的核心原因。

Bailis 和 Kingsbury 2014 年的調查向 Peter Deutsch 於 1994 年提出的分佈式計算的謬誤(Fallacies of Distributed Computing)之一發起了挑戰。網絡真的可靠嗎?

我們並沒有來自巨頭企業之外的調查結果或在公共互聯網上的調查結果。主要電信提供商也沒有足夠的數據,讓人無法瞭解他們的客戶端遇到的問題有多少可追溯到網絡問題。

我們常會遇到大型雲提供商的網絡堆棧中斷的情況,這可能導致部分互聯網下線幾個小時,但只有影響力很高的事件纔會影響到大量可見客戶端。

網絡中斷可能影響範圍很大,但不是每個案例都會產生嚴重影響。雲客戶端也不一定需要詳細瞭解他們遇到的問題。

當出現中斷時,不可能識別出這是否是由提供商導致的網絡錯誤。對他們而言,第三方服務都是黑箱。如果不是主要提供商,是不可能估計出影響有多大的。

對比一下主要玩家公佈的系統報告,如果可能導致中斷的潛在問題中僅有一小部分是網絡問題,那麼可以說你是相當幸運的。網絡連接仍面臨着許多常規問題,比如硬件故障、拓撲變化、管理配置更改和電源故障。但我最近看到一個新聞,發現鯊魚撕咬也是一個現實存在的問題——已經出現過鯊魚撕咬海底光纜的案例。

ACID 有很多含義

ACID 表示原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)。

ACID 是數據庫事務(database transaction)需要向用戶確保有效的屬性——即使在出現崩潰、錯誤、硬件故障等情況時也需要保證這些屬性。

如果沒有 ACID 或類似的保證,應用開發者將難以區分他們自己的職責與數據庫能夠提供的保證。大多數關係事務數據庫都會盡力符合 ACID 指標,但 NoSQL 運動等新方法催生了許多沒有 ACID 事務的數據庫,這些這些事務的實現成本比較高。

在我剛進入這一行業時,我們的技術主管當時討論過 ACID 是否已是一個過時的概念。可以合理地說,ACID 可視爲一種定義寬鬆的描述,而不是嚴格的實現標準。

現如今,我發現 ACID 最有用的地方是它提供了問題的類別(以及可能的解決方案的類別)。

並非每個數據庫都符合 ACID,而在符合 ACID 的數據庫中,ACID 的解讀方式也可能不同。爲什麼 ACID 會有不同的實現方式?一個原因是在實現 ACID 時,需要權衡的東西太多了。

數據庫在做廣告宣傳時可能會說自己符合 ACID,但在許多邊緣案例上仍可能有不同的解釋或在處理不太可能發生的事件時的方法不同。

爲了適當地理解故障模式和設計權衡,開發者至少可以在高層面上了解數據庫實現各項功能的方式。

一個衆所周知的爭議問題是 MongoDB 在第 4 版後有多符合 ACID。MongoDB 很長時間都不支持日誌功能,儘管默認情況下其也不會更頻繁地(每 60 秒)將數據文件提交到磁盤。

考慮以下情況,一個應用執行兩次寫入(w1 和 w2)。MongoDB 能夠在第一次寫入時保留更改,但無法在寫入 w2 時保留這項更改,因爲這會出現由硬件故障所致的崩潰。

MongoDB 在寫入物理磁盤前崩潰而導致數據丟失的示意圖

將數據提交到磁盤的過程具有較高的成本,而通過避免提交,它們可以宣稱在寫入方面表現出色,但這樣就犧牲了持久性。

如今,MongoDB 已經有了日誌功能,但髒寫(dirty writes)仍然可能影響數據的持久性,因爲它們默認是每 100 ms 提交一次。

對於日誌及這些日誌所表示的更改的持久性,也可能會出現同樣的情況,不過這種風險要小得多。

每個數據庫具有不同的一致性和隔離性

在 ACID 屬性中,一致性和隔離性的不同實現細節的範圍是最廣的,因爲其涉及的權衡因素更多。

一致性和隔離性都是實現成本較高的屬性。

爲了保持數據一致,它們需要協調而且正得到越來越多的討論。當必須以水平方式擴展數據中心時(尤其是對於不同的地區),這些問題會變得更加困難。

因爲此時可用性會下降且網絡分區會越來越普遍,這會導致很難實現高層面的一致性。

CAP 定理爲這一現象給出了更普適的解釋。

需要指出的是,即使有一些不一致性,一般應用也能處理,或者程序開發者對這一問題有足夠的認知,讓他們能爲該應用添加用於處理這一情況的邏輯,從而無需過於依賴他們的數據庫。

數據庫往往會提供多種不同的隔離層,這樣應用開發者就可以基於自己的權衡策略來選擇最具成本效益的。

當隔離更弱時,速度可能更快,但也可能導致數據競爭(data race)。

當隔離更強時,不會出現某些潛在的數據競爭,但速度會更慢,而且還可能出現爭用(contention)情況,這甚至可能將數據庫的速度拖慢到中斷的程度。

現有併發模型及它們之間的關係概況

SQL 標準僅定義了 4 種隔離層級,但理論上和實踐中的層級都更多。jepson.io 很好地總結了現有併發模型的情況:https://jepsen.io/consistency。

舉個例子,谷歌的 Spanner 使用了時鐘同步來保證外部可串行化,即使這是一種更嚴格的隔離層,但標準隔離層中卻並沒有這樣的定義。

SQL 標準中提及的隔離層級包括:

  • 可串行化(最嚴格,成本最高):可串行化執行(serializable execution)得到的效果與這些事務的某些序列執行的效果一樣。序列執行(serial execution)是指在每個事務執行完成之後再執行下一個事務。關於可串行化執行,需要注意的一點是:由於解釋的差異性,它往往被實現爲快照隔離(snapshot isolation),比如 Oracle,而快照隔離並不在 SQL 標準中。

  • 可重複的讀取:當前事務中未提交的讀取對當前事務來說是可見的,但其它事務做出的改變(比如新插入的行)不是可見的。

  • 已提交的讀取:未提交的讀取對事務來說不可見。只有已提交的寫入是可見的,但可能出現幻象讀取(phantom read)。如果另一個事務插入和提交了新的行,則當前事務在查詢時可以看到它們。

  • 未提交的讀取(最不嚴格,成本最低):允許髒讀(dirty read),事務可以看到其它事務做出的尚未提交的更改。在實踐中,這個層級可用於返回近似聚合結果,比如對一個表格的 COUNT(*) 查詢。

可串行化層級出現數據競爭的情況最少,但成本也最高,而且會讓系統出現最多爭用。其它隔離層級的成本更低一些,但也更可能出現數據競爭問題。某些數據庫允許自行設置隔離層級,某些數據庫則在這方面更爲固執一點,並不一定支持所有這些層級。

而就算數據庫宣稱自己支持這些隔離層級,但只要仔細檢查一下它們的行爲,就可以瞭解這些數據庫實際究竟是怎麼做的。

每個數據庫在不同隔離層級上的併發異常概況

Martin Kleppmann 的 hermitage 項目總結了不同的併發異常,並說明了一個數據庫在不同的隔離層級上能否處理這樣的異常:https://github.com/ept/hermitage 。Kleppmann 的研究表明數據庫設計者會以不同的方式解釋隔離層級。

當你無法搞定鎖時,就使用樂觀鎖

鎖的成本非常高,不僅是因爲它們會爲數據庫引入更多爭用,而且還需要你的應用服務器與數據庫之間存在一致的連接。網絡分區可能會更顯著地影響排它鎖(exclusive lock),這會導致難以識別和解決的死鎖(deadlock)。如果有些案例無法很好地使用排它鎖,可以選擇樂觀鎖(optimistic locking)。

樂觀鎖這種方法是指當讀取某行時會記錄版本號、上次修改的時間戳或其校驗和(checksum)。然後你可以在更改記錄之前檢查原子方面並無修改的版本。

UPDATE productsSET name = 'Telegraph receiver', version = 2WHERE id = 1 AND version = 1

如果另一項更新之前已經修改了這一行,那麼對 products 表的更新將影響 0 行。如果沒有更早的更新,則它會影響 1 行,則我們可以說更新成功了。

除了髒讀和數據丟失,還存在其它異常

當我們在探討數據一致性時,我們主要關注的是可能導致髒讀和數據丟失的競爭問題。但數據方面的異常並不止這兩種。

舉個例子,還有一種異常是寫偏序(write skew)。寫偏序更難以識別認定,因爲我們不會主動地去查找這個問題。導致寫偏序的原因不是發生在寫入上的髒讀或數據丟失,而是因爲數據上的邏輯約束損壞。

比如,假設一個監控應用需要一個人類操作員始終處於待命狀態。

BEGIN tx1;                      BEGIN tx2;SELECT COUNT(*)FROM operatorsWHERE oncall = true;0                               SELECT COUNT(*)                                FROM operators                                WHERE oncall = TRUE;                                0UPDATE operators                UPDATE operatorsSET oncall = TRUE               SET oncall = TRUEWHERE userId = 4;               WHERE userId = 2;COMMIT tx1;                     COMMIT tx2;

在上面的情況中,如果這些事務中有兩個成功提交,就會出現寫偏序。即使此時沒有出現髒讀或數據丟失,數據也失去了完整性,因爲其指定了兩個待命的人。

可串行化隔離、模式設計或數據庫約束有助於消除寫偏序。開發者需要在開發過程中識別這樣的異常,以避免生產過程中出現數據異常。話雖如此,識別代碼庫中的寫偏序卻非常之難。

尤其是在大型系統中,如果負責基於同一表格構建功能的不同團隊之間沒有溝通且沒有互相檢查他們存取數據的方式,那麼就會出現這種問題。

我的數據庫和我在排序方面並不總是一致的

數據庫提供的一大核心能力是排序保證,但排序結果可能會出乎應用開發者的預料。數據庫查閱事務的順序就是它們接收這些事務的順序,而不是開發者查看它們時的程序設計順序。

事務執行的順序難以預測,尤其是在高容量的併發系統中。

在開發時,尤其是在使用非阻塞軟件庫進行開發時,較差的樣式和可讀性可能會導致用戶認爲事務是按順序執行的,即使它們可能以任何順序抵達數據庫。

下面的程序看起來像是 T1 和 T2 將按順序調用,但如果這些函數是非阻塞的,則它們將立即帶着 promise 返回,調用的順序將取決於它們在數據庫中接收到的時間。

result1 = T1() // results are actually promisesresult2 = T2()

如果需要原子性(以便完全提交或放棄所有操作)且序列很重要,則 T1 和 T2 中的操作應該運行在單個數據庫事務中。

應用層面的分片可以存在於該應用之外

分片(Sharding)是一種水平劃分數據庫的方法。有的數據庫可以自動地對數據進行水平分區,有的數據庫則不支持這種功能或做得不好。當數據架構師 / 開發者可以預測訪問數據的方式時,他們可能會在用戶區域創建水平分區,而不是將這項工作委託給他們的數據庫。

這種方式稱爲應用級分片(application-level sharding)。

應用級分片這個名稱往往會給人帶來一種錯誤印象,讓人以爲這種分片應該存在於應用服務之中。

分片功能可以實現爲數據庫的前面一層。取決於數據增長和架構迭代情況,分片的要求可能會變得非常複雜。如果能在無需重新部署應用服務器的前提下對某些策略進行迭代,則會大有裨益。

應用服務器與分片服務分離的架構示例

如果將分片作爲一個單獨的服務,你就能更好地在不重新部署應用服務器的前提下迭代分片策略。Vitess 就是應用級分片系統的一個例子。Vitess 爲 MySQL 提供了水平分片,並允許客戶端通過 MySQL 協議連接它;Vitess 會將數據分片到多個互相之間無聯繫的 MySQL 節點上。

AUTOINCREMENT 可能有害

AUTOINCREMENT(自動遞增)是生成主鍵(primary key)的一種常用方法。數據庫被用作 ID 生成器以及數據庫中有 ID 生成指定表格的情況其實並不少見。但使用自動遞增生成主鍵的方式其實並不理想,原因有幾點:

  • 在分佈式數據庫系統中,自動遞增很困難。爲了生成 ID,需要使用全局鎖才行。而如果你可以生成 UUID,那麼就不需要數據庫節點之間有任何合作。使用鎖的自動遞增可能導致爭用,並可能導致分佈式情況中插入性能顯著下降。MySQL 等一些數據庫可能需要特定的配置和更多的注意才能正確地完成 master-master 複製。這樣的配置容易混亂而且可能導致寫入中斷。

  • 某些數據庫有基於主鍵的分區算法。按順序排布的 ID 可能導致無法預測的熱點,從而使得某些分區過於繁忙,另一些則一直空閒。

  • 訪問數據庫中某行的最快方式是通過主鍵。如果你有更好的標識記錄的方式,那麼順序 ID 可能會讓表中最顯著的列成爲無意義的值。請儘可能地選擇全局獨一的自然主鍵(比如用戶名)。

請考慮自動遞增 ID 與 UUID 對索引、分區和分片的影響,然後再決定哪種方式對你而言最好。

過時的數據可能有用而且是無鎖的

多版本併發控制(MVCC)能實現我們上面簡要討論過的很多一致性。Postgres 和 Spanner 等一些數據庫使用 MVCC 以讓每個事務都能看到一個快照,即該數據庫的一個更舊版本。

參照快照的事務仍然可以串行化以實現一致性。當讀取一箇舊快照時,實際讀取的是過時的數據。

但即使讀取的是稍微過時的數據,也會很有用處,比如當在生成數據分析結果或計算近似聚合值時。

讀取過時數據的第一大優勢是延遲(尤其是當你的數據庫分佈在不同的地區時)。

MVCC 數據庫的第二大優勢是其允許只讀事務是無鎖的。在需要大量讀取的應用中,一個優勢是用過時的數據也是可行的。

即便太平洋另一端有某個數據的最新版本,但也可以從本地讀取 5 秒前的過時副本。

數據庫會自動清除舊版本,而在某些情況下,數據庫也支持按需清理。舉個例子,Postgres 允許用戶按需執行 VACUUM 操作或每隔一段時間自動執行 VACUUM,而 Spanner 則是通過運行一個垃圾收集器來丟棄時間超過 1 小時的版本。

任何時鐘源之間都會發生時鐘偏移

在計算領域,隱藏得最好的祕密是所有時間 API 都在說謊。

我們的機器並不能準確地知道當前的時間是多少。我們的計算機全都包含一個用以產生計時信號的石英晶體。

但石英晶體並不能準確計時和計算時間偏移量,要麼比實際時鐘快,要麼就更慢。

一天的偏移量甚至可達 20 秒。爲了準確,我們的計算機時間必須不時地與實際時間保持同步。

NTP 服務器可用於同步,但同步本身卻可能由於網絡的原因而出現延遲。與同一數據中心的 NTP 服務器同步況且需要時間,與公共 NTP 服務器同步更是可能產生更大的偏移。

原子鐘和 GPS 時鐘是更好的確定當前時間的信息源,但它們的部署成本更高,而且需要複雜的設置,不可能在每臺機器上都安裝。

由於存在這些限制條件,數據中心通常使用的是多層方法。即在使用原子鐘和 / 或 GPS 時鐘提供準確計時的同時,再通過輔助服務器將時間信息廣播給其它機器。

這意味着所有機器都與實際的當前時間存在一定程度的偏移。

不僅如此,應用和數據庫往往搭建在不同的機器中,甚至還可能位於不同的數據中心。因此,不僅分散在不同機器上的不同數據庫節點之間無法統一時間,應用服務器時鐘和數據庫節點時鐘也無法統一。

谷歌的 TrueTime 爲此採用了一種不同的方法。大多數人認爲谷歌在時鐘上的成果可以歸功於他們使用了原子鐘和 GPS 時鐘,但那其實僅僅是部分原因。TrueTime 實際上是這樣工作的:

  • TrueTime 使用了兩個不同的時間信號源:GPS 時鐘和原子鐘。這些時鐘存在不同的故障模式,因此同時使用兩者可以提升可靠性。

  • TrueTime 的 API 並不是常規型的。它會以區間的形式返回時間。因此實際時間事實上處於這個時間區間的上界和下界之間。因此,谷歌的分佈式數據庫 Spanner 就可以等到它確定了當前時間超過了特定時間之後才執行事務。這種方法會給系統帶來一些延遲,尤其是當主機通告的不確定性很高時;但這種方法能保證正確性,即使數據庫分佈在全球也是如此。

使用 TrueTime 的 Spanner 組件,其中 TT.now() 會返回一個時間區間,這樣 Spanner 就可以插入睡眠時間以確保當前時間已超過特定時間戳。

噹噹前時間的置信度下降時,Spanner 執行操作可能會耗費更多時間。因此,即使不可能獲得精準的時鐘,保證時鐘的置信度對性能而言也是非常重要的。

延遲有很多含義

如果房間裏有 10 個人,你問他們「延遲(latency)」是什麼意思,你可能會得到 10 個不同的答案。

在數據庫中,延遲通常是指數據庫延遲,而非客戶端所感知到的延遲。客戶端感知到的延遲包含數據庫延遲和網絡延遲。在調試不斷惡化的問題時,分辨客戶端延遲和數據庫延遲是非常重要的。

在收集和展示指標時,往往需要同時包含這兩種延遲。

評估每個事務的性能需求

有時候,數據庫會將它們的讀寫吞吐量和延遲作爲性能優勢的賣點來進行宣傳。儘管這能在評估數據庫的性能時從較高層面上展現主要的限制因素,但爲了更全面地進行評估,需要單獨分開評估各個關鍵操作的性能,比如每次查詢或每個事務的執行性能。示例:

  • 爲具有給定約束條件的包含 5000 萬行的表格 X 插入新的一行並填充相關表格時的吞吐量和延遲。

  • 當平均好友數爲 500 時,查詢一個用戶的好友的好友時的延遲。

  • 當用戶訂閱了 500 個賬號且每個小時有 X 項新輸入時,檢索用戶時間線前 100 條記錄時的延遲。

評估和實驗可能包含這樣的關鍵性案例,直到你有信心你的數據庫能夠滿足你的性能需求。另一個類似的經驗法則是在收集延遲指標和設置 SLO 時考慮這種故障情況。

在收集每個操作的指標時要注意高基數。如果你需要高基數的調試數據,請使用日誌或分佈式的跟蹤方法。如果你想了解延遲調試方法,請參閱《Want to Debug Latency?》(https://medium.com/observability/want-to-debug-latency-7aa48ecbe8f7)。

嵌套事務可能有害

並非每個數據庫都支持嵌套事務(nested transactions),但如果支持,那麼嵌套事務可能導致出人意料的程序設計錯誤,而且這種錯誤往往不易識別,直到出現了明顯異常才能看清。

如果你想要避免嵌套事務,則可以使用客戶端軟件庫來檢測和避免嵌套事務。如果你不能避免嵌套事務,則必須注意不要出現意料之外的情況,即當提交的事務因爲子事務而被意外拋棄時。

如果將事務封裝在不同的層中,可能會出現出人意料的嵌套事務案例,而從可讀性角度來看,其意圖可能將變得難以理解。看看下面的程序:

with newTransaction():    Accounts.create("609-543-222")    with newTransaction():        Accounts.create("775-988-322")        throw Rollback();

以上代碼的結果是什麼?是兩個事務都會回滾還是僅回滾內部那個事務?如果我們當時依賴的多層軟件庫將該事務的創建過程封裝起來不爲我們所見,我們還能識別和改進這樣的案例嗎?

假設一個具有多項操作(比如 newAccount)的數據層已經在它們自己的事務中實現了。當你用更高層的業務邏輯(它們運行在自己的事務中)運行它們時,會發生什麼?隔離性和一致性又會怎樣?

function newAccount(id string) {   with newTransaction():       Accounts.create(id)}

與其耗費資源去解決這些仍待解決的問題,還不如不使用嵌套事務。即使不創建它們自己的事務,你的數據層仍可以實現高層操作。然後,業務邏輯會啓動事務,在事務上運行操作,提交或中止。

function newAccount(id string) {    Accounts.create(id)}// In main application:with newTransaction():    // Read some data from database for configuration.    // Generate an ID from the ID service.    Accounts.create(id)    Uploads.create(id) // create upload queue for the user.
   

事務不應維持應用狀態

應用開發者可能會想在事務中使用應用狀態來更新特定的值或調整查詢參數。這時所要考慮的一個關鍵事項是選擇合適的範圍。客戶端在遇到網絡問題時往往會重試事務。如果一個事務依賴於在其它地方會變化的狀態,那麼其可能根據該問題中數據競爭的可能性選擇錯誤的值。事務應注意應用中的數據競爭。

var seq int64with newTransaction():     newSeq := atomic.Increment(&seq)     Entries.query(newSeq)     // Other operations...

上面的事務不管最終結果究竟如何,在每次運行時都會增加序列號。如果因爲網絡問題而導致提交失敗,則在第二次重試時會使用不同的序列號進行查詢。

查詢計劃器能提供有關數據庫的一切信息

查詢計劃器(query planner)決定了查詢在數據庫中的執行方式。它們還會在運行之前分析和優化這些查詢。計劃器僅能基於其擁有的信號提供某些可能的估計。如何確定找到以下查詢的結果的方法:

SELECT * FROM articles where author = "rakyll" order by title;

檢索結果的方法有兩種:

  • 全表掃描:我們可以遍歷表中的每一項,然後返回作者名匹配的文章,然後再執行排序。

  • 索引掃描:我們可以使用索引來查找匹配的 ID,檢索這些行,再執行排序。

查詢計劃器的作用是確定哪種策略是最佳選擇。不過對於哪些可以預測,哪些可能導致糟糕的決策,查詢計劃器僅有有限的信號。

數據庫管理員(DBA)或開發者可使用它們來診斷和優化表現較差的查詢。

當數據庫升級時,如果新版本的數據庫出現了性能問題,那麼這個數據庫可以調節查詢計劃器並進行自我診斷。

慢查詢日誌、延遲問題或關於執行時間的統計信息等報告可用於確定需要優化的查詢。

查詢計劃器提供的某些指標可能具有較多噪聲,尤其是當估計延遲或 CPU 時間時。作爲對查詢計劃器的補充,跟蹤和執行路徑工具對診斷這些問題而言可能會更加有用,不過並非每個數據庫都會提供這樣的工具。

在線遷移可能很複雜,但卻可以實現

在線或實時遷移的意思是在不停機且不損害數據正確性的同時從一個數據庫遷移到另一個數據庫。如果是遷移到同樣的數據庫 / 引擎,在線遷移會更爲簡單;但如果是遷移到性能特性和組織結構要求不同的新數據庫,那情況會複雜得多。

在線遷移有多種模式,下面介紹其中一種:

  • 開始向兩個數據庫執行雙寫入(dual writes)。在這一階段,新數據庫還不包含所有數據,但將開始看到新數據。一旦這一步得到了保證,你就可以進入下一步了。

  • 讓讀取路徑可同時使用這兩個數據庫。

  • 主要使用新數據庫來進行讀取和寫入。

  • 停止向舊數據庫寫入,但繼續保持從舊數據庫讀取。此時,新數據庫仍未包含所有新數據,而在獲取舊記錄時,可能還需要回退至舊數據庫。

  • 這時候,舊數據庫處於只讀狀態。從舊數據庫取出新數據庫缺失的值對新數據庫進行回填。遷移完成後,所有的讀取和寫入路徑都將使用新數據庫,舊數據庫則從系統中移除。

如果你需要更具體的案例,可以看看 Stripe 的遵循這一模式的遷移策略:https://stripe.com/blog/online-migrations

數據庫顯著增長時會引入不可預測性

數據庫增長會讓你遭遇不可預測的擴展問題。

我們對自己數據庫的內部情況越瞭解,可能就越難預測它們的擴展情況,還有些事情是我們無法預測的。

在數據庫增大時,之前關於數據規模和網絡容量需求的假設和預期都將變得過時。這時候,爲了避免中斷,需要大規模地重寫組織結構、大規模地改進運營、解決容量問題、重新考慮部署方案或遷移到其它數據庫。

不要以爲了解你當前數據庫的內部情況就萬無一失了,規模擴大還會帶來新的未知。

無法預測的熱點、數據不平衡的分佈、意料之外的容量和硬件問題、不斷增長的流量和新的網絡分區都會讓你重新考慮你的數據庫、數據模型、部署模型和部署規模。

<END>

推薦閱讀:

絕了!Dataway讓SpringBoot不在需要Controller、Service、DAO、Mapper了

你見過哪些操蛋的代碼?切勿模仿!

5T技術資源大放送!包括但不限於:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公衆號內回覆「2048」,即可免費獲取!!

微信掃描二維碼,關注我的公衆號

寫留言

朕已閱 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章