로지컬 리플리케이션
로지컬 리플리케이션 (Logical Replication)
PostgreSQL의 로지컬 리플리케이션을 CloudNativePG에서 선언적으로 사용하는 방법을 알려 드릴게요. Publication(발행)과 Subscription(구독) 리소스를 활용해 데이터를 실시간으로 복제하고, 라이브 마이그레이션이나 메이저 업그레이드에 어떻게 활용하는지 정리해요.
출처: 문서
본문
PostgreSQL은 정확한 블록 주소 수준과 바이트 단위 복사로 동작하는 물리적 리플리케이션을 넘어, 로지컬 리플리케이션(logical replication)을 제공함으로써 복제 능력을 확장해요. 로지컬 리플리케이션은 정의된 복제 아이덴티티(보통 기본 키)를 기반으로 데이터 객체와 그 변경 사항을 복제해요.
로지컬 리플리케이션은 게시-구독(publish-and-subscribe) 모델을 사용하며, 구독자(subscriber)가 퍼블리셔(publisher) 노드의 퍼블리케이션(publication)에 연결해요. 구독자는 이 퍼블리케이션에서 데이터 변경을 가져오고 다시 재발행(re-publish)할 수 있어, 캐스케이딩 리플리케이션과 복잡한 토폴로지를 가능하게 해요.
:::info[Important] CloudNativePG에서 퍼블리셔 클러스터의 페일오버 후 로지컬 리플리케이션 구독자를 보호하려면, 로지컬 디코딩용 리플리케이션 슬롯 동기화가 활성화되어 있는지 확인하세요. 이게 없으면 로지컬 리플리케이션 클라이언트가 페일오버 후 데이터를 잃고 원활하게 이어지지 못할 수 있어요. 구성 방법은 "리플리케이션: 로지컬 디코딩 슬롯 동기화"를 참고해 주세요. :::
이 유연한 모델은 특히 다음에 유용해요:
- 온라인 데이터 마이그레이션
- 라이브 PostgreSQL 버전 업그레이드
- 시스템 간 데이터 분산
- 실시간 분석
- 외부 애플리케이션과의 통합
:::info 자세한 내용, 예시, 제한 사항은 공식 PostgreSQL 로지컬 리플리케이션 문서를 참고해 주세요. :::
CloudNativePG는 핵심 PostgreSQL 로지컬 리플리케이션 객체에 대한 선언적 지원을 제공함으로써 이 기능을 강화해요:
- 퍼블리케이션(Publications):
Publication리소스 사용 - 구독(Subscriptions):
Subscription리소스 사용
퍼블리케이션 (Publications)
PostgreSQL의 게시-구독 리플리케이션 모델에서 퍼블리케이션(publication)은 데이터 변경의 소스예요. 이는 데이터베이스 안의 하나 이상의 테이블에서 생성된 변경 집합(리플리케이션 집합이라고도 함)의 논리적 컨테이너 역할을 해요. 퍼블리케이션은 퍼블리셔 역할을 하는 PostgreSQL 10+ 인스턴스 어디서든 정의할 수 있으며, 퍼블릭 클라우드의 인기 DBaaS 솔루션이 관리하는 인스턴스도 포함해요. 각 퍼블리케이션은 단일 데이터베이스에 연결되며 어떤 테이블과 변경을 복제할지 세밀하게 제어할 수 있어요.
Kubernetes 외부의 퍼블리셔를 위해 SQL로 퍼블리케이션을 만들거나 cnpg publication create 플러그인 명령을 활용할 수 있어요.
CloudNativePG로 Cluster 객체를 관리할 때 PostgreSQL 퍼블리케이션은 Publication 리소스를 통해 선언적으로 정의할 수 있어요.
:::info
각 Publication 객체에 정의할 수 있는 전체 속성 목록은 API 참조를 참고해 주세요.
:::
freddie라는 클러스터가 있고 app 데이터베이스의 모든 테이블을 복제하려 한다고 가정해 볼게요. 다음은 Publication 매니페스트예요:
apiVersion: postgresql.cnpg.io/v1
kind: Publication
metadata:
name: freddie-publisher
spec:
cluster:
name: freddie
dbname: app
name: publisher
target:
allTables: true
위 예시에서:
- 퍼블리케이션 객체는
freddie-publisher(metadata.name)로 이름 지어져요. - 퍼블리케이션은
freddie클러스터(spec.cluster.name)의 primary를 통해 이름publisher(spec.name)로 생성돼요. app데이터베이스(spec.dbname)의 모든 테이블(spec.target.allTables: true)을 포함해요.
퍼블리케이션 테이블에 대한 세밀한 제어 (Fine-grained control over publication tables)
allTables 옵션이 데이터베이스의 모든 테이블을 복제하는 편리한 방법을 제공하지만, PostgreSQL 15 이상에서는 CREATE PUBLICATION 명령을 통해 향상된 유연성을 도입해요. 이를 통해 어떤 테이블, 또는 어떤 유형의 데이터 변경을 퍼블리케이션에 포함할지 정확히 정의할 수 있어요.
:::info[Important]
PostgreSQL 15 이전 버전을 사용한다면 특정 릴리스에서 사용 가능한 CREATE PUBLICATION의 구문과 옵션을 확인하세요. 일부 파라미터와 기능은 지원되지 않을 수 있어요.
:::
복잡하거나 맞춤화된 리플리케이션 설정은 PostgreSQL 로지컬 리플리케이션 문서를 참고해 주세요.
또한 리플리케이션 대상을 선언적으로 커스터마이즈하는 방법은 CloudNativePG API 참조를 참고해 주세요.
다음 예시는 app 데이터베이스의 portal 스키마에 있는 모든 테이블과 access 스키마의 users 테이블을 복제하는 퍼블리케이션을 정의해요:
apiVersion: postgresql.cnpg.io/v1
kind: Publication
metadata:
name: publisher
spec:
cluster:
name: freddie
dbname: app
name: publisher
target:
objects:
- tablesInSchema: portal
- table:
name: users
schema: access
Publication 매니페스트의 필수 필드 (Required Fields in the Publication Manifest)
Publication 객체에 필요한 필드는 다음과 같아요:
metadata.name: KubernetesPublication객체의 고유 이름.spec.cluster.name: PostgreSQL 클러스터 이름.spec.dbname: 퍼블리케이션이 생성되는 데이터베이스 이름.spec.name: PostgreSQL에서의 퍼블리케이션 이름.spec.target: 퍼블리케이션에 포함할 테이블 또는 변경을 지정해요.
Publication 객체는 특정 Cluster를 참조해야 하며, 이는 퍼블리케이션이 생성될 위치를 결정해요. 클러스터의 primary 인스턴스가 관리하며, 필요에 따라 퍼블리케이션이 생성되거나 업데이트되도록 보장해요.
:::warning
spec.cluster 필드는 생성 후 변경할 수 없어요(immutable). 다른 Cluster에 퍼블리케이션을 만들려면 기존 것을 업데이트하지 말고 새 Publication 리소스를 만드세요.
:::
리컨실리에이션과 상태 (Reconciliation and Status)
Publication을 만든 후 CloudNativePG는 지정된 클러스터의 primary 인스턴스에서 이를 관리해요. 성공적인 리컨실리에이션 주기 후 Publication 상태는 다음을 반영해요:
applied: true: 구성이 성공적으로 적용됐음을 나타내요.observedGeneration이metadata.generation과 일치: 적용된 구성이 가장 최근 변경과 일치함을 확인해요.
리컨실리에이션 중 오류가 발생하면 status.applied가 false가 되고 status.message 필드에 오류 메시지가 포함돼요.
퍼블리케이션 제거 (Removing a publication)
publicationReclaimPolicy 필드는 Publication 객체를 삭제할 때의 동작을 제어해요:
retain(기본값): 수동 관리를 위해 퍼블리케이션을 PostgreSQL에 그대로 둬요.delete: 퍼블리케이션을 PostgreSQL에서 자동으로 제거해요.
다음 예시를 고려해 보세요:
apiVersion: postgresql.cnpg.io/v1
kind: Publication
metadata:
name: freddie-publisher
spec:
cluster:
name: freddie
dbname: app
name: publisher
target:
allTables: true
publicationReclaimPolicy: delete
이 경우 Publication 객체를 삭제하면 freddie 클러스터의 app 데이터베이스에서 publisher 퍼블리케이션도 제거돼요.
replica 클러스터에서는 데이터베이스가 읽기 전용이므로, Publication 객체를 삭제해도 publicationReclaimPolicy: delete 상태에서도 finalizer를 해제하고 PostgreSQL에서 퍼블리케이션을 제거하지 않고 Kubernetes 객체만 제거해요. 퍼블리케이션 삭제는 그것을 소유한 primary 클러스터에 맡겨져요.
구독 (Subscriptions)
PostgreSQL의 게시-구독 리플리케이션 모델에서 구독(subscription)은 데이터 변경을 소비하는 다운스트림 구성 요소를 나타내요. 구독은 퍼블리셔의 데이터베이스에 대한 연결을 설정하고 구독할 퍼블리케이션 집합(하나 이상)을 지정해요. 구독은 구독자(subscriber) 역할을 하는 지원되는 PostgreSQL 인스턴스 어디서든 만들 수 있어요.
:::info[Important] 스키마 정의는 복제되지 않으므로, 데이터 복제가 시작되기 전에 구독자에 해당 테이블이 이미 정의되어 있어야 해요. :::
CloudNativePG는 Subscription 리소스를 사용해 선언적으로 구독을 정의할 수 있게 함으로써 구독 관리를 단순화해요.
:::info
각 Subscription 객체에 정의할 수 있는 전체 속성 목록은 API 참조를 참고해 주세요.
:::
freddie 클러스터(퍼블리셔)의 app 데이터베이스에 있는 publisher 퍼블리케이션의 변경 사항을 king 클러스터(구독자)의 app 데이터베이스로 복제하려 한다고 가정해 볼게요. 다음은 Subscription 매니페스트의 예시예요:
apiVersion: postgresql.cnpg.io/v1
kind: Subscription
metadata:
name: freddie-to-king-subscription
spec:
cluster:
name: king
dbname: app
name: subscriber
externalClusterName: freddie
publicationName: publisher
위 예시에서:
- 구독 객체는
freddie-to-king-subscriber(metadata.name)로 이름 지어져요. - 구독은
king클러스터(spec.cluster.name)의app데이터베이스(spec.dbname)에 이름subscriber(spec.name)로 생성돼요. spec.externalClusterName으로 참조되는 외부freddie클러스터의publisher퍼블리케이션에 연결해요.
이 설정을 돕기 위해 freddie 외부 클러스터가 king 클러스터의 구성에 정의되어야 해요. 아래는 king 매니페스트에서 외부 클러스터를 정의하는 방법을 보여주는 예시 발췌문이에요:
externalClusters:
- name: freddie
connectionParameters:
host: freddie-rw.default.svc
user: postgres
dbname: app
:::info
externalClusters 섹션 구성에 대한 자세한 내용은 문서의 "Bootstrap" 섹션을 참고해 주세요.
:::
보시다시피 구독은 네트워크로 접근 가능한 모든 PostgreSQL 데이터베이스에 연결할 수 있어요. 이 유연성 덕분에 거의 제로 다운타임으로 Kubernetes에 데이터를 원활하게 마이그레이션할 수 있어요. 인기 있는 클라우드 기반 Database-as-a-Service(DBaaS) 플랫폼을 포함한 다양한 환경에서의 전환에 탁월한 옵션이에요.
Subscription 매니페스트의 필수 필드 (Required Fields in the Subscription Manifest)
Subscription 객체를 정의하는 데 필수 필드는 다음과 같아요:
metadata.name: 네임스페이스 내 KubernetesSubscription객체의 고유 이름.spec.cluster.name: 구독이 생성될 PostgreSQL 클러스터의 이름.spec.dbname: 구독이 생성될 데이터베이스의 이름.spec.name: PostgreSQL에 나타날 구독의 이름.spec.externalClusterName:spec.cluster.name클러스터의 구성에 정의된 외부 클러스터의 이름. 퍼블리셔 데이터베이스를 참조해요.spec.publicationName: 구독이 연결할 퍼블리셔 데이터베이스의 퍼블리케이션 이름.
Subscription 객체는 구독이 관리될 위치를 결정하는 특정 Cluster를 참조해야 해요. CloudNativePG는 지정된 클러스터의 primary 인스턴스에서 구독이 생성되거나 업데이트되도록 보장해요.
:::warning
spec.cluster 필드는 생성 후 변경할 수 없어요(immutable). 다른 Cluster에서 구독을 관리하려면 기존 것을 업데이트하지 말고 새 Subscription 리소스를 만드세요.
:::
리컨실리에이션과 상태 (Reconciliation and Status)
Subscription을 만든 후 CloudNativePG는 지정된 클러스터의 primary 인스턴스에서 이를 관리해요. 성공적인 리컨실리에이션 주기 후 Subscription 상태는 다음을 반영해요:
applied: true: 구성이 성공적으로 적용됐음을 나타내요.observedGeneration이metadata.generation과 일치: 적용된 구성이 가장 최근 변경과 일치함을 확인해요.
리컨실리에이션 중 오류가 발생하면 status.applied가 false가 되고 status.message 필드에 오류 메시지가 포함돼요.
구독 제거 (Removing a Subscription)
subscriptionReclaimPolicy 필드는 Subscription 객체를 삭제할 때의 동작을 제어해요:
retain(기본값): 수동 관리를 위해 구독을 PostgreSQL에 그대로 둬요.delete: 구독을 PostgreSQL에서 자동으로 제거해요.
다음 예시를 고려해 보세요:
apiVersion: postgresql.cnpg.io/v1
kind: Subscription
metadata:
name: freddie-to-king-subscription
spec:
cluster:
name: king
dbname: app
name: subscriber
externalClusterName: freddie
publicationName: publisher
subscriptionReclaimPolicy: delete
이 경우 Subscription 객체를 삭제하면 king 클러스터의 app 데이터베이스에서 subscriber 구독도 제거돼요.
replica 클러스터에서는 데이터베이스가 읽기 전용이므로, Subscription 객체를 삭제해도 subscriptionReclaimPolicy: delete 상태에서도 finalizer를 해제하고 PostgreSQL에서 구독을 제거하지 않고 Kubernetes 객체만 제거해요. 구독 삭제는 그것을 소유한 primary 클러스터에 맡겨져요.
페일오버에 대한 복원력 (Resilience to Failovers)
퍼블리셔의 페일오버 후에도 로지컬 리플리케이션 구독이 계속 작동하도록 하려면 CloudNativePG가 클러스터 전체에 로지컬 디코딩 슬롯을 동기화하도록 구성하세요. 자세한 안내는 로지컬 디코딩 슬롯 동기화를 참고해 주세요.
제한 사항 (Limitations)
PostgreSQL의 로지컬 리플리케이션은 공식 문서에 설명된 대로 몇 가지 고유한 제한이 있어요. 특히 다음 객체는 복제되지 않아요:
- 데이터베이스 스키마 및 DDL 명령
- 시퀀스 데이터
- 대형 객체(Large objects)
스키마 복제 해결 (Addressing Schema Replication)
스키마 복제와 관련된 첫 번째 제한은 CloudNativePG의 기능으로 쉽게 해결할 수 있어요. 예를 들어 복제해야 하는 테이블의 스키마를 복사하기 위해 import 부트스트랩 기능을 활용할 수 있어요. 또는 어떤 PostgreSQL 데이터베이스에서든 하듯이 스키마를 수동으로 만들 수도 있어요.
시퀀스 처리 (Handling Sequences)
시퀀스는 로지컬 리플리케이션을 통해 자동으로 동기화되지 않지만, CloudNativePG는 라이브 마이그레이션에서 사용할 수 있는 해결책을 제공해요.
cnpg 플러그인을 사용해 시퀀스 값을 동기화해 퍼블리셔와 구독자 데이터베이스 간의 일관성을 보장할 수 있어요.
로지컬 리플리케이션으로 라이브 마이그레이션과 메이저 Postgres 업그레이드 예시 (Example of live migration and major Postgres upgrade with logical replication)
로지컬 리플리케이션의 강력한 기능을 강조하기 위해, 이 예시는 PostgreSQL 16을 실행하는 퍼블리셔 데이터베이스(freddie)에서 최신 PostgreSQL 버전을 실행하는 구독자 데이터베이스(king)로 데이터를 복제하는 방법을 보여줘요. 이 설정을 Kubernetes 클러스터에 배포해 평가하고 직접 학습할 수 있어요.
이 예시는 로지컬 리플리케이션이 데이터 일관성을 보장하면서 PostgreSQL 버전 간 라이브 마이그레이션과 업그레이드를 어떻게 용이하게 하는지 보여줘요. 로지컬 리플리케이션과 CloudNativePG를 결합하면 Kubernetes 환경에서 그러한 시나리오를 쉽게 설정, 관리, 평가할 수 있어요.
1단계: 퍼블리셔(freddie) 설정 (Step 1: Setting Up the Publisher)
첫 번째 단계는 버전 16의 freddie PostgreSQL 클러스터를 만드는 것이에요. 클러스터는 단일 인스턴스를 포함하며, 10,000개의 숫자를 저장하는 n 테이블로 초기화된 app 데이터베이스를 포함해요. 데이터베이스의 모든 테이블을 포함하는 publisher라는 로지컬 리플리케이션 퍼블리케이션도 구성돼요.
freddie 클러스터와 그 퍼블리케이션 리소스를 설정하는 매니페스트는 다음과 같아요:
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: freddie-app
spec:
cluster:
name: freddie
name: app
login: true
replication: true
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: freddie
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:16-standard-trixie
storage:
size: 1Gi
bootstrap:
initdb:
postInitApplicationSQL:
- CREATE TABLE n (i SERIAL PRIMARY KEY, m INTEGER)
- INSERT INTO n (m) (SELECT generate_series(1, 10000))
- ALTER TABLE n OWNER TO app
---
apiVersion: postgresql.cnpg.io/v1
kind: Publication
metadata:
name: freddie-publisher
spec:
cluster:
name: freddie
dbname: app
name: publisher
target:
allTables: true
2단계: 구독자(king) 설정 (Step 2: Setting Up the Subscriber)
다음으로 최신 PostgreSQL 버전을 실행하는 king PostgreSQL 클러스터를 만들어요. 이 클러스터는 외부 클러스터 구성을 사용해 freddie 클러스터의 app 데이터베이스에서 스키마를 가져와 초기화돼요. 그런 다음 freddie의 publisher가 게시하는 변경을 소비하도록 freddie-to-king-subscription Subscription 리소스가 구성돼요.
king 클러스터와 그 구독을 설정하는 매니페스트는 다음과 같아요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: king
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:18-standard-trixie
storage:
size: 1Gi
bootstrap:
initdb:
import:
type: microservice
schemaOnly: true
databases:
- app
source:
externalCluster: freddie
externalClusters:
- name: freddie
connectionParameters:
host: freddie-rw.default.svc
user: app
dbname: app
password:
name: freddie-app
key: password
---
apiVersion: postgresql.cnpg.io/v1
kind: Subscription
metadata:
name: freddie-to-king-subscription
spec:
cluster:
name: king
dbname: app
name: subscriber
externalClusterName: freddie
publicationName: publisher
king 클러스터가 실행되면 app 데이터베이스에 연결해 n 테이블의 레코드를 세어 리플리케이션이 작동하는지 확인할 수 있어요. 다음 예시는 간단함을 위해 cnpg 플러그인이 제공하는 psql 명령을 사용해요:
kubectl cnpg psql king -- app -qAt -c 'SELECT count(*) FROM n'
10000
이 명령은 10000을 반환해야 하며, freddie 클러스터의 데이터가 king 클러스터에 성공적으로 복제되었음을 확인해요.
cnpg 플러그인을 사용하면 퍼블리셔와 구독자 간의 일관성을 보장하기 위해 기존 시퀀스를 동기화할 수도 있어요. 아래 예시는 king 클러스터의 시퀀스를 동기화하는 방법을 보여줘요:
kubectl cnpg subscription sync-sequences king --subscription=subscriber
SELECT setval('"public"."n_i_seq"', 10000);
10000
이 명령은 king 클러스터의 n_i_seq 시퀀스를 현재 값과 일치하도록 업데이트해 소스 데이터베이스와 동기화되도록 보장해요.