|| JOBKOREA X ALBAMONpg_partman, pg_cron을 활용한 Outbox Table Partitioning 적용기
안녕하세요. 공통플랫폼개발팀 김성훈입니다.
입사한지 이제 막 6개월이 넘어가는 시점에 기술 블로그를 작성하려고 하니 감회가 새롭네요.
최근 프로젝트에서 Outbox Pattern을 사용하면서 PostgreSQL(이하 PG) Daily Partitioning을 진행했습니다.
이 과정에서 어떤 고민이 있었고, 어떻게 파티셔닝을 적용했는지에 대해
다음과 같은 순서로 공유드리도록 하겠습니다.
기존 보일러플레이트 Outbox Pattern 구조 (Transactional Outbox Pattern)
PG Partitioning을 수행한 배경
Outbox Table에 파티셔닝 적용하기
pg_partman, pg_cron을 사용하여 파티셔닝 테이블 관리 자동화하기
마무리
1. 기존 보일러플레이트 Outbox Pattern 구조
저희 잡코리아는 각 프로젝트의 템플릿인 ‘보일러플레이트' 기반으로 프로젝트를 구성하고 있습니다. (관련 내용은 아래 포스팅에 있습니다.)
보일러플레이트 도입기, 그런데 이제 KLiK을 곁들인
따라서, 저희의 신규 프로젝트 역시 보일러플레이트를 기반으로 구성을 진행했었습니다.
기존 보일러플레이트에는 Event를 사용할 때 Transactional Outbox Pattern 이 적용된 상태였습니다.
(Transactional Outbox Pattern을 다루는 글은 아니기 때문에 해당 패턴에 대한 설명은 생략하고 넘어가도록 하겠습니다.)
보일러플레이트 Transactional Outbox Pattern 도식화Boilerplate Transactional Outbox Pattern
보일러플레이트의 지원 도메인 (Apply) 기준으로 현재 Event Produce & Consume Flow는 다음과 같습니다.
Application 지원 도메인 이벤트 발생
하나의 Transaction으로 Apply CUD & Outbox Table Insert
Transaction Log Tailing 방식으로 Debezium을 사용하여 CDC를 통해 Message Relay
Debezium에서 Kafka Message Publish로 Event Produce
2. PG Partitioning을 수행한 배경
그렇다면 위의 Outbox 구조에서 왜 Partitioning이 필요했을까요?
보일러플레이트를 기반으로 신규 프로젝트를 구성했을 때,
이미 운영중인 다른 서비스들에서 Outbox Table을 보고 다음과 같은 개선점을 발견하게 되었었습니다.
많은 이벤트 발생으로 인해 Outbox Table에 너무 많은 데이터가 쌓였다.
따라서 지속적으로 Outbox Table에 쌓이는 데이터를 처리해야 한다.
Transactional Outbox Pattern 사용 시에는 이벤트가 발생할 때마다 Outbox 테이블에 Row가 추가됩니다.
현재 약 6개월간 운영한 다른 서비스의 Outbox에 쌓인 Row는 약 2억건이었습니다. (WOW!
)
하나의 서비스에서 이벤트가 발생하는 빈도는 너무나도 잦기 때문에 Outbox에 쌓인 Row 수 또한 많은 것을 확인할 수 있었습니다.
이러한 상황을 해결하기 위해 고려한 설계 방안은 다음과 같습니다.
데이터 삭제 주기를 두고 주기마다 쌓인 Row Delete
DB 파티셔닝 후 주기마다 파티셔닝 테이블 삭제
이미 글에서 스포가 되었지만 결론적으로는 파티셔닝 방법을 택하게 되었습니다.
첫 번째 방법은 다음과 같은 이유로 선택하지 않게 되었습니다.
데이터 삭제주기마다 Outbox Table 쌓인 Row Delete
처음에는 간단하게 생각하여 해당 방법으로 진행하려고 했으나,
PG의 Vaccum 동작 Side-Effect로 인해 전체 DB 부하가 발생할 위험이 있어
해당 방법을 사용하지 않았습니다.
※ PG Vaccum?
PG의 Vaccum은 PG의 Dead Tuple 스토리지 공간을 회수하는 작업입니다.
입사한지 이제 막 6개월이 넘어가는 시점에 기술 블로그를 작성하려고 하니 감회가 새롭네요.
최근 프로젝트에서 Outbox Pattern을 사용하면서 PostgreSQL(이하 PG) Daily Partitioning을 진행했습니다.
이 과정에서 어떤 고민이 있었고, 어떻게 파티셔닝을 적용했는지에 대해
다음과 같은 순서로 공유드리도록 하겠습니다.
기존 보일러플레이트 Outbox Pattern 구조 (Transactional Outbox Pattern)
PG Partitioning을 수행한 배경
Outbox Table에 파티셔닝 적용하기
pg_partman, pg_cron을 사용하여 파티셔닝 테이블 관리 자동화하기
마무리
1. 기존 보일러플레이트 Outbox Pattern 구조
저희 잡코리아는 각 프로젝트의 템플릿인 ‘보일러플레이트' 기반으로 프로젝트를 구성하고 있습니다. (관련 내용은 아래 포스팅에 있습니다.)
보일러플레이트 도입기, 그런데 이제 KLiK을 곁들인
따라서, 저희의 신규 프로젝트 역시 보일러플레이트를 기반으로 구성을 진행했었습니다.
기존 보일러플레이트에는 Event를 사용할 때 Transactional Outbox Pattern 이 적용된 상태였습니다.
(Transactional Outbox Pattern을 다루는 글은 아니기 때문에 해당 패턴에 대한 설명은 생략하고 넘어가도록 하겠습니다.)
보일러플레이트 Transactional Outbox Pattern 도식화Boilerplate Transactional Outbox Pattern
보일러플레이트의 지원 도메인 (Apply) 기준으로 현재 Event Produce & Consume Flow는 다음과 같습니다.
Application 지원 도메인 이벤트 발생
하나의 Transaction으로 Apply CUD & Outbox Table Insert
Transaction Log Tailing 방식으로 Debezium을 사용하여 CDC를 통해 Message Relay
Debezium에서 Kafka Message Publish로 Event Produce
2. PG Partitioning을 수행한 배경
그렇다면 위의 Outbox 구조에서 왜 Partitioning이 필요했을까요?
보일러플레이트를 기반으로 신규 프로젝트를 구성했을 때,
이미 운영중인 다른 서비스들에서 Outbox Table을 보고 다음과 같은 개선점을 발견하게 되었었습니다.
많은 이벤트 발생으로 인해 Outbox Table에 너무 많은 데이터가 쌓였다.
따라서 지속적으로 Outbox Table에 쌓이는 데이터를 처리해야 한다.
Transactional Outbox Pattern 사용 시에는 이벤트가 발생할 때마다 Outbox 테이블에 Row가 추가됩니다.
현재 약 6개월간 운영한 다른 서비스의 Outbox에 쌓인 Row는 약 2억건이었습니다. (WOW!
)
하나의 서비스에서 이벤트가 발생하는 빈도는 너무나도 잦기 때문에 Outbox에 쌓인 Row 수 또한 많은 것을 확인할 수 있었습니다.
이러한 상황을 해결하기 위해 고려한 설계 방안은 다음과 같습니다.
데이터 삭제 주기를 두고 주기마다 쌓인 Row Delete
DB 파티셔닝 후 주기마다 파티셔닝 테이블 삭제
이미 글에서 스포가 되었지만 결론적으로는 파티셔닝 방법을 택하게 되었습니다.
첫 번째 방법은 다음과 같은 이유로 선택하지 않게 되었습니다.
데이터 삭제주기마다 Outbox Table 쌓인 Row Delete
처음에는 간단하게 생각하여 해당 방법으로 진행하려고 했으나,
PG의 Vaccum 동작 Side-Effect로 인해 전체 DB 부하가 발생할 위험이 있어
해당 방법을 사용하지 않았습니다.
※ PG Vaccum?
PG의 Vaccum은 PG의 Dead Tuple 스토리지 공간을 회수하는 작업입니다.

