사전 논리적 복제
개념

논리적 복제

gabury1고친 사람 github-actions[bot]

논리적 복제는 원본에서 무엇이 바뀌었는지를 행 단위로 풀어서 사본에 보냅니다. 디스크의 바이트를 옮기지 않고 「이 행을 이렇게 바꿨다」는 내용을 옮깁니다. 사본은 그 내용을 받아 자기 테이블에 다시 적용합니다. 그래서 받는 쪽이 버전이 다르거나 일부 테이블만 받아도 됩니다.

쉽고 빠른 이해

논리적 복제는 「주문 7번의 상태를 결제로 바꿨다」 같은 변경 내용을 다른 데이터베이스에 보내는 복제입니다. 받는 쪽은 그 내용을 읽고 자기 테이블에서 같은 행을 고칩니다.

디스크 내용을 바이트 단위로 베끼면 받는 쪽이 원본과 같은 버전, 같은 저장 방식이어야 합니다. 변경 내용을 보내면 그 제약이 풀립니다. 새 버전 서버로 옮기거나, 테이블 몇 개만 분석용 서버로 보낼 수 있습니다.

도는 모양은 셋입니다.

  1. 원본이 데이터를 고치면서 변경 기록을 남깁니다
  2. 그 기록을 「어느 테이블의 어느 행이 어떤 값이 됐다」로 풀어냅니다
  3. 사본이 풀어낸 변경을 받아 같은 순서로 적용합니다

대가가 있습니다. 풀어내는 만큼 원본이 일을 더 합니다. UPDATE 한 번이 백만 행을 바꾸면 변경도 백만 건이 갑니다.

원본이 죽었을 때 대신 설 똑같은 대기 서버가 목적이면 바이트를 베끼는 물리적 복제가 더 단순합니다.

상세

이 절은 논리적 복제가 무엇을 보내는지, 어떻게 도는지, 무엇을 얻고 무엇을 치르는지를 차례로 봅니다. 같은 일을 하는 물리적 복제와 나란히 놓고 보면 차이가 잘 보입니다.

원고 한 벌을 다른 사무실과 똑같이 맞춰 두는 방법은 둘입니다. 고칠 때마다 그 쪽을 복사기로 떠서 보낼 수 있습니다. 아니면 「3쪽 둘째 줄의 '사과'를 '배'로」 같은 수정 쪽지만 보낼 수도 있습니다. 쪽지를 받은 사무실은 자기 원고에서 그 줄을 찾아 고칩니다. 복사본을 보내는 쪽이 물리적 복제, 쪽지를 보내는 쪽이 논리적 복제입니다.

복제와 두 방식

복제는 같은 데이터를 여러 곳에 사본으로 두는 일입니다. 한 곳이 멈춰도 다른 곳이 답하게 하려고 씁니다. 읽기 요청을 여러 서버에 나눠 받으려고도 씁니다.

사본을 원본과 맞추려면 원본의 변경을 사본에 알려야 합니다. 무엇을 알리느냐에 따라 방식이 갈립니다. 데이터가 디스크에 놓인 모양을 알리면 물리적 복제입니다. 데이터가 뜻하는 행과 값을 알리면 논리적 복제입니다.

물리적 복제는 「이 파일의 이 블록이 이런 바이트가 됐다」를 보냅니다. 사본은 그 바이트를 받아 그대로 덮어씁니다. 결과는 원본과 바이트까지 같은 사본입니다. 대신 사본이 원본과 같은 저장 방식을 써야 바이트를 알아듣습니다.

논리적 복제는 「주문 테이블에서 id 가 7 인 행의 상태가 '결제'가 됐다」를 보냅니다. 사본은 이 내용을 읽고 자기 테이블에서 그 행을 고칩니다. 사본이 행을 디스크에 어떤 모양으로 담는지는 상관하지 않습니다. 그래서 저장 방식이 달라도 받을 수 있습니다.

물리적 복제 논리적 복제
보내는 것 디스크 블록의 바이트 변화 테이블 · 행 · 값의 변화
사본의 모양 원본과 바이트까지 같다 행과 값이 같다. 디스크 모양은 달라도 된다
받는 쪽 조건 원본과 같은 저장 방식 · 대개 같은 버전 같은 테이블 구조를 알아들으면 된다
범위 데이터베이스 전체 테이블 단위로 고를 수 있다

변경 기록에서 행 변경으로

대부분의 데이터베이스는 데이터를 고치기 전에 무엇을 고칠지 로그에 먼저 적습니다. 서버가 갑자기 꺼져도 이 로그를 보고 복구하기 위해서입니다. WAL(Write-Ahead Log, 미리 쓰기 로그)이 그런 로그입니다. 이 편에서는 이것을 변경 기록이라 부릅니다.

변경 기록은 원래 복구용이라 디스크 블록 수준으로 적힙니다. 물리적 복제는 이 기록을 손대지 않고 사본에 보냅니다. 논리적 복제는 한 단계를 더 거칩니다. 기록을 읽어 「어느 테이블의 어느 행이 무엇이 됐다」로 풀어냅니다. 이 풀어내기를 논리 디코딩이라고 부르는 구현도 있습니다.

풀어낸 변경은 세 종류입니다.

변경 사본이 알아야 하는 것
넣기(INSERT) 테이블 이름 · 새 행의 열 값 전부
고치기(UPDATE) 테이블 이름 · 어느 행인지 · 바뀐 열 값
지우기(DELETE) 테이블 이름 · 어느 행인지

넣기는 새 값만 있으면 됩니다. 고치기와 지우기는 사본이 자기 쪽에서 같은 행을 찾아야 합니다. 그래서 행마다 그 행을 가리키는 표지가 필요합니다. 대개 기본 키가 그 표지가 됩니다. 기본 키가 없는 테이블이면 바뀌기 전 열 값을 전부 실어 보내 행을 찾게 합니다.

한 번의 쓰기가 사본에 닿는 경로

앱이 원본에 쓴 값이 사본에 반영되기까지를 따라가 봅니다. 여기서 앱은 사용자 요청을 받아 데이터베이스에 쓰기를 보내는 서버 프로그램입니다. 커밋은 쓴 내용을 확정해서 더는 되돌리지 않기로 하는 일입니다.

sequenceDiagram
    participant 앱
    participant 원본
    participant 사본
    앱->>원본: 주문 7번을 결제로 바꿔라
    Note over 원본: 변경 기록에 먼저 적는다
    원본-->>앱: 커밋했다
    Note over 원본: 기록을 행 변경으로 풀어낸다
    원본->>사본: 주문 테이블 id 7 상태 결제
    Note over 사본: 자기 테이블에서 id 7 을 찾아 고친다

원본은 변경 기록에 적고 커밋한 뒤 앱에 답합니다. 풀어내기와 보내기는 그 뒤에 이어집니다. 그래서 사본은 원본보다 조금 늦게 같은 값에 닿습니다. 이 늦음이 복제 지연입니다.

트랜잭션은 여러 변경을 한 묶음으로 성공시키거나 한 묶음으로 취소하는 단위입니다. 트랜잭션 하나가 여러 행을 바꾸면 변경도 여러 건이 나옵니다. 논리적 복제는 이 묶음을 지킵니다. 커밋된 트랜잭션의 변경만 커밋된 순서대로 보냅니다. 취소된 트랜잭션의 변경은 사본에 가지 않습니다.

문장을 보내지 않고 행을 보내는 까닭

변경을 알리는 방법이 하나 더 있습니다. 원본이 받은 SQL(Structured Query Language, 구조적 질의 언어) 문장을 사본에 보내 다시 실행하게 하는 방식입니다. 이것을 문장 기반 복제라고 부릅니다. 보내는 양은 적습니다. 백만 행을 바꾸는 문장도 한 줄이면 갑니다.

문제는 같은 문장이 두 곳에서 다른 결과를 낼 때입니다. 지금 시각을 넣는 NOW() 가 대표적입니다. 원본에서는 10시 정각에 돕니다. 사본에서는 3초 뒤에 돕니다. 그러면 두 곳의 결제 시각이 달라집니다.

사본에 보내는 것 원본의 결제 시각 사본의 결제 시각
문장 SET 결제시각 = NOW() 10:00:00 10:00:03
행 변경 결제시각 = 10:00:00 10:00:00 10:00:00

난수를 만드는 함수도 같은 문제를 냅니다. 행을 보내면 원본에서 이미 정해진 값이 가므로 이런 어긋남이 생기지 않습니다. 그래서 논리적 복제는 문장이 아니라 행 변경을 보냅니다. 이 방식을 행 기반 복제라고 부르기도 합니다.

한 원본에서 여러 곳으로

행 변경은 저장 방식과 떨어져 있습니다. 그래서 받는 쪽이 같은 데이터베이스가 아니어도 됩니다. 행과 값을 알아듣기만 하면 됩니다.

flowchart TD
    A["원본 데이터베이스"] --> B["버전이 다른 새 서버"]
    A --> C["주문 테이블만 받는 분석 서버"]
    A --> D["검색 엔진"]

새 버전 서버는 데이터베이스를 판올림할 때 씁니다. 새 서버를 사본으로 붙여 따라잡게 둔 뒤 앱의 연결을 그쪽으로 옮깁니다. 서비스를 멈추는 시간이 그 옮기는 순간으로 줄어듭니다.

분석 서버는 필요한 테이블만 받습니다. 오래 걸리는 집계 쿼리가 원본을 느리게 만들지 않게 떼어 내는 용도입니다.

검색 엔진처럼 데이터베이스가 아닌 곳으로 보낼 때도 있습니다. 받는 쪽은 행 변경을 하나씩 받아 자기 데이터를 고칩니다.

이렇게 데이터베이스의 행 변경을 뽑아 다른 시스템을 갱신하는 일을 변경 데이터 캡처라고 부릅니다. 논리적 복제가 내는 행 변경이 그 재료가 됩니다.

치르는 것

풀어내는 일은 공짜가 아닙니다. 원본은 변경 기록을 읽고 행 변경으로 바꾸는 데 CPU(Central Processing Unit, 중앙 처리 장치) 시간과 메모리를 더 씁니다. 보내는 양도 늘 수 있습니다. UPDATE 한 번이 백만 행을 바꾸면 행 변경도 백만 건이 갑니다.

사본도 보통의 데이터베이스라 직접 쓰기를 받을 수 있습니다. 이 쓰기가 원본에서 온 변경과 부딪힐 수 있습니다.

사본에 같은 키의 행을 먼저 넣어 두면 원본에서 온 넣기가 막힙니다. 사본에서 행을 지워 두면 원본에서 온 고치기가 고칠 행을 못 찾습니다. 어느 쪽이든 적용이 멈출 수 있습니다.

테이블 구조를 바꾸는 명령은 구현에 따라 함께 넘어가지 않기도 합니다. 이때는 원본과 사본의 스키마를 따로 맞춰 둬야 합니다. 열이 한쪽에만 있으면 변경을 적용하지 못합니다.

언제 쓰고 언제 안 쓰나

논리적 복제가 맞는 경우는 사본이 원본과 달라야 할 때입니다. 버전이 다른 서버로 옮길 때, 일부 테이블만 보낼 때, 다른 종류의 시스템으로 보낼 때가 그렇습니다. 여러 원본의 테이블을 한 곳으로 모을 때도 씁니다.

원본이 죽으면 바로 대신 설 대기 서버, 곧 장애 조치용 서버에는 대개 물리적 복제를 씁니다. 바이트까지 같은 사본이라 구조 변경이나 행 표지를 따로 챙길 일이 없습니다.

관련 항목

논리적 복제가 속하는 상위 분류

복제 · 데이터베이스 · 데이터 동기화 · 분산 시스템

논리적 복제와 맞세워지는 전달 방식

물리적 복제 · 스트리밍 복제 · 문장 기반 복제 · 블록 수준 복제 · 로그 전달

논리적 복제를 이루는 구성 요소

WAL · 논리 디코딩 · 복제 슬롯 · 바이너리 로그 · 기본 키 · 트랜잭션 · 커밋

논리적 복제가 실어 나르는 변경

INSERT · UPDATE · DELETE · 행 · 스키마 · DDL

논리적 복제를 실제로 구현·채택한 제품

PostgreSQL · MySQL · Debezium · Apache Kafka · Kafka Connect

논리적 복제로 하는 작업

변경 데이터 캡처 · 무중단 업그레이드 · 데이터 마이그레이션 · 데이터 웨어하우스 · 검색 엔진 · 읽기 전용 복제본

논리적 복제에서 자주 나는 장애

복제 지연 · 복제 충돌 · 스키마 드리프트 · 데이터 불일치

다른 이름: logical replication · 로지컬 레플리케이션 · 논리 복제 · 행 기반 복제