分布式 PostgreSQL之Citus 架構(gòu)
節(jié)點(diǎn)
Citus 是一種 PostgreSQL 擴(kuò)展,它允許數(shù)據(jù)庫服務(wù)器(稱為節(jié)點(diǎn))在“無共享(shared nothing)”架構(gòu)中相互協(xié)調(diào)。這些節(jié)點(diǎn)形成一個集群,允許 PostgreSQL 保存比單臺計(jì)算機(jī)上更多的數(shù)據(jù)和使用更多的 CPU 內(nèi)核。這種架構(gòu)還允許通過簡單地向集群添加更多節(jié)點(diǎn)來擴(kuò)容數(shù)據(jù)庫。
- 擴(kuò)展
https://www.postgresql.org/docs/current/external-extensions.html
Coordinator 與 Worker
每個 cluster 都有一個稱為 coordinator(協(xié)調(diào)器) 的特殊節(jié)點(diǎn)(其他節(jié)點(diǎn)稱為 worker 節(jié)點(diǎn))。應(yīng)用程序?qū)⑺鼈兊牟樵儼l(fā)送到 coordinator 節(jié)點(diǎn),coordinator 節(jié)點(diǎn)將其轉(zhuǎn)發(fā)給相關(guān)的 worker 并累積結(jié)果。
對于每個查詢,coordinator 要么將其路由到單個 worker 節(jié)點(diǎn),要么將其并行化到多個節(jié)點(diǎn),具體取決于所需數(shù)據(jù)是位于單個節(jié)點(diǎn)上還是多個節(jié)點(diǎn)上。coordinator 通過查閱其元數(shù)據(jù)表知道如何做到這一點(diǎn)。這些 Citus 特定表跟蹤 worker 節(jié)點(diǎn)的 DNS 名稱和運(yùn)行狀況,以及跨節(jié)點(diǎn)數(shù)據(jù)的分布情況。
分布式數(shù)據(jù)
表類型
Citus 集群中有三種類型的表,每種表都以不同方式存儲在節(jié)點(diǎn)中,并且用于不同的目的。
類型 1:分布式表
第一種類型,也是最常見的,是分布式表。對于 SQL 語句而言,它們看似是普通的表,但在 worker 節(jié)點(diǎn)之間水平分區(qū)。
這里 table 的行存儲在 worker 的表 table_1001、table_1002 等中。組件 worker 表稱為分片(shards)。
分布列
Citus 使用使用分片算法將行分配到分片?;诒砹?稱為分布列(distribution column))的值執(zhí)行分配,此分配具有確定性。集群管理員在分布表時(shí)必須指定此列。做出正確的選擇,這一點(diǎn)對于性能和功能有重要影響。
類型 2:引用表
引用表 是一種分布式表,其全部內(nèi)容都集中到單個分片中,并在每個 worker 上復(fù)制。因此,對任何 worker 的查詢都可以在本地訪問 引用 信息,無需從另一個節(jié)點(diǎn)請求行,因此也不會產(chǎn)生此類網(wǎng)絡(luò)開銷。引用表沒有分布列,因?yàn)闊o需區(qū)分每行的各個分片。
引用表 通常很小,用于存儲與在任何工作節(jié)點(diǎn)上運(yùn)行的查詢相關(guān)的數(shù)據(jù)。例如,訂單狀態(tài)或產(chǎn)品類別等枚舉值。
當(dāng)與 引用表 交互時(shí),我們會自動對事務(wù)執(zhí)行兩階段提交 (2PC)。這意味著 Citus 確保您的數(shù)據(jù)始終處于一致狀態(tài),無論您是在寫入、修改還是刪除它。
- 2PC
https://en.wikipedia.org/wiki/Two-phase_commit_protocol
類型 3:本地表
當(dāng)您使用 Citus 時(shí),您連接并與之交互的 coordinator 節(jié)點(diǎn)是安裝了 Citus 擴(kuò)展的常規(guī) PostgreSQL 數(shù)據(jù)庫。因此,您可以創(chuàng)建普通表并選擇不對其進(jìn)行分片。這對于不參與連接查詢的小型管理表很有用。一個示例是用于應(yīng)用程序登錄和身份驗(yàn)證的用戶表。
創(chuàng)建標(biāo)準(zhǔn) PostgreSQL 表很容易,因?yàn)樗悄J(rèn)值。這是你運(yùn)行 CREATE TABLE 時(shí)得到的。在幾乎每個 Citus 部署中,我們都會看到標(biāo)準(zhǔn) PostgreSQL 表與 distributed 和 reference 表共存。事實(shí)上,如前所述,Citus 本身使用本地表來保存集群元數(shù)據(jù)。
Shards
上一節(jié)將分片描述為在 worker 節(jié)點(diǎn)內(nèi)的較小表中包含分布式表的行的子集。本節(jié)詳細(xì)介紹了技術(shù)細(xì)節(jié)。
協(xié)調(diào)器上的 pg_dist_shard 元數(shù)據(jù)表包含系統(tǒng)中每個分布式表的每個分片的行。該行與分片 ID 相匹配,分片 ID 的范圍是一組哈希整數(shù) (shardminvalue, shardmaxvalue)。
SELECT * from pg_dist_shard;
logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue
---------------+---------+--------------+---------------+---------------
github_events | 102026 | t | 268435456 | 402653183
github_events | 102027 | t | 402653184 | 536870911
github_events | 102028 | t | 536870912 | 671088639
github_events | 102029 | t | 671088640 | 805306367
(4 rows)
如果 coordinator 節(jié)點(diǎn)要確定哪個分片包含 github_events 行,它將對行中分布列的值執(zhí)行哈希算法。然后此節(jié)點(diǎn)檢查哪個分片的范圍包含此哈希值。定義范圍后,哈希函數(shù)的image(圖像)就是兩者的并查。
分片放置
假設(shè)分片 102027 與相應(yīng)的行關(guān)聯(lián)。在某個 worker 中的 github_events_102027 表中讀取或?qū)懭氪诵小J悄膫€ worker?這完全由元數(shù)據(jù)表確定。分片映射到 worker 的過程稱為分片放置(shard placement)。
coordinator 節(jié)點(diǎn)將查詢重寫為引用特定表(例如 github_events_102027)的片段,并對相應(yīng) worker 運(yùn)行這些片段。下面的查詢示例在后臺運(yùn)行,旨在查找分片 ID 為 102027 的節(jié)點(diǎn)。
SELECT
shardid,
node.nodename,
node.nodeport
FROM pg_dist_placement placement
JOIN pg_dist_node node
ON placement.groupid = node.groupid
AND node.noderole = 'primary'::noderole
WHERE shardid = 102027;
┌─────────┬───────────┬──────────┐
│ shardid │ nodename │ nodeport │
├─────────┼───────────┼──────────┤
│ 102027 │ localhost │ 5433 │
└─────────┴───────────┴──────────┘
在 github_events 示例中,有四個分片。每個表的分片數(shù)量在其在集群中分布時(shí)是可配置的。
最后請注意,Citus 允許復(fù)制分片以防止數(shù)據(jù)丟失。有兩種復(fù)制“模式”:Citus 復(fù)制和流復(fù)制。前者創(chuàng)建額外的備份分片放置并針對所有更新它們的所有它們運(yùn)行查詢。后者效率更高,利用 PostgreSQL 的流式復(fù)制將每個節(jié)點(diǎn)的整個數(shù)據(jù)庫備份到一個 follower 數(shù)據(jù)庫。這是透明的,不需要 Citus 元數(shù)據(jù)表的參與。
共置
由于可以根據(jù)需要將分片及其副本放置在節(jié)點(diǎn)上,因此將包含相關(guān)表的相關(guān)行的分片放在同一節(jié)點(diǎn)上是有意義的。這樣,它們之間的連接查詢可以避免通過網(wǎng)絡(luò)發(fā)送盡可能多的信息,并且可以在單個 Citus 節(jié)點(diǎn)內(nèi)執(zhí)行。
一個示例是包含商店、產(chǎn)品和購買的數(shù)據(jù)庫。如果所有三個表都包含 - 并且由 - store_id 列分布,那么限制在單個存儲中的所有查詢都可以在單個工作節(jié)點(diǎn)上高效運(yùn)行。即使查詢涉及這些表的任意組合也是如此。
并行性
跨多臺機(jī)器分散查詢允許一次運(yùn)行更多查詢,并允許通過向集群添加新機(jī)器來擴(kuò)展處理速度。此外,如上一節(jié)所述,將單個查詢拆分為片段可以提高專用于它的處理能力。后一種情況實(shí)現(xiàn)了最大的并行性,這意味著 CPU 內(nèi)核的利用率。
讀取或影響均勻分布在多個節(jié)點(diǎn)上的分片的查詢能夠以“實(shí)時(shí)”速度運(yùn)行。請注意,查詢的結(jié)果仍然需要通過協(xié)調(diào)器節(jié)點(diǎn)傳回,因此當(dāng)最終結(jié)果緊湊時(shí)(例如計(jì)數(shù)和描述性統(tǒng)計(jì)等聚合函數(shù)),加速效果最為明顯。
查詢執(zhí)行
在執(zhí)行多分片查詢時(shí),Citus 必須平衡并行性的收益與數(shù)據(jù)庫連接的開銷(網(wǎng)絡(luò)延遲和工作節(jié)點(diǎn)資源使用)。要配置 Citus 的查詢執(zhí)行以獲得最佳的數(shù)據(jù)庫工作負(fù)載結(jié)果,它有助于了解 Citus 如何管理和保存協(xié)調(diào)節(jié)點(diǎn)和工作節(jié)點(diǎn)之間的數(shù)據(jù)庫連接。
Citus 將每個傳入的多分片查詢會話轉(zhuǎn)換為稱為任務(wù)的每個分片查詢。它將任務(wù)排隊(duì),并在能夠獲得與相關(guān)工作節(jié)點(diǎn)的連接時(shí)運(yùn)行它們。對于分布式表 foo 和 bar 的查詢,下面是連接管理圖:
coordinator 節(jié)點(diǎn)為每個會話都有一個連接池。每個查詢(例如圖中的 SELECT * FROM foo)僅限于為每個 worker 的任務(wù)打開最多 citus.max_adaptive_executor_pool_size(整數(shù))個同時(shí)連接。該設(shè)置可在會話級別進(jìn)行配置,以進(jìn)行優(yōu)先級管理。
在同一連接上按順序執(zhí)行短任務(wù)比為它們并行建立新連接更快。另一方面,長時(shí)間運(yùn)行的任務(wù)受益于更直接的并行性。
為了平衡短任務(wù)和長任務(wù)的需求,Citus 使用 citus.executor_slow_start_interval(整數(shù))。該設(shè)置指定多分片查詢中任務(wù)的連接嘗試之間的延遲。當(dāng)查詢首先對任務(wù)進(jìn)行排隊(duì)時(shí),這些任務(wù)只能獲取一個連接。在每個有待處理連接的時(shí)間間隔結(jié)束時(shí),Citus 會增加它將打開的同時(shí)連接數(shù)。通過將 GUC 設(shè)置為 0,可以完全禁用慢啟動行為。
當(dāng)任務(wù)完成使用連接時(shí),會話池將保持連接打開以供以后使用。緩存連接避免了 coordinator 和 worker 之間重新建立連接的開銷。但是,每個池一次打開的空閑連接不超過 citus.max_cached_conns_per_worker(整數(shù))個,以限制 worker 中空閑連接資源的使用。
最后,設(shè)置 citus.max_shared_pool_size (integer) 充當(dāng)故障保險(xiǎn)。它限制了所有任務(wù)之間每個 worker 的總連接數(shù)。