Beenslab Blog

PostgreSQL Read-Only 레플리카에서 데이터가 바로 안 보이는 이유 + 직접 실험해보기

beenchangseo·2025년 4월 24일Hits

🐘 PostgreSQL Read-Only 레플리카에서 데이터가 바로 안 보이는 이유 + 직접 실험해보기

서비스를 운영하다 보면 한 번쯤 이런 경험이 있습니다:

✅ 어떤 데이터를 업데이트했는데,

❌ 바로 직후 조회해보니 반영이 안 되어 있음?! 😱

이번 글에서는 PostgreSQL의 Read-Only Replica 환경에서 발생할 수 있는 데이터 복제 지연 문제를 다룹니다. 실제로 겪었던 디버깅 과정과 함께, 이를 재현할 수 있는 Node.js + Docker 실험 환경까지 공유합니다.


🧩 현상 요약

pass key 삭제 REST API 호출 → 200 OK
→ 직후 pass key 조회 시, 삭제되지 않은 값이 반환됨


⚙️ 시스템 구성

  • pgUtil: Primary 연결 (읽기/쓰기 가능)
  • roPgUtil: Read-Only Replica 연결 (읽기 전용)

🔍 재현 시나리오

  1. Primary 트랜잭션에서 row 업데이트

    BEGIN;
    UPDATE pass_key SET deleted = true WHERE id = 'abc123';
    COMMIT;
    
    
  2. 직후 Read-Only 커넥션으로 동일 row 조회

    SELECT * FROM pass_key WHERE id = 'abc123';
    
    
  • 기대 결과: deleted = true
  • 실제 결과: deleted = false

🧪 디버깅 과정 요약

  • Slonik 트랜잭션 구현 문제인가? → ❌ 문제 없음
  • 쿼리 순서 꼬임인가? → ❌ 순서 보장 확인됨
  • Primary에서만 조회하면 문제 없음 → ✅ Replica 복제 지연으로 판단

💡 결론: Read-Only Replica의 복제 지연 (Replication Lag)

PostgreSQL은 Streaming Replication 구조를 사용하며, 일반적으로 비동기(async) 방식입니다. 이는 Primary에서 COMMIT이 완료되어도, Replica 반영에는 수 밀리초 ~ 수십 밀리초의 지연이 생길 수 있다는 뜻입니다.


🐳 로컬에서 직접 실험해보기 (Docker + Node.js)

복제 지연 현상을 테스트해볼 수 있도록 Primary / Replica 구성과 테스트 코드를 함께 제공합니다.

📦 docker-compose.yml

version: '3.8'

services:
  postgres-primary:
    image: postgres:15
    container_name: postgres-primary
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - ./primary-data:/var/lib/postgresql/data
    networks:
      - pgnet

  postgres-replica:
    image: postgres:15
    container_name: postgres-replica
    depends_on:
      - postgres-primary
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: app
    ports:
      - "5433:5432"
    command:
      - bash
      - -c
      - |
        sleep 5
        echo "host replication all all trust" >> /var/lib/postgresql/data/pg_hba.conf
        echo "standby_mode = 'on'" > /var/lib/postgresql/data/recovery.conf
        echo "primary_conninfo = 'host=postgres-primary port=5432 user=postgres password=pass'" >> /var/lib/postgresql/data/recovery.conf
        echo "recovery_target_timeline = 'latest'" >> /var/lib/postgresql/data/recovery.conf
        postgres -c wal_level=replica -c hot_standby=on -c max_wal_senders=10 -c wal_keep_size=64 -c hot_standby_feedback=on -c max_standby_streaming_delay=10000
    volumes:
      - ./replica-data:/var/lib/postgresql/data
    networks:
      - pgnet

networks:
  pgnet:

❗ max_standby_streaming_delay=10000 → 최대 10초의 지연 허용 (테스트용)


🧪 테스트 코드 (Node.js + node-postgres)

import { Client } from 'pg';

const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));

const main = async () => {
  const primary = new Client({ connectionString: 'postgres://user:pass@localhost:5432/app' });
  const replica = new Client({ connectionString: 'postgres://user:pass@localhost:5433/app' });

  await primary.connect();
  await replica.connect();

  const id = 'test-id';

  console.log('🚀 Updating row in primary...');
  await primary.query('UPDATE pass_key SET deleted = true WHERE id = $1', [id]);

  console.log('🕵️ Immediately reading from replica...');
  const before = await replica.query('SELECT deleted FROM pass_key WHERE id = $1', [id]);
  console.log('Replica BEFORE delay:', before.rows[0]);

  await delay(200);
  const after = await replica.query('SELECT deleted FROM pass_key WHERE id = $1', [id]);
  console.log('Replica AFTER 200ms delay:', after.rows[0]);

  await primary.end();
  await replica.end();
};

main();


✅ 실행 결과 예시

🚀 Updating row in primary...
🕵️ Immediately reading from replica...
Replica BEFORE delay: { deleted: false }
Replica AFTER 200ms delay: { deleted: true }


🛡 실전에서 주의할 점

상황해결 방안
실시간성 높은 조회Primary에서 조회 강제 (pgUtil 등)
중요 트랜잭션 직후 조회캐시 또는 지연 후 조회 권장
복제 지연 상태 확인pg_stat_replication, pg_last_xact_replay_timestamp() 활용

📌 마무리

PostgreSQL의 레플리카는 매우 유용하지만, eventual consistency 환경이라는 사실을 잊지 말아야 합니다. 실시간 조회나 중요한 트랜잭션 직후에는 반드시 일관성을 고려한 설계가 필요합니다.

"쓰기 직후 즉시 읽는 것은 항상 Primary에서 하자."


🔗 참고 자료