Uber為何要放棄Postgres選擇遷移到MySQL,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)建站專注于企業(yè)成都營銷網站建設、網站重做改版、盧龍網站定制設計、自適應品牌網站建設、H5響應式網站、成都商城網站開發(fā)、集團公司官網建設、成都外貿網站制作、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為盧龍等各大城市提供網站開發(fā)制作服務。
Uber 的早期架構包含了一個用 Python 開發(fā)的單體后端應用程序,這個應用程序使用 Postgres 作為數(shù)據(jù)存儲。從那個時候開始,Uber 的架構已經發(fā)生了巨大變化,變成了微服務,并采用新的數(shù)據(jù)平臺模型。具體地說,之前使用 Postgres 的地方,現(xiàn)在改用 Schemaless,一種構建在 MySQL 之上的新型數(shù)據(jù)庫分片層。在本文中,我們將探討 Postgres 的一些缺點,并解釋為什么我們要在 MySQL 之上構建 Schemaless 和其他后端服務。
我們遭遇了 Postgres 的諸多限制:
低效的寫入操作;
低效的數(shù)據(jù)復制;
數(shù)據(jù)損壞問題;
糟糕的副本 MVCC 支持;
難以升級到新版本。
我們將通過分析 Postgres 的表和索引在磁盤上的表示方式來探究以上這些限制,并將其與 MySQL 的 InnoDB 存儲引擎進行比較。請注意,我們的分析主要是基于我們對較舊的 Postgres 9.2 版本系列的經驗。據(jù)我們所知,在本文中討論的內部架構在較新的 Postgres 發(fā)行版中并未發(fā)生顯著變化,并且至少自 Postgres 8.3 發(fā)行版(現(xiàn)在已近 10 歲)以來,9.2 版本的基本設計都沒有發(fā)生顯著變化。
磁盤表示
一個關系型數(shù)據(jù)庫必須能夠執(zhí)行一些關鍵任務:
提供插入、更新和刪除能力;
提供修改模式的能力;
支持 MVCC,讓不同的數(shù)據(jù)庫連接具有各自的事務視圖。
這些功能如何協(xié)同工作是設計數(shù)據(jù)庫磁盤數(shù)據(jù)表示的重要部分。
Postgres 的一個核心設計是不可變數(shù)據(jù)行。這些不可變數(shù)據(jù)行在 Postgres 中被稱為“元組”。這些元組通過 ctid 來唯一標識。從概念上看,ctid 表示元組在磁盤上的位置(即物理磁盤偏移)??赡軙卸鄠€ ctid 描述單個行(例如,為了支持 MVCC,可能存在一個數(shù)據(jù)行的多個版本,或者一個數(shù)據(jù)行的舊版本還沒有被 autovacuum 進程回收掉)。元組集合構成一張表。表本身是有索引的,這些索引被組織成某種數(shù)據(jù)結構(通常是 B 樹),將索引字段映射到 ctid。
通常,這些 ctid 對用戶是透明的,但了解它們的工作原理有助于了解 Postgres 表的磁盤結構。要查看當前行的 ctid,可以在語句中將“ctid”添加到列列表中:
uber@[local] uber=> SELECT ctid, * FROM my_table LIMIT 1; -[ RECORD 1 ]--------+------------------------------ ctid | (0,1) ...其他字段...
我們通過一個簡單的用戶表來解釋這個。對于每個用戶,我們都有一個自動遞增的用戶 ID 主鍵、用戶的名字和姓氏以及用戶的出生年份。我們還針對用戶全名(名字和姓氏)定義了復合二級索引,并針對用戶的出生年份定義了另一個二級索引。創(chuàng)建表的 DDL 可能是這樣的:
CREATE TABLE users ( id SERIAL, first TEXT, last TEXT, birth_year INTEGER, PRIMARY KEY (id) ); CREATE INDEX ix_users_first_last ON users (first, last); CREATE INDEX ix_users_birth_year ON users (birth_year);
這里定義了三個索引:一個主鍵索引和兩個二級索引。
我們往表中插入以下這些數(shù)據(jù),包括一些有影響力的歷史數(shù)學家:
如前所述,這里的每一行都有一個隱式、唯一的 ctid。因此,我們可以這樣考慮表的內部表示形式:
主鍵索引(將 id 映射到 ctid)的定義如下:
B 樹索引是在 id 字段上定義的,并且 B 樹中的每個節(jié)點都存有 ctid 的值。請注意,在這種情況下,由于使用了自動遞增的 ID,B 樹中字段的順序恰好與表中的順序相同,但并不是一直都這樣。
二級索引看起來差不多,主要區(qū)別在于字段的存儲順序不同,因為 B 樹必須按字典順序來組織。(first,last) 索引從名字的字母表順序開始:
類似的,birth_year 索引按照升序排列,如下所示:
對于后兩種情況,二級索引中的 ctid 字段不是按照字典順序遞增的,這與自動遞增主鍵的情況不同。
假設我們需要更新該表中的一條記錄,比如我們要更新 al-Khwārizmī的出生年份。如前所述,行的元組是不可變的。因此,為了更新記錄,我們向表中添加了一個新的元組。這個新的元組有一個新的 ctid,我們將其稱為 I。Postgres 需要區(qū)分新元組 I 與舊元組 D。在內部,Postgres 在每個元組中保存了一個版本字段和一個指向先前元組的指針(如果有的話)。因此,表的最新結構如下所示:
只要存在 al-Khwārizmī行的兩個版本,索引中就必須同時包含兩個行的條目。為簡便起見,我們省略了主鍵索引,只顯示了二級索引,如下所示:
我們用紅色表示舊數(shù)據(jù)行,用綠色表示新數(shù)據(jù)行。Postgres 使用另一個版本字段來確定哪個元組是最新的。數(shù)據(jù)庫根據(jù)這個字段確定哪個元組對不允許查看新版本數(shù)據(jù)的事務可見。
在 Postgres 中,主索引和二級索引都直接指向磁盤上的元組偏移量。當元組位置發(fā)生變化時,必須更新所有索引。
復制
當我們在表中插入新行時,如果啟用了流式復制,Postgres 需要對其進行復制。為了能夠在發(fā)生崩潰后恢復,數(shù)據(jù)庫維護了預寫日志(WAL),并用它來實現(xiàn)兩階段提交。即使未啟用流式復制,數(shù)據(jù)庫也必須維護 WAL,因為 WAL 可以保證 ACID 中的原子性和持久性。
為了更好地理解 WAL,我們可以想象一下如果數(shù)據(jù)庫意外發(fā)生崩潰(例如突然斷電)會發(fā)生什么。WAL 代表了一系列數(shù)據(jù)庫計劃對表和索引在磁盤上內容做出的更改。Postgres 守護進程在啟動時會將 WAL 的數(shù)據(jù)與磁盤上的實際數(shù)據(jù)進行對比。如果 WAL 中包含未反映到磁盤上的數(shù)據(jù),數(shù)據(jù)庫就會更正元組或索引數(shù)據(jù),并回滾出現(xiàn)在 WAL 中但在事務中沒有被提交的數(shù)據(jù)。
Postgres 通過將主數(shù)據(jù)庫上的 WAL 發(fā)送給副本來實現(xiàn)流式復制。每個副本數(shù)據(jù)庫就像是在進行崩潰恢復,不斷地應用 WAL 更新。流式復制和實際發(fā)生崩潰恢復之間的唯一區(qū)別是,處于“熱備用”模式的副本在應用 WAL 時可以提供查詢服務,但真正處于崩潰恢復模式的 Postgres 數(shù)據(jù)庫通常會拒絕提供查詢服務,直到數(shù)據(jù)庫實例完成崩潰恢復過程。
因為 WAL 實際上是為實現(xiàn)崩潰恢復而設計的,所以它包含了底層的磁盤更新信息。WAL 包含了元組及其磁盤偏移量(即行 ctid)在磁盤上的表示。如果副本完全與主數(shù)據(jù)庫同步,此時暫停 Postgres 的主數(shù)據(jù)庫和副本,那么副本的磁盤內容與主數(shù)據(jù)庫的磁盤內容將完全一致。因此,如果副本與主數(shù)據(jù)庫不同步,可以用 rsync 之類的工具來修復。
Postgres 的設計導致 Uber 的數(shù)據(jù)效率低下,還讓我們遇到了很多麻煩。
寫入放大
Postgres 的第一個問題是寫入放大。通常,寫入放大是指將數(shù)據(jù)寫入 SSD 磁盤時遇到的問題:小的邏輯更新(例如,寫入幾個字節(jié))在轉換到物理層時會放大,成本會變高。在之前的示例中,如果我們對 al-Khwārizmī的出生年份進行小的邏輯更新,必須進行至少四個物理更新:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)
將新的行元組寫入表空間;
更新主鍵索引;
更新 (first,last) 索引;
更新 birth_year 索引。
實際上,這四個更新也只反映了對主表空間的寫操作。除此之外,這些寫操作也需要反映在 WAL 中,因此磁盤上的寫操作總數(shù)會變得更多。
這里值得注意的是更新 2 和更新 3。在更新 al-Khwārizmī的出生年份時,實際上并沒有修改它的主鍵,也沒有修改名字和姓氏。但盡管如此,仍然必須在數(shù)據(jù)庫中創(chuàng)建新的行元組,以便更新這些索引。對于具有大量二級索引的表,這些多余的步驟可能會導致效率低下。例如,如果我們在一張表中定義了十二個索引,即使只更新了單個索引對應的字段,也必須將該更新傳播給所有 12 個索引,以便反映新行的 ctid。
復制
這個寫入放大問題自然也轉化到了復制層,因為復制發(fā)生在磁盤級別。數(shù)據(jù)庫并不會復制小的邏輯記錄,例如“將 ctid D 的出生年份更改為 770”,而是將之前的 4 個 WAL 條目傳播到網絡上。因此,寫入放大問題也轉化為復制放大問題,Postgres 復制數(shù)據(jù)流很快變得非常冗長,可能占用大量帶寬。
如果 Postgres 復制僅發(fā)生在單個數(shù)據(jù)中心內,那么復制帶寬可能就不是問題?,F(xiàn)代網絡設備和交換機可以處理大量帶寬,很多托管服務提供商還提供了免費或便宜的數(shù)據(jù)中心內部帶寬。但是,如果要在數(shù)據(jù)中心之間進行復制,問題就會迅速升級。例如,Uber 最初使用了西海岸托管中心里的物理服務器。為了進行災備,我們在東海岸托管中心添加了服務器。于是,我們在西部數(shù)據(jù)中心里有一個主 Postgres 實例(加上副本),在東部也有一個副本集。
級聯(lián)復制將數(shù)據(jù)中心間的帶寬限制為只能滿足主數(shù)據(jù)庫和單個副本之間的帶寬需求,雖然第二個數(shù)據(jù)中心里還有很多副本。因為 Postgres 復制協(xié)議的冗繁,使用了大量索引的數(shù)據(jù)庫會有很大的數(shù)據(jù)量。購買跨地域大帶寬成本非常高昂,即使錢不成問題,也不可能獲得與本地帶寬類似的效果。這個帶寬問題也給 WAL 歸檔帶來了麻煩。除了將所有 WAL 更新從西海岸發(fā)送到東海岸之外,我們還要將所有 WAL 都存檔到文件存儲服務中,這是為了確保在發(fā)生災難時我們可以還原數(shù)據(jù)。在早期的流量高峰期間,我們寫入存儲服務的帶寬不夠快,無法跟上 WAL 的寫入速度。
數(shù)據(jù)損壞
在例行升級主數(shù)據(jù)庫以便增加數(shù)據(jù)庫容量的過程中,我們遭遇了 Postgres 9.2 個一個 bug。因為副本在切換時間方面出現(xiàn)了錯誤,導致其中一些副本錯誤地應用了一小部分 WAL 記錄。由于這個問題,一些本應由版本控制機制標記為無效的記錄實際上并未被標記為無效。
下面的查詢說明了這個錯誤將如何影響我們的用戶表:
SELECT * FROM users WHERE id = 4;
這個查詢將返回兩條記錄:初始的 al-Khwārizmī行(出生年份為 780 CE)和新的 al-Khwārizmī行(出生年份為 770 CE)。如果將 ctid 添加到 WHERE 中,對于這兩條返回的記錄,我們將看到不同的 ctid 值。
這個問題非常煩人。首先,我們無法得知這個問題究竟影響了多少行數(shù)據(jù)。數(shù)據(jù)庫返回的重復結果在很多情況下會導致應用程序邏輯故障。我們最終添加了防御性編程語句,用來檢測會出現(xiàn)這個問題的表。這個錯誤影響到了所有服務器,而在不同的副本實例上損壞的數(shù)據(jù)行是不一樣的。也就是說,在其中一個副本實例上,行 X 可能是壞的,行 Y 是好的,但是在另一副本實例上,行 X 可能是好,行 Y 可能是壞的。我們無法確定數(shù)據(jù)損壞的副本數(shù)量以及問題是否影響了主數(shù)據(jù)庫。
據(jù)我們所知,每個數(shù)據(jù)庫只有幾行數(shù)據(jù)會出現(xiàn)這個問題,但我們擔心的是,由于復制發(fā)生在物理級別,最后可能會完全破壞數(shù)據(jù)庫索引。B 樹索引很重要的一點是必須定期進行重新平衡(rebalance),并且當子樹移動到新的磁盤位置時,這些重新平衡操作可能會完全改變樹的結構。如果移動了錯誤的數(shù)據(jù),則可能導致樹的大部分完全無效。
最后,我們找到了問題所在,并確定新的主數(shù)據(jù)庫沒有損壞的數(shù)據(jù)行。我們通過從主數(shù)據(jù)庫的最新快照重新同步所有副本(這是一個費力的過程)來修復副本的數(shù)據(jù)損壞問題。
我們遇到的錯誤只出現(xiàn)在 Postgres 9.2 的某些版本中,并且已經修復了很長時間了。但是,我們仍然擔心此類錯誤會再次發(fā)生。新版本的 Postgres 可能還會出現(xiàn)此類錯誤,并且由于數(shù)據(jù)復制的方式,這類問題有可能被傳播到所有的數(shù)據(jù)庫中。
副本 MVCC
Postgres 沒有提供真正的副本 MVCC 支持。副本只應用 WAL 更新,導致它們在任何時候都具有與主數(shù)據(jù)庫相同的磁盤數(shù)據(jù)副本。這種設計給 Uber 帶來了麻煩。
Postgres 需要為 MVCC 維護舊數(shù)據(jù)的一個副本。如果流式復制遇到一個正在執(zhí)行的事務,而數(shù)據(jù)庫更新影響到了事務范圍內的行,那么更新操作就會被阻塞。在這種情況下,Postgres 會暫停 WAL 線程,直到事務結束。如果事務處理要花費很長時間,這就會是個問題,因為副本可能嚴重滯后于主數(shù)據(jù)庫。因此,Postgres 在這種情況下應用超時策略:如果一個事務導致 WAL 發(fā)生阻塞一定的時間,Postgres 將會終止這個事務。
這種設計意味著副本通常會比主數(shù)據(jù)庫落后幾秒鐘,很容易出現(xiàn)事務被終止的情況。例如,假設開發(fā)人員寫了一些代碼,需要通過電子郵件將收據(jù)發(fā)送給用戶。根據(jù)編寫方式的不同,代碼可能會隱式地讓數(shù)據(jù)庫事務處于打開狀態(tài),直到電子郵件完成發(fā)送為止。盡管在執(zhí)行不相關的阻塞 IO 時一直打開數(shù)據(jù)庫事務是很糟糕的做法,但大多數(shù)工程師并不是數(shù)據(jù)庫專家,他們可能也不知道有這個問題,特別是在使用隱藏了底層細節(jié)的 ORM 框架時。
升級 Postgres
由于復制發(fā)生在物理層面,所以我們無法在 Postgres 的不同版本之間復制數(shù)據(jù)。Postgres 9.3 的主數(shù)據(jù)庫不能被復制到 Postgres 9.2 的副本,而 Postgres 9.2 的主數(shù)據(jù)庫也不能被復制到 Postgres 9.3 的副本。
我們按照以下這些步驟從一個 Postgres GA 版本升級到另一個版本:
關閉主數(shù)據(jù)庫。
在主數(shù)據(jù)庫上運行 pg_upgrade 命令,這個命令會就地更新主數(shù)據(jù)庫數(shù)據(jù)。對于大型數(shù)據(jù)庫,通常需要花費數(shù)小時,并且在這個過程過程中無法從主數(shù)據(jù)庫讀取數(shù)據(jù)。
再次啟動主數(shù)據(jù)庫。
創(chuàng)建主數(shù)據(jù)庫的最新快照。這一步驟完全復制了主數(shù)據(jù)庫的所有數(shù)據(jù),因此大型數(shù)據(jù)庫也需要花費數(shù)小時。
擦除所有副本,并將最新的快照從主數(shù)據(jù)庫還原到副本上。
將副本帶回到復制層次結構中。等待副本完全跟上主數(shù)據(jù)庫的所有更新。
我們從 Postgres 9.1 開始,并成功完成了升級過程,遷移到了 Postgres 9.2。但是,這個過程花費了數(shù)小時,我們無力承擔再次執(zhí)行這種升級過程的費用。到 Postgres 9.3 發(fā)布時,Uber 的規(guī)模增長極大增加了我們的數(shù)據(jù)集,因此升級時間就變得更長了。因此,即使 Postgres 9.5 已經發(fā)布了,我們的 Postgres 實例仍然是 9.2 版本。
如果你的 Postgres 是 9.4 或更高版本,可以使用 pgologic 之類的東西,它為 Postgres 實現(xiàn)了一個邏輯復制層。你可以用它在不同的 Postgres 版本之間復制數(shù)據(jù),這意味著可以從 9.4 升級到 9.5,而不會造成大面積停機。不過,這個功能仍然是有問題的,因為它尚未被集成到 Postgres 主線中。而對于那些使用較舊版本的 Postgres 的人來說,pgologic 并不適用。
上文解釋了 Postgres 的一些局限性,接下來,我們將解釋為什么 MySQL 會成為 Uber 工程團隊存儲項目(例如 Schemaless)的新工具。在很多情況下,我們發(fā)現(xiàn) MySQL 更適合我們的使用場景。為了理解這些差異,我們研究了 MySQL 的架構,并將其與 Postgres 進行了對比。我們專門分析了 MySQL 的 InnoDB 存儲引擎。
InnoDB 的磁盤表示
與 Postgres 一樣,InnoDB 支持 MVCC 和可變數(shù)據(jù)等高級功能。關于 InnoDB 磁盤表示的詳盡細節(jié)不在本文的討論范圍之內,我們將把重點放在它與 Postgres 的主要區(qū)別上。
最主要的架構差異是:Postgres 直接將索引記錄映射到磁盤上的位置,而 InnoDB 使用了二級結構。InnoDB 的二級索引有一個指向主鍵值的指針,而不是指向磁盤位置的指針(如 Postgres 中的 ctid)。因此,MySQL 會將二級索引將索引鍵與主鍵相關聯(lián):
要基于 (first, last) 索引 執(zhí)行查詢,需要進行兩次查找。第一次先搜索表,找到記錄的主鍵。在找到主鍵之后,搜索主鍵索引,找到數(shù)據(jù)行對應的磁盤位置。
所以,在執(zhí)行二級查找時,InnoDB 相比 Postgres 略有不利,因為 InnoDB 必須搜索兩個索引,而 Postgres 只需要搜索一個。但是,由于數(shù)據(jù)已經規(guī)范化,在更新行數(shù)據(jù)時只需要更新實際發(fā)生變化的索引記錄。此外,InnoDB 通常會在原地進行行數(shù)據(jù)更新。為了支持 MVCC,如果舊事務需要引用一行數(shù)據(jù),MySQL 會將舊行復制到一個叫作回滾段的特殊區(qū)域中。
我們來看看更新 al-Khwārizmī的出生年份會發(fā)生什么。如果空間足夠,id 為 4 的那一行數(shù)據(jù)中的出生年份字段會進行原地更新(實際上,這個更新總是發(fā)生在原地,因為出生年份是一個占用固定空間量的整數(shù))。出生年份索引也進行原地更新。舊數(shù)據(jù)行將被復制到回滾段。主鍵索引不需要更新,(first, last) 索引也不需要更新。即使這張表有大量索引,也只需要更新包含 birth_year 字段的索引。假設我們基于 signup_date、last_login_time 等字段建立了索引,我們不需要更新這些索引,但在 Postgres 中需要更新。
這種設計還讓數(shù)據(jù)清理和壓縮變得更加高效?;貪L段中的數(shù)據(jù)可以直接清除,相比之下,Postgres 的 autovacuum 進程必須進行全表掃描來識別哪些行可以清除。
MySQL 使用了額外的中間層:二級索引記錄指向主索引記錄,主索引保存了數(shù)據(jù)行在磁盤上的位置。如果數(shù)據(jù)行偏移量發(fā)生變化,只需要更新主索引。
復制
MySQL 支持多種不同的復制模式:
基于語句的復制將會復制邏輯 SQL 語句(它將按字面意義復制 SQL 語句,例如:UPDATE users SET birth_year = 770 WHERE id = 4);
基于行的復制將會復制發(fā)生變化的行記錄;
混合復制將這兩種模式混合在一起。
這幾種模式各有優(yōu)缺點?;谡Z句的復制通常是最緊湊的,但可能需要副本應用大量語句來更新少量數(shù)據(jù)。另一方面,基于行的復制(與 Postgres WAL 復制類似)雖然更為冗繁,但更具可預測性和在副本上的更新效率。
在 MySQL 中,只有主索引有指向行的磁盤偏移量的指針。在進行復制時,這具有重要的意義。MySQL 復制流只需要包含有關行的邏輯更新信息。對于類似“將行 X 的時間戳從 T_1 更改為 T_2”這樣的更新,副本會自動推斷需要修改哪些索引。
相比之下,Postgres 復制流包含了物理變更,例如“在磁盤偏移量 8,382,491 處寫入字節(jié) XYZ”。在使用 Postgres 時,對磁盤進行的每一個物理變更都需要包含在 WAL 流中。較小的邏輯修改(例如更新時間戳)也需要執(zhí)行很多磁盤變更:Postgres 必須插入新的元組,并更新所有索引,讓它們指向這個元組,所以會有很多變更被放入 WAL 流中。這種設計差異意味著 MySQL 復制二進制日志比 PostgreSQL WAL 流更緊湊。
復制方式也對副本的 MVCC 產生重要影響。由于 MySQL 復制流具有邏輯更新,副本可以具有真正的 MVCC 語義,所以對副本的讀取查詢不會阻塞復制流。相比之下,Postgres WAL 流包含了磁盤上的物理更改,Postgres 副本無法應用與讀取查詢相沖突的復制更新,因此無法實現(xiàn) MVCC。
MySQL 的復制架構意味著即使有 bug 導致表損壞,也不太可能會發(fā)生災難性故障。因為復制發(fā)生在邏輯層,所以像重新平衡 B 樹之類的操作永遠不會導致索引損壞。一個典型的 MySQL 復制問題是語句被跳過(或者被應用兩次),這可能導致數(shù)據(jù)丟失或無效,但不會導致數(shù)據(jù)庫中斷。
最后,MySQL 的復制架構可以很容易在不同的 MySQL 版本之間進行復制。MySQL 的邏輯復制格式還意味著存儲引擎層中的磁盤變更不會影響復制格式。在進行 MySQL 升級時,典型的做法是一次將更新應用于一個副本,在更新完所有副本后,將其中一個提升為新的主副本。這幾乎可以實現(xiàn)零停機升級,很容易就可以讓 MySQL 保持最新狀態(tài)。
到目前為止,我們介紹了 Postgres 和 MySQL 的磁盤架構。MySQL 還有其他一些重要方面也讓它的性能明顯優(yōu)于 Postgres。
緩沖池
首先,兩個數(shù)據(jù)庫的緩存方式不同。Postgres 為內部緩存分配了一些內存,但是與計算機上的內存總量相比,這些緩存通常很小。為了提高性能,Postgres 允許內核通過頁面緩存自動緩存最近訪問的磁盤數(shù)據(jù)。例如,我們最大的 Postgres 副本有 768 GB 的可用內存,但實際上只有 25 GB 被用作 Postgres 的進程 RSS 內存,這樣就為 Linux 頁面緩存留出了 700 GB 以上的可用內存。
這種設計的問題在于,與訪問 RSS 內存相比,通過頁面緩存訪問數(shù)據(jù)實際上開銷更大。為了從磁盤上查找數(shù)據(jù),Postgres 進程發(fā)出 lseek 和 read 系統(tǒng)調用來定位數(shù)據(jù)。這些系統(tǒng)調用中的每一個都會引起上下文切換,這比從主存儲器訪問數(shù)據(jù)的開銷更大。實際上,Postgres 在這方面甚至還沒有完全進行優(yōu)化:Postgres 并未利用 pread 系統(tǒng)調用,這個系統(tǒng)調用會將 seek 和 read 操作合并為一個系統(tǒng)調用。
相比之下,InnoDB 存儲引擎通過緩沖池實現(xiàn)了自己的 LRU。從邏輯上講,這與 Linux 頁面緩存相似,但它是在用戶空間中實現(xiàn)的。盡管 InnoDB 緩沖池的設計比 Postgres 的設計要復雜得多,但它具備一些優(yōu)勢:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)
可以實現(xiàn)自定義 LRU。例如,可以檢測出可能會破壞 LRU 的訪問模式,并防止其造成更大問題。
較少的上下文切換。通過 InnoDB 緩沖池訪問的數(shù)據(jù)不需要進行用戶 / 內核上下文切換。最壞的情況是發(fā)生 TLB 未命中,這些開銷相對較小,可以通過使用大頁面來緩解。
連接處理
MySQL 通過一個連接一個線程的方式來實現(xiàn)并發(fā)連接。這種開銷相對較低,每個線程都有自己的棧內存和分配給特定連接的緩沖堆內存。在 MySQL 中使用 10000 個左右的并發(fā)連接,這種情況并不少見,實際上,在我們現(xiàn)有的某些 MySQL 實例上,連接數(shù)已經接近這個數(shù)字。
但是,Postgres 采用的是一個連接一個進程的設計,這比一個連接一個線程的設計要昂貴得多。派生新進程比生成新線程占用更多的內存。此外,進程之間的 IPC 比線程之間的 IPC 也昂貴得多。Postgres 9.2 通過 System V IPC 原語實現(xiàn) IPC,而不是使用輕量級的 futex。futex 比 System V IPC 更快,因為通常情況下,futex 不存在竟態(tài)條件,因此無需進行上下文切換。
除了內存和 IPC 開銷,Postgres 似乎也無法很好地支持大量連接,即使有足夠的可用內存。我們在 Postgres 中使用數(shù)百個活動連接時遇到了大問題。Postgres 文檔建議采用進程外連接池機制來處理大量連接,但沒有詳細說明是為什么。因此,我們使用 pgbouncer 來處理 Postgres 的連接池。但是,我們的后端服務偶爾會出現(xiàn) bug,導致它們打開的活動連接過多,從而延長了宕機時間。
在 Uber 早期,Postgres 為我們提供了很好的服務,但是隨著公司規(guī)模的增長,我們遇到了伸縮性問題?,F(xiàn)在,我們仍然保留了一些舊的 Postgres 實例,但大部分數(shù)據(jù)庫都建立在 MySQL 之上(通常使用 Schemaless 層),或者在某些特殊情況下會使用像 Cassandra 這樣的 NOSQL 數(shù)據(jù)庫。
關于Uber為何要放棄Postgres選擇遷移到MySQL問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關知識。
文章題目:Uber為何要放棄Postgres選擇遷移到MySQL
分享地址:http://bm7419.com/article46/jdddeg.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、網站營銷、響應式網站、做網站、微信公眾號、建站公司
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)