最初,使用文件作為數(shù)據(jù)庫,將數(shù)據(jù)轉(zhuǎn)換為JSON大型對象。后來決定放慢速度,遷移數(shù)據(jù)庫。在此過程中出現(xiàn)了一些問題和障礙,但最終我們成功地完成了這一不可能的數(shù)據(jù)庫遷移。(大衛(wèi)亞設(shè),Northern Exposure)。
Brad 加入一家初創(chuàng)公司
大約一年前,當(dāng)我剛加入 Tailscale()時,我問 Crawshaw()的第一件事是:“嗯……你們使用的是什么數(shù)據(jù)庫呢?MySQL、PostgreSQL、SQLite?“我知道他喜歡 SQLite。
“一個文本文件,”他回答道。
“嗯?”
“是的,我們將一個大型 JSON 對象寫入一個文本文件。”
“怎么寫?什么時候?qū)懀繉懯裁???/p>
“嗯,無論什么時候,只要有什么東西一變,我們都會在我們的單進(jìn)程中獲取一個鎖,然后重寫這個文件!”他高興地笑著說。
聽起來很瘋狂。其實(shí)就是很瘋狂。的確,它很容易測試,但是,不能擴(kuò)展。這些,我們都知道。但是,當(dāng)時它還行得通。
后來,它不行了。
即使使用高速 NVMe 驅(qū)動器,并且將數(shù)據(jù)庫分成兩部分(重要數(shù)據(jù)和 tmpfs 上的可能丟失的臨時數(shù)據(jù)),有些事情也會變得越來越慢。我們知道這一天終將到來。文件達(dá)到了 150MB 的峰值,我們正在以磁盤 I/O 允許的最快速度寫入它。這已經(jīng)到了極限。
那么,遷移到 MySQL 或 PostgreSQL,如何?或者是 SQLite?
不,Crawshaw 另有主意。
聊聊大衛(wèi)的背景故事
Tailscale 的協(xié)調(diào)服務(wù)器(我們的“控制面板”)以控制 CONTROL()聞名遐邇。它目前是單個 VM 上的單個 Go 進(jìn)程。它最早的原型使用的是 SQLite。我們最初的設(shè)計(jì)與最終的設(shè)計(jì)有非常大的差異,包括同步到客戶端機(jī)器上的配置數(shù)據(jù)庫,以及所有我們最終不再需要的其他概念。在這個過程中,我們每周都要對 SQL 數(shù)據(jù)模型進(jìn)行非常大規(guī)模的重組,這需要大量的鍵盤輸入工作。SQL 已經(jīng)得到了廣泛使用,它持久、有效,但將其引入到任何編程語言中幾乎都需要做大量的粘合。(通常大家都試圖用 ORM 來避免這種情況,用令人生厭的大量魔法字和效率損失來取代那些同樣令人生厭的鍵盤輸入工作。)
一天,我厭倦了重構(gòu),就把它徹底丟在一邊,建了一個內(nèi)存數(shù)據(jù)模型進(jìn)行實(shí)驗(yàn)。這樣,迭代速度更快了。幾周后,一位客戶想要試用一下。我還沒有做好提交數(shù)據(jù)模型和用 SQL 來完成它的準(zhǔn)備,所以我選擇了一條捷徑:將持有所有數(shù)據(jù)的對象包裝在一個 中。所有訪問都要經(jīng)過它,在編輯時,將整個結(jié)構(gòu)傳遞給 j,然后寫入磁盤。這就是我們用大約 20 行 Go 實(shí)現(xiàn)的數(shù)據(jù)模型持久層。
我們本來計(jì)劃要遷移為別的語言的,但忙著忙著就給忘記了。
JSONMutexDB 的后面是什么?
下一步顯然是遷移到 SQL。我最喜歡的仍然是 SQLite,但是我不能說服自己把一個快速增長的服務(wù)遷移到它上面。它當(dāng)然是可行,尤其是我們的控制面板的設(shè)計(jì)并不需要典型的 web 服務(wù)高可用性:短時間停機(jī)的無非就是使新節(jié)點(diǎn)無法登錄而已,正在工作的網(wǎng)絡(luò)可以保持正常工作。
其后是 MySQL(或 PostgreSQL)。我對 1998 年以后的 MySQL 不是特別熟悉,但我確信它是可以的。不過,開源數(shù)據(jù)庫的 HA 情況有些令人驚訝:您可以使用傳統(tǒng)的滯后副本,也可以提交到具有令人非常驚訝的事務(wù)語義的無主副本集群。我對試圖在這些語義之上設(shè)計(jì)一個穩(wěn)定的 API 或良好的網(wǎng)絡(luò)圖計(jì)算并不感興趣。CockroachDB() 曾經(jīng)看起來很有前途,而實(shí)際上現(xiàn)在仍然很有前途!但對于一個數(shù)據(jù)庫來說,它還是相對比較新的,我不太放心把一些特性附著在一個新的 DBMS 上,因?yàn)槿绻覀冃枰獙⑦@些特性中遷移出來時,可能很難做得到。
讓我們的控制服務(wù)器依賴于 MySQL 或 PostgreSQL 還意味著我們對控制服務(wù)器的測試將變得緩慢和丑陋。Brad 與 Perkeep()曾就此有過爭論,他之前寫過,它的確可行,但我們不想要求未來的員工都這么做。它需要在你的機(jī)器上部署 Docker 環(huán)境,速度不是特別快。
后來有一天我們看到一份 Jepsen 寫的 etcd 報告()。這篇報告不似 Jepsen 之前那種滿篇吐槽的風(fēng)格,里面還指出了一些 etcd()的優(yōu)點(diǎn)。結(jié)合 Dave Anderson()的一些正面體驗(yàn),我們開始考慮是否可以直接使用 etcd。由于是它用 Go 編寫的,我們可以直接將它連接到我們的測試中,并直接使用它。無需 Docker,無需 mock,就可以測試我們在生產(chǎn)環(huán)境中實(shí)際使用的東西。
事實(shí)上,我們寫入到磁盤的核心數(shù)據(jù)模型嚴(yán)格遵循了以下模式:
type AllTheData struct { BigLock Somethings map[string]Something Widgets map[string]Widget Gadgets map[string]Gadget }
復(fù)制代碼
這很好地映射到了 KV-store 上。因此,我們將 etcd 作為一個“最小可行的數(shù)據(jù)庫”。它做了我們當(dāng)前所需要的最關(guān)鍵的事情,那就是 1)將 BigLock 拆解成更類似于 的東西。2)減少 I/O,只寫改變的數(shù)據(jù),而不是整體都寫。
(我們會謹(jǐn)慎避免使用任何難以映射到 CockroachDB 的 etcd 特性。)
這樣做的缺點(diǎn)是,etcd 雖然在 Kubernetes 中很流行,但是數(shù)據(jù)庫系統(tǒng)的用戶相對較少。作為一家公司,Tailscale 正致力于在其上打造一款創(chuàng)新代幣()。但這款數(shù)據(jù)庫從概念上講非常小,以致于我們不必把它當(dāng)作一個黑盒。當(dāng)我們在 etcd 3.4 中遇到一個異常緩慢的主鍵分頁的極端情況時,我能夠閱讀它的源代碼并在一個小時內(nèi)編寫出一個修復(fù)程序。(后來,我發(fā)現(xiàn) etcd 的下一個版本也已經(jīng)做了一樣的修復(fù)(),所以我們將其反向移植了過來。)
我們的 etcd 客戶端包裝器
我們用于 etcd 的客戶端是開放源碼的,網(wǎng)址是 gi(https://gi)。它圍繞了兩個概念:1)DB 中的總數(shù)據(jù)量足夠小,可以放入服務(wù)器的內(nèi)存中;2)讀比寫更常見。鑒于這一點(diǎn),我們希望降低讀取成本。
我們的方法是對 etcd 注冊一個監(jiān)控。每次更改都被發(fā)送到這個客戶端,這個客戶端在一個 后面維護(hù)一個龐大的緩存 map[string] interface{}。當(dāng)你創(chuàng)建一個 Tx 并且做一次 Get 時,這個值從這個緩存中讀出(這個緩存可能在 etcd 之后,但是通過跟蹤 modrev 來保持事務(wù)一致性:即一個全局遞增的 ID, etcd 使用它來界定鍵-值對的修訂)。為了避免緩存中的混疊錯誤,我們將對象復(fù)制出來,但是通過對緩存中的對象實(shí)現(xiàn)更有效的克隆調(diào)用,避免了每次 Get 時的 JSON 解碼。
最終結(jié)果是,從 etcd 獲取一個值不需要任何網(wǎng)絡(luò)流量。
當(dāng)我在設(shè)計(jì)一個包時,我感受到了編寫 Go 時它的類型系統(tǒng)的局限性,這樣的感受并不多,它是其中之一。如果我使用的是一種具有各種花哨功能的語言,那么我可以在離開緩存的對象上放置某種 const 限定符,從而避免對內(nèi)存進(jìn)行克隆。即便如此,在我們的服務(wù)器上執(zhí)行的性能分析卻表明,復(fù)制并不是一個性能問題,所以該例可能說明,我實(shí)際上并不需要那些心心念念的更復(fù)雜的類型系統(tǒng)。通常情況下,假設(shè)很可能并不正確,性能分析才更具啟發(fā)意義。
一個障礙:索引
選擇最小可行的“nosql”的最大問題是缺乏每個標(biāo)準(zhǔn) SQL DBMS 所提供的出色的索引系統(tǒng)。我們要么在 etcd 中存儲索引,要么在客戶端的內(nèi)存中管理索引。
我們使用 JSONMutexDB 在內(nèi)存中生成它們,因?yàn)楦臄?shù)據(jù)模型要容易得多。使用 etcd 的一個簡單做法是將它們寫入數(shù)據(jù)庫,但這將產(chǎn)生非常復(fù)雜的數(shù)據(jù)模型。不幸的是,如果我們想要同時運(yùn)行多個控制進(jìn)程以實(shí)現(xiàn)高可用性和更好的發(fā)布管理,就意味著我們不再只有一個管理數(shù)據(jù)的進(jìn)程,因此我們的索引需要支持事務(wù)(以及回滾)。因此,我們投入了大約兩到三周的工程時間來設(shè)計(jì)事務(wù)一致的內(nèi)存索引。這一點(diǎn)描述起來有些復(fù)雜,所以筆者將在后續(xù)的博客文章中專題解釋,敬請期待。
遷移
而遷徙本身卻沒什么特別值得注意的,這其實(shí)件好事。我們這兩個系統(tǒng)并行運(yùn)行了一段時間,并在某個時間點(diǎn)停止了舊系統(tǒng)的使用。最令人興奮的是,當(dāng)我們關(guān)閉 JSON 寫入時,提交延遲降低了很多。在管理面板中編輯網(wǎng)絡(luò)時這一點(diǎn)尤為明顯。我們有漂亮的 Grafana 圖表,在切換之前我們就調(diào)整了 Prometheus 配置以保持更多的歷史紀(jì)錄。不論在哪種情況下,寫操作都能從幾乎一秒(有時更糟!)的時間縮短到毫秒級。剛開始的時候,寫入并不是我們的第二目標(biāo)。永遠(yuǎn)不要低估“臨時”起意會產(chǎn)生多么長久的影響!
未來
在這項(xiàng)工作中,除了確保 Tailscale 控制面板可以在可預(yù)見的未來擴(kuò)展外,最令人興奮的事情是我們發(fā)布過程的改進(jìn)。我們可以輕松地將多個控制面板實(shí)例附加到一個一致的數(shù)據(jù)庫中,這意味著我們可以切換為藍(lán)綠部署()。這將讓 Tailscale 的工程師們有信心去嘗試部署特性,因?yàn)樽兏茉斐傻淖畈罱Y(jié)果是有限的。我們的目標(biāo)是將開發(fā)速度保持在接近 JSONMutexDB 早期的水平,當(dāng)時我們可以在不到一秒的時間內(nèi)重新編譯并在本地運(yùn)行,每天部署上 10 幾次。
1.《tx26c看這里!一次幾乎不可能的數(shù)據(jù)庫遷移》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點(diǎn),與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《tx26c看這里!一次幾乎不可能的數(shù)據(jù)庫遷移》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實(shí),對其原創(chuàng)性、真實(shí)性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/lishi/1968715.html