建立與修改分散式物件(DDL)

以下章節說明如何利用 Citus 中的資料定義語言(DDL)建立與修改分散式物件。

建立與分發結構

Citus 12.0 引入 schema_based_sharding ,允許分散式結構。 分散式結構會自動與個別的共置群組關聯。 當你在這些結構中建立資料表時,這些資料表會自動成為沒有分片金鑰的共置分散式資料表。

你可以用兩種方式在 Citus 中分發結構模式:

  1. 手動呼叫該 citus_schema_distribute 函式:

    SELECT citus_schema_distribute('user_service');
    

    此方法也可讓您將現有的一般結構描述轉換成分散式結構描述。

    備註

    您只能散發不包含分散式和參考資料表的結構描述。

  2. 啟用設定變數:enable_schema_based_sharding

    SET citus.enable_schema_based_sharding TO ON;
    
    CREATE SCHEMA AUTHORIZATION user_service;
    

    將當前會話的變數或永久變更在 postgresql.conf。 當你將參數設為 ON時,所有建立的結構預設都是分布式的。

散發架構的程式會自動指派,並將其移至叢集中的現有節點。 背景分片重平衡器在重新平衡叢集時會取得這些結構及其內的所有資料表。 接著執行最佳移動,並在叢集節點間遷移結構。

要將結構轉換回一般的 PostgreSQL 架構,請使用以下 citus_schema_undistribute 函式:

SELECT citus_schema_undistribute('user_service');

結構中的 user_service 資料表和資料會從目前節點移回叢集中的協調節點。

建立與分發資料表

要建立分散式資料表,首先定義資料表結構。 請使用 CREATE TABLE 陳述式定義表格,就像一般 PostgreSQL 表格一樣。

CREATE TABLE github_events
(
    event_id bigint,
    event_type text,
    event_public boolean,
    repo_id bigint,
    payload jsonb,
    repo jsonb,
    actor jsonb,
    org jsonb,
    created_at timestamp
);

接著,使用 create_distributed_table() 函式指定資料表分配欄位並建立工作者分片。

SELECT create_distributed_table('github_events', 'repo_id');

此函式告知 Citus github_events 表格應透過雜湊欄位值來分配至該 repo_id 欄位。 函式也會利用 citus.shard_count 組態值在工作節點上建立分片。

此範例會產生總 citus.shard_count 數的分片,每個分片擁有雜湊權杖空間的一部分。 建立分片後,函式會將所有分散式元資料儲存在協調器上。

每個建立的分片都會有一個獨特的分片 ID。 每個分片都以一個名為 tablename_shardid的 PostgreSQL 表格形式出現在工作節點上。 是 tablename 分散式資料表的名稱,而 是 shardid 該分片唯一分配的 ID。 你可以連接到 Worker PostgreSQL 實例,查看或執行個別分片的指令。

您現在已準備好將資料插入分散發式資料表,並在該資料表上執行查詢。 欲了解更多本節所使用的使用者定義函數,請參閱 Citus 效用函數

參考資料表

前一節描述如何將資料表分配成多個水平分片。 另一個選項是將資料表分散到單一分片,並將分片複製到每個工作節點。 以這種方式分布的資料表是 參考資料表。 使用參考資料表來儲存叢集中多個節點需要頻繁存取的資料。

參考資料表的常見候選項目包含:

  • 較小的資料表需要與較大的分散式資料表連接。
  • 多租戶應用程式中的表格,沒有租戶 ID 欄位,或與租戶無關聯。 在某些情況下,為了減少遷移工作量,使用者甚至會選擇將目前沒有租戶 ID 的租戶資料表組成參考資料表。
  • 在多個資料行間需要唯一條件約束且夠小的資料表。

舉例來說,假設一個多租戶電子商務網站需要計算其任何門市交易的銷售稅。 稅務資訊並非專屬於任何租用戶。 把它整合到共用表格中是合理的。 以美國為中心的參考資料表可能如下所示:

-- a reference table

CREATE TABLE states (
  code char(2) PRIMARY KEY,
  full_name text NOT NULL,
  general_sales_tax numeric(4,3)
);

-- distribute it to all workers

SELECT create_reference_table('states');

現在,例如一個計算購物車稅金的查詢可以在資料表上 states 聯結,而不需要網路額外負荷,且可以將外部索引鍵新增至狀態碼,以取得更好的驗證。

除了將資料表作為單一複製分片分發外,使用者自訂函式 create_reference_table 還會在 Citus 元資料表中標記為參考資料表。 Citus 會自動執行兩階段提交(2PC),以提供強烈的一致性保證。

如果你已有分散式資料表,可以透過執行以下方式將其更改為參考資料表:

SELECT undistribute_table('table_name');
SELECT create_reference_table('table_name');

關於在多租戶應用程式中使用參考表的另一個範例,請參見 mt_ref_tables

分發協調器資料

如果你將現有的 PostgreSQL 資料庫轉換成 Citus 叢集的協調節點,就能有效地將資料分散到其資料表中,且對應用程式的中斷最小。

create_distributed_table前述函式適用於空表與非空表。 對於非空資料表,它會自動將資料表列分配到叢集中。 你可以從「通知:從本地資料表複製資料...」的訊息判斷它是否在分發資料。例如:

CREATE TABLE series AS SELECT i FROM generate_series(1,1000000) i;
SELECT create_distributed_table('series', 'i');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$public.series$$)
 create_distributed_table
 --------------------------

 (1 row)

資料傳輸時,資料表上的寫入會被阻擋。 該函式在提交後,會將待處理寫入視為分散式查詢。 如果函式失敗,查詢會再次回到本地。 讀取可以照常進行,並在函式提交後變成分散式查詢。

散發資料表 A 和 B 時,其中 A 具有 B 的外部索引鍵,請先散發索引鍵目的地資料表 B。 以錯誤的順序執行此操作會導致錯誤:

ERROR: cannot create foreign key constraint
DETAIL: Referenced table must be a distributed table or a reference table.

如果你無法按正確順序分發,就丟棄外鍵,分發資料表,然後重新建立外鍵。

分發資料表後,使用 truncate_local_data_after_distributing_table 移除本地資料的函式。 分散式資料表中剩餘的本地資料無法被 Citus 查詢存取,並可能對協調器造成無關的限制違規。

當從外部資料庫(例如從 Amazon RDS 遷移到我們的 cloud_topic資料庫)時,首先 create_distributed_table使用 。 然後,將資料複製到表格中。 複製到分散式資料表可避免協調器節點上的空間不足。

共置資料表

共置是一種將資料戰術性劃分的做法,將相關資訊保留在同一台機器上,以實現高效的關聯操作,同時利用整個資料集的水平擴展性。 欲了解更多資訊與範例,請參閱 共置群組表

Citus 會自動將表格分組共置。 若要手動控制資料表的共置群組指派,請使用 colocate_with 的選擇性 create_distributed_table 參數。 如果你不在意資料表的共置,就省略這個參數。 它預設為 'default',將表格與任何其他具有相同分布欄位類型與分片數量的預設共置表分組。 如果你想打破或更新這種隱含共址,請使用 update_distributed_table_colocation()

-- these tables are implicitly co-located by using the same
-- distribution column type and shard count with the default
-- co-location group

SELECT create_distributed_table('A', 'some_int_col');
SELECT create_distributed_table('B', 'other_int_col');

當新資料表與其潛在隱含共置群組中的其他資料表無關時,請指定 colocated_with => 'none'

-- not co-located with other tables

SELECT create_distributed_table('A', 'foo', colocate_with => 'none');

將無關的表格拆分為各自的共置群組,可以提升分片重新平衡的效能,因為同一群組中的分片會一起移動。

當資料表相關時(例如當它們被連接時),明確地將它們共置。 提升適當共置比任何重新平衡額外負荷更加重要。

若要明確共置多個資料表,先分配一個資料表,然後將其他資料表放入其共置群組。 例如:

-- distribute stores
SELECT create_distributed_table('stores', 'store_id');

-- add to the same group as stores
SELECT create_distributed_table('orders', 'store_id', colocate_with => 'stores');
SELECT create_distributed_table('products', 'store_id', colocate_with => 'stores');

關於共置群組的資訊會儲存在表格中 pg_dist_colocation ,同時 pg_dist_partition 揭示哪些表格被分配到哪些群組。

卸除資料表

使用標準的 PostgreSQL DROP TABLE 指令來移除分散式資料表。 如同一般資料表,DROP TABLE 會移除目標資料表中存在的任何索引、規則、觸發程式和條件約束。 此外,此命令也會卸除背景工作節點上的分區,並清除其中繼資料。

DROP TABLE github_events;

修改資料表

Citus 會自動傳播多種 DDL 語句。 修改協調器節點上的分散式資料表時,工作者節點上的分片也會被更新。 其他 DDL 語句則需手動傳播,且某些語句被禁止,例如會修改分配欄位的語句。 嘗試執行不符合自動傳播資格的 DDL 會產生錯誤,協調節點上的資料表保持不變。

以下是DDL語句類別的參考資料,這些陳述會傳播。 你可以透過設定參數來啟用或關閉自動傳播。

新增或修改欄位

Citus 會自動傳播大多數 ALTER TABLE 指令。 新增欄位或更改預設值的操作方式與單機 PostgreSQL 資料庫相同:

-- Adding a column

ALTER TABLE products ADD COLUMN description text;

-- Changing default value

ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;

你也可以對現有欄位做重大修改,例如重新命名或更改資料型態。 不過,你無法更改分配欄位的資料型別。 此欄位決定 Citus 如何在叢集中分配資料表資料,而修改其資料型態則需要移動資料。

如果你嘗試更改分布欄位的資料型態,會收到錯誤訊息:

-- assuming store_id is the distribution column
-- for products, and that it has type integer

ALTER TABLE products
ALTER COLUMN store_id TYPE text;

/*
ERROR:  cannot execute ALTER TABLE command involving partition column
*/

作為一個變通方法,可以考慮更改分配欄位 <alter_distributed_table>,更新它,然後再改回來。

新增或移除限制條件

使用 Citus 後,你可以繼續享受關聯式資料庫的安全性,包括資料庫的限制。 欲了解更多資訊,請參閱 PostgreSQL 文件。由於分散式系統的特性,Citus 不會在工作節點間交叉參照唯一性限制或參考完整性。

若要設定共置散發式資料表之間的外部索引鍵,請一律在索引鍵中包含散發資料行。 這種結構可能涉及製作關鍵化合物。

你可以在以下情況下建立外鍵:

  • 在兩個本地(非分布式)資料表之間,
  • 在兩個參考資料表之間,
  • 參考表與本地表之間(預設啟用,透過 enable_local_ref_fkeys),
  • 當鍵包含分發欄位時,介於兩個共址的分散式資料表之間,或
  • 作為一個分散式資料表,參考一個參考資料表。

不支援從參考資料表到分散式資料表的外鍵。

Citus 支援從本地到參考表的所有外鍵參 照動作 ,但不支援 ON DELETE/UPDATE CASCADE 反方向(參照到本地)。

備註

主索引鍵和唯一限制必須包含散發資料行。 將它們加入非分布欄位會產生錯誤。

以下範例展示了如何在分散式資料表上建立主鍵與外鍵:

--
-- Adding a primary key
-- --------------------

-- We'll distribute these tables on the account_id. The ads and clicks
-- tables must use compound keys that include account_id.

ALTER TABLE accounts ADD PRIMARY KEY (id);
ALTER TABLE ads ADD PRIMARY KEY (account_id, id);
ALTER TABLE clicks ADD PRIMARY KEY (account_id, id);

-- Next distribute the tables

SELECT create_distributed_table('accounts', 'id');
SELECT create_distributed_table('ads',      'account_id');
SELECT create_distributed_table('clicks',   'account_id');

--
-- Adding foreign keys
-- -------------------

-- Note that this can happen before or after distribution, as long as
-- there exists a uniqueness constraint on the target column(s) which
-- can only be enforced before distribution.

ALTER TABLE ads ADD CONSTRAINT ads_account_fk
  FOREIGN KEY (account_id) REFERENCES accounts (id);
ALTER TABLE clicks ADD CONSTRAINT clicks_ad_fk
  FOREIGN KEY (account_id, ad_id) REFERENCES ads (account_id, id);

同樣地在唯一性條件約束中包含散發資料行方式如下:

-- Suppose we want every ad to use a unique image. Notice we can
-- enforce it only per account when we distribute by account id.

ALTER TABLE ads ADD CONSTRAINT ads_unique_image
  UNIQUE (account_id, image_url);

你可以對任何欄位(不論是否分布)套用非空約束,因為它們不需要在工作者間查詢。

ALTER TABLE ads ALTER COLUMN image_url SET NOT NULL;

使用 NOT VALID 約束

在某些情況下,強制新列約束,同時允許現有不合規列保持不變,是有用的。 Citus 透過使用 PostgreSQL 的「NOT VALID」限制指定,支援 CHECK 約束與外鍵的此功能。

舉例來說,考慮一個應用程式將使用者資料儲存在參考表中。

-- we're using the "text" column type here, but a real application
-- might use "citext" which is available in a PostgreSQL contrib module

CREATE TABLE users ( email text PRIMARY KEY );
SELECT create_reference_table('users');

隨著時間推移,一些非地址者可能會進入表格。

INSERT INTO users VALUES
   ('foo@example.com'), ('hacker12@aol.com'), ('lol');

你想要驗證地址,但 PostgreSQL 通常不允許你新增一個對現有列失敗的 CHECK 約束。 不過,它 允許 一個標記為無效的限制:

ALTER TABLE users
ADD CONSTRAINT syntactic_email
CHECK (email ~
   '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
) NOT VALID;

此策略成功,新排得以保護。

INSERT INTO users VALUES ('fake');

/*
ERROR:  new row for relation "users_102010" violates
        check constraint "syntactic_email_102010"
DETAIL:  Failing row contains (fake).
*/

之後,在非尖峰時段,資料庫管理員可以嘗試修復錯誤列並重新驗證限制。

-- later, attempt to validate all rows
ALTER TABLE users
VALIDATE CONSTRAINT syntactic_email;

PostgreSQL 文件在 ALTER TABLE 章節中有有關 NOT VALID 和 VALIDATE CONSTRAINT 的詳細資訊。

加減指標

Citus 支援新增與移除 索引

-- Adding an index

CREATE INDEX clicked_at_idx ON clicks USING BRIN (clicked_at);

-- Removing an index

DROP INDEX clicked_at_idx;

新增索引需要寫入鎖定,這在多租戶記錄系統中可能不理想。 為了減少應用程式停機時間,改為同時建立索引。 這個方法需要比標準索引組建更多的總工時,而且需要較長的時間才能完成。 不過,由於此方法允許在建置索引時繼續正常作業,因此此方法對於在生產環境中新增索引十分有用。

-- Adding an index without locking table writes

CREATE INDEX CONCURRENTLY clicked_at_idx ON clicks USING BRIN (clicked_at);

類型與功能

當你建立自訂 SQL 型別和使用者定義函式時,Citus 會自動將它們傳播到工作節點。 然而,在包含分散式操作的交易中建立這些資料庫物件會帶來權衡。

Citus 透過使用多個連線來平行化跨 create_distributed_table() 分片等操作。 然而,當你建立資料庫物件時,Citus 會用一個連線將物件傳播到工作節點。 將這兩個操作合併在同一筆交易中可能會產生問題,因為平行連線無法看到在單一連線上建立但尚未提交的物件。

請考慮使用交易區塊來建立類型、資料表、載入資料,以及散發資料表:

BEGIN;

-- type creation over a single connection:
CREATE TYPE coordinates AS (x int, y int);
CREATE TABLE positions (object_id text primary key, position coordinates);

-- data loading thus goes over a single connection:
SELECT create_distributed_table('positions', 'object_id');
\COPY positions FROM 'positions.csv'

COMMIT;

在 Citus 11.0 之前,Citus 延遲在工作節點建立型別,並在建立分散式資料表時另行提交。 這套操作序列使資料 create_distributed_table() 複製能夠平行進行。 然而,這也意味著該類型並不總是出現在 Citus 工作節點上。 或者,如果交易回滾,該型別仍留在工作節點上。

在 Citus 11.0 中,預設行為優先考量協調節點與工作節點間的結構一致性。 此行為有缺點:若物件傳播發生在同一交易中平行指令之後,交易無法完成,以下程式碼區塊中的 ERROR 顯示:

BEGIN;
CREATE TABLE items (key text, value text);
-- parallel data loading:
SELECT create_distributed_table('items', 'key');
\COPY items FROM 'items.csv'
CREATE TYPE coordinates AS (x int, y int);

ERROR: cannot run type command because there was a parallel operation on a distributed table in the transaction

如果你遇到這個問題,可以試試以下兩種變通方法之一:

  1. 設定 citus.create_object_propagation 為 以 deferred 回到舊的物件傳播行為。 這個選項可能會導致不同節點上資料庫物件存在的差異不一致。
  2. 設定 citus.multi_shard_modify_modesequential 以停用每節點平行運算。 相同交易中的資料載入速度可能會變慢。

資料庫與角色管理 DDL(Citus 13.1)

Citus 13.1 擴展了自動傳播功能,以簡化角色與資料庫管理。 協調者現在自動傳播:

  • ALTER DATABASE .. SET ..
  • ALTER USER RENAME
  • COMMENT ON DATABASE / COMMENT ON ROLE
  • CREATE DATABASE / DROP DATABASE
  • GRANT/REVOKE 表格欄位的權利
  • REASSIGN OWNED BY
  • SECURITY LABEL 在表格與欄位上

此外,Citus 會傳播 從任何節點發出的一些角色與資料庫管理指令:

  • CREATE DATABASE / DROP DATABASE
  • SECURITY LABEL ON ROLE
  • 一般角色管理指令

請參見 citus.enable_ddl_propagation

手動改裝

大多數 DDL 指令都是自動傳播的。 其他部分則可手動傳播變更,詳見 手動查詢傳播