개발/기술

citus - 스키마 정리하고 분산테이블 만들기

kwony 2024. 9. 5. 22:30

아래와 같은 스키마를 가진 database 가 있다고 해보자

  • content 테이블은 board 테이블을 참조하며 게시판 정보를 포함한다.
  • content_feed 은 board 테이블을 참조하면서 게시판별 피드 정보를 나타내는데 사용된다.
  • content_like 테이블은 user 와 content 정보를 참조하면서 유저의 컨텐츠 좋아요 정보를 저장한다.

일반 SNS 에서 흔히 볼 수 있는 스키마다.

 

운영중에 content 테이블에 데이터가 많이 쌓여서 citus 를 이용해서 분산하고자 한다. 분산 칼럼은 id 필드로 선정했다

select from create_distributed_table('content', 'id', shard_count := 4);

 

하지만 아래와 같은 에러가 발생한다.

ERROR:  referenced table "board" must be a distributed table or a reference table
DETAIL:  To enforce foreign keys, the referencing and referenced rows need to be stored on the same node.
HINT:  You could use SELECT create_reference_table('board') to replicate the referenced table to all nodes or consider dropping the foreign key

 

지금 content 테이블이 board 테이블을 참조하고 있는데 content 테이블을 분산테이블로 만든다면 지금과 같은 참조관계를 유지할 수 없다는 에러다.

 

두가지 방안이 있는다.

  • 하나는 board 테이블을 reference 테이블로 만드는 것이고
  • 다른 하나는 content → board 로 가는 참조 자체는 없애는 방식이다.

후자의 경우 외래키가 끊어지면서 실제 코드에 영향을 줄 수도 있는 부분이니 적절한 방식이 아니다.  board 테이블을 reference table 로 선언하는 방법을 사용하자.

 

select create_reference_table('board');
NOTICE:  local tables that are added to metadata automatically by citus, but not chained with reference tables via foreign keys might be automatically converted back to postgres tables
HINT:  Executing citus_add_local_table_to_metadata($$public.content$$) prevents this for the given relation, and all of the connected relations
NOTICE:  local tables that are added to metadata automatically by citus, but not chained with reference tables via foreign keys might be automatically converted back to postgres tables
HINT:  Executing citus_add_local_table_to_metadata($$public.content$$) prevents this for the given relation, and all of the connected relations
NOTICE:  local tables that are added to metadata automatically by citus, but not chained with reference tables via foreign keys might be automatically converted back to postgres tables
HINT:  Executing citus_add_local_table_to_metadata($$public.content_like$$) prevents this for the given relation, and all of the connected relations
NOTICE:  local tables that are added to metadata automatically by citus, but not chained with reference tables via foreign keys might be automatically converted back to postgres tables
HINT:  Executing citus_add_local_table_to_metadata($$public.content_feed$$) prevents this for the given relation, and all of the connected relations

 

그러면 로컬 테이블을 추가한다는 로그가 나오면서 테이블 생성이 완료된다

 

coordinator 에서 citus_tables 정보를 조회하면 아래처럼 board 테이블을 제외한 나머지 테이블이 local 테이블로 선언되어 있고

select * from citus_tables;
  table_name  | citus_table_type | distribution_column | colocation_id | table_size | shard_count | table_owner | access_method
--------------+------------------+---------------------+---------------+------------+-------------+-------------+---------------
 "user"       | local            | <none>              |             0 | 8192 bytes |           1 | postgres    | heap
 board        | reference        | <none>              |             2 | 24 kB      |           1 | postgres    | heap
 content      | local            | <none>              |             0 | 16 kB      |           1 | postgres    | heap
 content_feed | local            | <none>              |             0 | 8192 bytes |           1 | postgres    | heap
 content_like | local            | <none>              |             0 | 8192 bytes |           1 | postgres    | heap

 

워커 노드에선 board 테이블 뿐만 아니라 다른 테이블도 조회가 가능하다.

 

\dt
            List of relations
 Schema |     Name     | Type  |  Owner
--------+--------------+-------+----------
 public | board        | table | postgres
 public | content      | table | postgres
 public | content_feed | table | postgres
 public | content_like | table | postgres
 public | user         | table | postgres

 

board 테이블 뿐만 아니라 모든 테이블이 조회가 가능해서 의아할 수 있는데 citus 에서는 reference 테이블과 의존관계가 있는 테이블은 모두 local 테이블로 설정한다. 직접적인 의존이 아니라 중간에 다른 테이블이 껴있는 간접적인 의존 관계여도 local 테이블로 선언된다. 그래서 user 랑 content_like 도 local 테이블로 변경됐다.

 

생각해보면 그럴만도 한게 board 테이블을 reference table 로 선언했다는건 worker 노드에서 board 테이블을 이용해서 단독으로 쿼리를 만들수 있다는 것이고 외래키 관계를 이용해서 다른 테이블도 참조할 수 있다는 뜻이다. 그런데 정작 참조하고 있는 테이블이 스키마가 없는게 오히려 이상하게 보일수도 있다. 그래서 citus 에서는 연결관계가 있다면 모두 local 테이블로 만들어 버리고 있다.

 

아까 실패했던 쿼리를 다시 실행해보자

select from create_distributed_table('content', 'id', shard_count := 4);

 

근데 다시 아래와 같은 에러가 뜬다

ERROR:  cannot create foreign key constraint since foreign keys from reference tables and local tables to distributed tables are not supported
DETAIL:  Reference tables and local tables can only have foreign keys to reference tables and local tables

 

reference 테이블과 local 테이블의 외래키로 참조하는 테이블은 반드시 reference 테이블이거나 local 테이블이어야 한다는 뜻이다.

 

앞의 다이어그램을 다시보면 content_feed 와 content_like 테이블이 content 테이블을 참조하고 있는데 두 테이블은 아까 reference 테이블을 선언하면서 local 테이블로 변경됐다. 그런데 content 테이블을 분산 테이블로 만들면 로컬 테이블이 분산 테이블을 참조할 수 없는 룰에 위배되니 citus 에러 에러를 띄워서 막았다.

 

그럼 이건 어떻게 해결해야할까? 이것도 두가지 방법이 있다.

  • 하나는 외래키 관계를 끊어버리는 것이고
  • 다른 하나는 content 테이블을 참조하는 테이블을 동일하게 분산테이블로 만드는것.

local → 분산 테이블간의 참조 관계는 성립하지 않지만 분산 → 분산 테이블 간의 관계는 성립한다. 단, 이때 colocate 옵션을 추가해서 연관된 데이터가 같은 샤드에 위치하도록 만들어야 한다.

 

당연히 서비스 영향도를 줄이기 위해 후자 방법이 적절하다.

 

적용하기 위해 먼저 content 테이블을 만들기 위해 잠시 외래키 관계를 끊어주자.

alter table content_feed drop constraint content_feed_content_id_fkey;
alter table content_like drop constraint content_like_content_id_fkey;

 

이제 content 테이블을 분산 테이블로 만들수 있다.

select from create_distributed_table('content', 'id', shard_count := 4);

 

content_feed, content_like 테이블의 primary key 에 content_id 를 추가하자

alter table content_feed drop constraint content_feed_pkey;
alter table content_feed add primary key (id, content_id);
alter table content_like drop constraint content_like_pkey;
alter table content_like add primary key (id, content_id);

 

primary 키를 변경해주면 분산 칼럼을 content_id 로 사용할 수 있게 된다

select create_distributed_table('content_like', 'content_id', colocate_with:='content');
select create_distributed_table('content_like', 'content_id', colocate_with:='content');

 

 

이제 제약 사항들을 다시 원복해준다

alter table content_feed add constraint content_feed_content_id_fkey FOREIGN KEY (content_id) REFERENCES content(id) ON DELETE CASCADE;
alter table content_like add constraint content_like_content_id_fkey FOREIGN KEY (content_id) REFERENCES content(id)

 

정상적으로 실행 됐다면 분산테이블 적용 완료!


 

citus 에서는 분산 테이블 사용시 외래키 규칙을 엄격하게 적용하고 있는데 아무래도 잘못 꼬이면 데이터 조회 성능에서 크리티컬하게 떨어질 수 있기 때문인 것 같다.

 

아래 사진처럼 join 과정에서 필요한 데이터가 여러 노드에 분산되어 저장된다면 하나의 쿼리가 네개로 쪼개져서 실행하게 되지만

colocate_with 기능을 적절히 활용해 연관된 데이터를 하나의 노드로 모아두면 쿼리 한방에 실행이 가능하다.

규칙이 까다로워 자칫하면 외래키 지옥에 빠질수 있지만 처음에 잘 만들어두면 성능 문제없이 사용할 수 있을 것 같다.