分片

開放

1.1 什麼是分片?

如果你堅持谷歌,原來所謂的分區和所謂的分片之間有一個相當模糊的界限。大家隨便叫什麼就叫什麼。有些人區分水平分區和分片。其他人說分片是某種水平分區。

我沒有找到一個單一的術語標準可以得到創始人的批准和 ISO 的認證。個人內心的信念大概是這樣的:平均劃分就是按照任意的方式“把基數​​切成塊”。

  • 垂直分區- 按列。例如,有一個巨大的表,其中包含 60 列中的數十億條記錄。我們保留至少 60 個 20 億條記錄的巨型表,而不是保留一個這樣的巨型表——這不是列基,而是垂直分區(作為術語的一個例子)。
  • 水平分區- 我們逐行切割,可能在服務器內部。

這裡的尷尬時刻是水平分區和分片之間的細微差別。我可以被切成碎片,但我不能確定地告訴你它是什麼。感覺分片和水平分區是一回事。

一般來說,分片是指數據庫方面的大表或文檔、對象的專業集合,如果你沒有數據庫,但有文檔存儲,則完全按對象切分。也就是從20億個物體中,不管大小,都選出一塊。每個對象裡面的對象本身並沒有被切割成碎片,我們也沒有將它們分列放置,即我們將它們分批放置在不同的地方。

存在細微的術語差異。比如相對來說,Postgres的開發者可以說水平分區就是主表分出來的所有表都在同一個schema中,而在不同的機器上,這已經是sharding了。

一般意義上,不拘泥於具體的數據庫和具體的數據管理系統的術語,有一種分片的感覺就是逐行/逐文檔切片等等——僅此而已。

我強調典型。從某種意義上說,我們所做的這一切不僅僅是為了將 20 億個文檔切割成 20 個表,每個表都更易於管理,而是為了將其分佈在許多核心、許多磁盤或許多不同的物理或虛擬服務器上。

1.2 劃分不可分割的

據了解,我們這樣做是為了讓每個分片——每條數據——都被複製多次。但真的,沒有。

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

事實上,如果你做這樣的數據切片,並且從你勇敢的筆記本電腦上 MySQL 上的一個巨大的 SQL 表,你將生成 16 個小表,而不會超出一台筆記本電腦,不是一個單一的模式,不是一個單一的數據庫,等等. 等等。- 就是這樣,你已經有了分片。

這導致以下結果:

  • 帶寬增加。
  • 延遲不會改變,也就是說,在這種情況下,可以說,每個工人或消費者都有自己的延遲。幾乎在同一時間為不同的請求提供服務。
  • 或者兩者兼而有之,還有高可用性(複製)。

為什麼要帶寬?有時,我們可能會有如此大量的數據不適合 - 不清楚在哪裡,但它們不適合 - 在 1 {kernel | 磁盤 | 服務器 | ...}。只是資源不夠,僅此而已。為了使用這個大型數據集,您需要對其進行切割。

為什麼會有延遲?在一個內核上,掃描一個 20 億行的表比在 20 個內核上並行掃描 20 個表慢 20 倍。單個資源上的數據處理速度太慢。

為什麼要高可用?或者我們切割數據以便同時做一個和另一個,同時每個分片的多個副本 - 複製確保高可用性。

1.3 一個簡單的例子“如何手工完成”

條件分片可以使用測試表test.documents切出32個文檔,並從該表生成16個測試表,每個測試表大約2個文檔test.docs00,01,02,...,15。

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

為什麼?因為先驗我們不知道id是如何分佈的,如果從1到32(含),那麼每一個正好有2個文檔,否則沒有。

我們在這裡做為什麼。我們製作完16張表後,就可以“抓取”我們需要的16張了。不管我們命中了什麼,我們都可以並行化這些資源。例如,如果沒有足夠的磁盤空間,將這些表分解到單獨的磁盤上是有意義的。

不幸的是,這一切都不是免費的。我懷疑在規範 SQL 標準的情況下(我已經很久沒有重新閱讀 SQL 標準了,也許它已經很久沒有更新了),沒有官方的標準化語法可以對任何 SQL 服務器說:“親愛的SQL服務器,給我做32個分片,分4個盤。但是在單獨的實現中,通常有一個特定的語法來做基本相同的事情。PostgreSQL 有分區機制,MySQL 有 MariaDB,Oracle 很可能早就做了這一切。

然而,如果我們在沒有數據庫支持的情況下在標準框架內手動完成,那麼我們有條件地為數據訪問的複雜性付出代價。以前有一個簡單的 SELECT * FROM documents WHERE id=123,現在是 16 x SELECT * FROM docsXX。如果我們嘗試通過密鑰獲取記錄,那就太好了。如果我們試圖獲得早期的記錄範圍,那就更有趣了。現在(我強調,如果我們是傻瓜,並保持在標準的框架內),這 16 個 SELECT * FROM 的結果將必須在應用程序中組合。

您可以期待什麼性能變化?

  • 直觀地-線性。
  • 理論上 - 次線性,因為阿姆達爾定律。
  • 實際上,也許幾乎是線性的,也許不是。

事實上,正確的答案是未知的。通過巧妙地應用分片技術,您可以實現應用程序性能的顯著超線性下降,即使是 DBA 也會拿著一把紅熱的撲克牌跑來跑去。

讓我們看看這是如何實現的。很明顯,只是將設置設置為 PostgreSQL shards=16,然後它自己起飛,這並不有趣。讓我們考慮一下如何確保將分片速度減慢 16 倍 32 倍——從如何不這樣做的角度來看,這很有趣。

我們加速或減速的嘗試總是會遇到經典 - 古老的 Amdahl 法則,它說任何請求都沒有完美的並行化,總會有一些一致的部分。

1.4 阿姆達爾定律

總有一個序列化的部分。

總有一部分查詢執行是並行化的,總有一部分沒有並行化。即使在您看來是一個完美的並行查詢,至少您要從每個分片接收的行中發送給客戶端的結果行的集合始終存在,並且始終是順序的。

總有一些一致的部分。它可以很小,在一般背景下完全不可見,也​​可以很大,因此會強烈影響並行化,但它始終存在。

此外,它的影響正在發生變化並且可以顯著增長,例如,如果我們削減我們的表 - 讓我們提高賭注 - 從 64 條記錄變成 16 個表的 4 條記錄,這部分將會改變。當然,從如此巨大的數據量來看,我們正在使用手機和 2 MHz 86 處理器,我們沒有足夠的文件可以同時打開。顯然,有了這樣的輸入,我們一次打開一個文件。

  • 它是Total = Serial + Parallel。例如,並行是數據庫內部的所有工作,串行是將結果發送到客戶端。
  • 成為Total2 = Serial + Parallel/N + Xserial。比如整體ORDER BY時,Xserial>0。

通過這個簡單的例子,我試圖證明一些 Xserial 出現了。除了總是有一個序列化部分這一事實以及我們試圖並行處理數據這一事實之外,還有一個額外的部分來提供這種數據切片。粗略地說,我們可能需要:

  • 在數據庫的內部字典中找到這 16 個表;
  • 打開文件;
  • 分配內存;
  • 取消分配內存;
  • 合併結果;
  • 核之間同步。

一些不同步的效果仍然出現。它們可以微不足道,佔據總時間的十億分之一,但它們始終不為零,始終存在。在他們的幫助下,我們可以在分片後顯著降低性能。

這是關於阿姆達爾定律的標準圖片。這裡重要的是,理想情況下應該是直的並且線性增長的線會進入漸近線。但由於互聯網上的圖表難以理解,我認為我製作了更多帶有數字的可視化表格。

假設我們有一些只佔用 5% 的請求處理的序列化部分:serial = 0.05 = 1 / 20

直覺上,序列化部分似乎只佔用請求處理的 1/20,如果我們將 20 個內核的請求處理並行化,它將變得大約 20,在最壞的情況下快 18 倍。

其實數學是個沒心沒肺的東西

牆 = 0.05 + 0.95/核心數,加速比 = 1 / (0.05 + 0.95/核心數)

事實證明,如果仔細計算,序列化部分為 5%,加速比為 10 倍(10.3),比理論理想值提高了 51%。

8芯 = 5.9 = 74%
10芯 = 6.9 = 69%
20芯 = 10.3 = 51%
40核 = 13.6 = 34%
128核 = 17.4 = 14%

將 20 個內核(如果你願意,也可以是 20 個磁盤)用於過去處理的任務,我們在理論上什至永遠不會獲得超過 20 倍的加速,但在實踐中 - 更少。而且,隨著並行數的增加,效率低下也大大增加。

當只剩下 1% 的串行化工作,99% 被並行化時,加速比值有所提高:

8芯 = 7.5 = 93%
16芯 = 13.9 = 87%
32核 = 24.4 = 76%
64核 = 39.3 = 61%

對於一個完美的熱核查詢,自然需要幾個小時才能完成,而準備工作和結果的組裝只需要很少的時間 (serial = 0.001),我們已經可以看到良好的效率:

8芯 = 7.94 = 99%
16芯 = 15.76 = 99%
32核 = 31.04 = 97%
64核 = 60.20 = 94%

請注意,我們永遠不會看到 100%。在特別好的情況下,您可以看到,例如,99.999%,但不完全是 100%。

留言
  • 受歡迎
你必須登入才能留言
此頁面尚無留言