테이블 생성 및 분산
분산 테이블을 만들려면 먼저 테이블 스키마를 정의해야 합니다. 일반 PostgreSQL 테이블과 마찬가지로 CREATE TABLE 문을 사용하여 테이블을 정의하면 됩니다.
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');
이 함수는 github_events 테이블이 repo_id 컬럼을 기준으로 (해시를 통해) 분산되어야 함을 Citus에 알립니다. 또한 citus.shard_count와 citus.shard_replication_factor 설정 값을 사용하여 워커 노드에 샤드를 생성합니다.
이 예제는 총 citus.shard_count개의 샤드를 생성하며, 각 샤드는 해시 토큰 공간의 일부를 소유하고 기본 citus.shard_replication_factor 설정에 따라 복제됩니다. 워커에 생성된 샤드 복제본은 코디네이터의 테이블과 동일한 테이블 스키마, 인덱스 및 제약 조건 정의를 가집니다. 복제본 생성 후, 이 함수는 모든 분산 메타데이터를 코디네이터에 저장합니다.
생성된 각 샤드에는 고유한 샤드 ID가 할당되며, 모든 복제본은 동일한 샤드 ID를 공유합니다. 각 샤드는 워커 노드에서 tablename_shardid라는 이름의 일반 PostgreSQL 테이블로 표현됩니다. 여기서 tablename은 분산 테이블의 이름이고, shardid는 해당 샤드에 할당된 고유 ID입니다. 워커 노드의 postgres 인스턴스에 연결하여 개별 샤드를 보거나 명령을 실행할 수 있습니다.
이제 분산 테이블에 데이터를 삽입하고 쿼리를 실행할 준비가 되었습니다. 이 섹션에서 사용된 UDF에 대한 자세한 내용은 Citus 유틸리티 함수 문서에서 확인할 수 있습니다.
참조 테이블
위의 방법은 테이블을 여러 수평 샤드로 분산하지만, 다른 방법은 테이블을 단일 샤드로 분산하고 해당 샤드를 모든 워커 노드에 복제하는 것입니다. 이러한 방식으로 분산된 테이블을 참조 테이블이라고 합니다. 이는 클러스터의 여러 노드에서 자주 액세스해야 하는 데이터를 저장하는 데 사용됩니다.
참조 테이블의 일반적인 예는 다음과 같습니다.
- 대규모 분산 테이블과 조인해야 하는 소규모 테이블.
- 멀티 테넌트 애플리케이션에서
테넌트 ID컬럼이 없거나 테넌트와 연결되지 않은 테이블. (경우에 따라 마이그레이션 작업을 줄이기 위해 테넌트와 연결되어 있지만 현재테넌트 ID가 없는 테이블에서 참조 테이블을 만들도록 선택할 수도 있습니다.) - 여러 컬럼에 걸쳐 고유 제약 조건이 필요하고 크기가 충분히 작은 테이블.
예를 들어, 멀티 테넌트 전자상거래 웹사이트에서 모든 매장의 거래에 대한 판매세를 계산해야 한다고 가정해 보겠습니다. 세금 정보는 특정 테넌트에 한정되지 않습니다. 이를 공유 테이블에 통합하는 것이 합리적입니다. 미국을 기준으로 한 참조 테이블은 다음과 같을 수 있습니다.
-- 참조 테이블
CREATE TABLE states (
code char(2) PRIMARY KEY,
full_name text NOT NULL,
general_sales_tax numeric(4,3)
);
-- 모든 워커에 분산
SELECT create_reference_table('states');
이제 장바구니에 대한 세금 계산과 같은 쿼리는 네트워크 오버헤드 없이 states 테이블을 조인할 수 있으며, 더 나은 검증을 위해 state 코드에 외래 키를 추가할 수 있습니다.
create_reference_table UDF는 테이블을 단일 복제 샤드로 분산하는 것 외에도 Citus 메타데이터 테이블에서 이를 참조 테이블로 표시합니다. Citus는 이러한 방식으로 표시된 테이블을 수정하기 위해 자동으로 2단계 커밋(2PC)을 수행하여 강력한 일관성을 보장합니다.
기존 분산 테이블이 있는 경우 다음을 실행하여 참조 테이블로 변경할 수 있습니다.
SELECT undistribute_table('table_name');
SELECT create_reference_table('table_name');
코디네이터 데이터 분산
기존 PostgreSQL 데이터베이스를 Citus 클러스터의 코디네이터 노드로 변환하는 경우, 해당 테이블의 데이터를 효율적으로 분산하고 애플리케이션 중단을 최소화할 수 있습니다.
앞서 설명한 create_distributed_table 함수는 빈 테이블과 비어 있지 않은 테이블 모두에서 작동하며, 후자의 경우 자동으로 클러스터 전체에 테이블 행을 분산합니다. NOTICE: Copying data from local 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에서 Citus Cloud로 마이그레이션하는 등 외부 데이터베이스에서 데이터를 마이그레이션할 때는 먼저 create_distributed_table을 통해 Citus 분산 테이블을 만든 다음 테이블에 데이터를 복사합니다. 분산 테이블로 복사하면 코디네이터 노드의 공간 부족을 방지할 수 있습니다.
공동 배치(Co-location)
공동 배치는 관련 정보를 동일한 머신에 전략적으로 저장하여 효율적인 관계형 연산을 가능하게 하고 데이터 세트 전체의 수평적 확장성을 활용하는 방법입니다. 자세한 내용과 예제는 테이블 공동 배치 문서를 참조하십시오.
테이블은 그룹 내에서 공동 배치(co-located)됩니다. 테이블의 co-location 할당을 수동으로 제어하려면 create_distributed_table의 선택적 colocate_with 매개변수를 사용하십시오. 테이블의 co-location에 신경 쓰지 않는다면 이 매개변수를 무시하십시오. 기본값은 'default'이며, 이는 동일한 분산 컬럼 유형, 샤드 수 및 복제 요소를 가진 다른 모든 기본 co-location 테이블과 테이블을 그룹화합니다. 이 묵시적 colocation을 중단하거나 업데이트하려면 update_distributed_table_colocation()을 사용할 수 있습니다.
-- 다음 테이블들은 동일한 분산 컬럼 유형과 샤드 수를 사용하여
-- 기본 co-location 그룹에 묵시적으로 공동 배치됩니다.
SELECT create_distributed_table('A', 'some_int_col');
SELECT create_distributed_table('B', 'other_int_col');
새 테이블이 잠재적인 묵시적 co-location 그룹의 다른 테이블과 관련이 없는 경우 colocated_with => 'none'을 지정하십시오.
-- 다른 테이블과 공동 배치되지 않음
SELECT create_distributed_table('A', 'foo', colocate_with => 'none');
관련 없는 테이블을 자체 co-location 그룹으로 분할하면 동일한 그룹의 샤드를 함께 이동해야 하므로 샤드 리밸런싱 성능이 향상됩니다.
테이블이 실제로 관련된 경우(예: 조인될 예정인 경우) 명시적으로 함께 배치하는 것이 합리적입니다. 적절한 co-location으로 얻는 이점은 리밸런싱 오버헤드보다 큽니다.
여러 테이블을 명시적으로 공동 배치하려면 하나의 테이블을 분산한 다음 다른 테이블을 해당 co-location 그룹에 넣으십시오. 예를 들면 다음과 같습니다.
-- stores 분산
SELECT create_distributed_table('stores', 'store_id');
-- stores와 동일한 그룹에 추가
SELECT create_distributed_table('orders', 'store_id', colocate_with => 'stores');
SELECT create_distributed_table('products', 'store_id', colocate_with => 'stores');
co-location 그룹에 대한 정보는 pg_dist_colocation 테이블에 저장되며, pg_dist_partition은 어떤 테이블이 어떤 그룹에 할당되었는지 보여줍니다.
Citus 5.x에서 업그레이드
Citus 6.0부터 co-location을 핵심 개념으로 도입하고 pg_dist_colocation에서 테이블의 co-location 그룹 할당을 추적하기 시작했습니다. Citus 5.x에는 이 개념이 없었기 때문에 Citus 5로 생성된 테이블은 물리적으로 같은 위치에 있더라도 메타데이터에 명시적으로 공동 배치된 것으로 표시되지 않았습니다.
Citus는 쿼리 최적화 및 푸시다운을 위해 관리 메타데이터 정보를 사용하므로 이전에 생성된 테이블의 co-location을 Citus에 알리는 것이 중요합니다. 메타데이터를 수정하려면 mark_tables_colocated를 사용하여 테이블을 co-located로 표시하십시오.
-- stores, products, line_items가 Citus 5.x 데이터베이스에서 생성되었다고 가정합니다.
-- products와 line_items를 store의 co-location 그룹에 넣습니다.
SELECT mark_tables_colocated('stores', ARRAY['products', 'line_items']);
이 함수는 테이블이 동일한 방법, 컬럼 유형, 샤드 수 및 복제 방법으로 분산되어 있어야 합니다. 샤드를 다시 만들거나 데이터를 물리적으로 이동하지 않으며, Citus 메타데이터만 업데이트합니다.
테이블 삭제
표준 PostgreSQL DROP TABLE 명령을 사용하여 분산 테이블을 삭제할 수 있습니다. 일반 테이블과 마찬가지로 DROP TABLE은 대상 테이블에 존재하는 모든 인덱스, 규칙, 트리거 및 제약 조건을 삭제합니다. 또한 워커 노드의 샤드를 삭제하고 관련 메타데이터를 정리합니다.
DROP TABLE github_events;
테이블 수정
Citus는 다양한 DDL 문을 자동으로 전파합니다. 즉, 코디네이터 노드의 분산 테이블을 수정하면 워커의 샤드도 업데이트됩니다. 다른 DDL 문은 수동으로 전파해야 하며, 분산 컬럼을 수정하는 등 일부 문은 금지됩니다. 자동 전파 조건에 맞지 않는 DDL을 실행하려고 하면 오류가 발생하고 코디네이터 노드의 테이블은 변경되지 않은 상태로 유지됩니다.
다음은 전파되는 DDL 문 범주에 대한 참조입니다. 구성 매개변수를 사용하여 자동 전파를 활성화하거나 비활성화할 수 있습니다.
컬럼 추가/수정
Citus는 대부분의 ALTER TABLE 명령을 자동으로 전파합니다. 컬럼을 추가하거나 기본값을 변경하는 것은 단일 PostgreSQL 데이터베이스에서와 동일하게 작동합니다.
-- 컬럼 추가
ALTER TABLE products ADD COLUMN description text;
-- 기본값 변경
ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;
기존 컬럼의 이름 바꾸기 또는 데이터 유형 변경과 같은 주요 변경도 가능합니다. 그러나 분산 컬럼의 데이터 유형은 변경할 수 없습니다. 이 컬럼은 테이블 데이터가 Citus 클러스터에서 어떻게 분산되는지 결정하며, 데이터 유형을 수정하려면 데이터를 이동해야 합니다.
이를 시도하면 오류가 발생합니다.
-- store_id가 products의 분산 컬럼이고 정수 유형이라고 가정
ALTER TABLE products
ALTER COLUMN store_id TYPE text;
/*
ERROR: cannot execute ALTER TABLE command involving partition column
*/
해결 방법으로 분산 컬럼을 변경하고, 업데이트한 다음 다시 변경하는 것을 고려할 수 있습니다.
제약 조건 추가/삭제
Citus를 사용하면 데이터베이스 제약 조건(PostgreSQL 문서 참조)을 포함하여 관계형 데이터베이스의 안전성을 계속 누릴 수 있습니다. 분산 시스템의 특성상 Citus는 워커 노드 간의 고유성 제약 조건 또는 참조 무결성을 교차 참조하지 않습니다.
다음과 같은 경우에 외래 키를 생성할 수 있습니다.
- 두 로컬(비분산) 테이블 간
- 두 참조 테이블 간
- 참조 테이블과 로컬 테이블 간 (기본적으로 활성화,
citus.enable_local_reference_table_foreign_keys (boolean)통해) - 키에 분산 컬럼이 포함된 경우, 두 개의 공동 배치된 분산 테이블 간
- 참조 테이블 역할을 하는 분산 테이블
참조 테이블에서 분산 테이블로의 외래 키는 지원되지 않습니다.
Citus는 로컬 테이블에서 참조 테이블로의 모든 외래 키 참조 동작을 지원하지만, 반대 방향(로컬 테이블 참조)의 ON DELETE/UPDATE CASCADE는 지원하지 않습니다.
기본 키 및 고유성 제약 조건은 분산 컬럼을 포함해야 합니다. 분산되지 않은 컬럼에 추가하면 오류가 발생합니다.
다음 예제는 분산 테이블에서 기본 키와 외래 키를 생성하는 방법을 보여줍니다.
--
-- 기본 키 추가
-- --------------------
-- account_id를 기준으로 테이블을 분산합니다. ads 및 clicks
-- 테이블은 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);
-- 다음으로 테이블 분산
SELECT create_distributed_table('accounts', 'id');
SELECT create_distributed_table('ads', 'account_id');
SELECT create_distributed_table('clicks', 'account_id');
--
-- 외래 키 추가
-- -------------------
-- 분산 전후에 모두 가능합니다. 단, 대상 컬럼에 고유성 제약 조건이
-- 있어야 하며, 이는 분산 전에만 적용될 수 있습니다.
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);
마찬가지로 고유성 제약 조건에 분산 컬럼을 포함합니다.
-- 모든 광고가 고유한 이미지를 사용하도록 한다고 가정합니다. account_id로
-- 분산할 때 계정별로만 적용할 수 있습니다.
ALTER TABLE ads ADD CONSTRAINT ads_unique_image
UNIQUE (account_id, image_url);
NOT NULL 제약 조건은 워커 노드 간 조회가 필요하지 않으므로 모든 컬럼(분산 여부와 관계없이)에 적용할 수 있습니다.
ALTER TABLE ads ALTER COLUMN image_url SET NOT NULL;
NOT VALID 제약 조건 사용
경우에 따라 새 행에는 제약 조건을 적용하고 기존의 비준수 행은 그대로 두는 것이 유용합니다. Citus는 PostgreSQL의 "NOT VALID" 제약 조건 지정을 사용하여 CHECK 제약 조건 및 외래 키에 대해 이 기능을 지원합니다.
예를 들어, 사용자 프로필을 참조 테이블에 저장하는 애플리케이션을 고려해 보겠습니다.
-- 여기서는 "text" 컬럼 유형을 사용하지만, 실제 애플리케이션은
-- postgres contrib 모듈에서 사용 가능한 "citext"를 사용할 수 있습니다.
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).
*/
나중에, 비성수 시간에 데이터베이스 관리자는 잘못된 행을 수정하고 제약 조건을 다시 검증할 수 있습니다.
-- 나중에 모든 행 검증 시도
ALTER TABLE users
VALIDATE CONSTRAINT syntactic_email;
PostgreSQL 문서의 ALTER TABLE 섹션에는 NOT VALID 및 VALIDATE CONSTRAINT에 대한 자세한 정보가 있습니다.
인덱스 추가/삭제
Citus는 인덱스 추가 및 삭제를 지원합니다.
-- 인덱스 추가
CREATE INDEX clicked_at_idx ON clicks USING BRIN (clicked_at);
-- 인덱스 삭제
DROP INDEX clicked_at_idx;
인덱스 추가에는 쓰기 잠금이 필요하며, 이는 멀티 테넌트 "레코드 시스템"에서는 바람직하지 않을 수 있습니다. 애플리케이션 다운타임을 최소화하려면 CONCURRENTLY를 사용하여 인덱스를 생성하십시오. 이 방법은 표준 인덱스 빌드에 비해 더 많은 총 작업량이 필요하고 완료하는 데 더 오래 걸립니다. 그러나 인덱스를 빌드하는 동안 정상적인 작업을 계속할 수 있으므로 프로덕션 환경에서 새 인덱스를 추가하는 데 유용합니다.
-- 테이블 쓰기를 잠그지 않고 인덱스 추가
CREATE INDEX CONCURRENTLY clicked_at_idx ON clicks USING BRIN (clicked_at);
수동 수정
현재 다른 DDL 명령은 자동으로 전파되지 않지만 변경 사항을 수동으로 전파할 수 있습니다. 수동 쿼리 전파를 참조하십시오.