DRBD — 분산 복제 블록 디바이스

DRBD(Distributed Replicated Block Device)를 고가용성 스토리지 복제 관점에서 심층 분석합니다. Primary/Secondary 역할 전환과 디스크 상태 머신, Protocol A/B/C 지연시간·내구성·처리량 특성 비교, Wire Protocol 패킷 구조와 핸드셰이크 과정, Activity Log와 Bitmap 기반 재동기화 알고리즘, Online Verify를 통한 무정지 무결성 검증, Multi-Volume 리소스 구성, WAN 환경 프록시 복제, drbd_device/drbd_peer_device 내부 동작과 복제 스레드 모델, 네트워크 단절 시 quorum 정책과 split-brain 예방 전략, Pacemaker/Corosync OCF RA 연동 자동화, LINSTOR 기반 대규모 볼륨 수명주기 관리, DRBD + NFS/SMB HA NAS 운영 패턴, 성능 튜닝과 모니터링/트러블슈팅 절차까지 실전 운영 기준으로 다룹니다.

전제 조건: Block I/O, Device Mapper / LVM 문서를 먼저 읽으세요. DRBD는 블록 디바이스 계층에서 동작하며 bio(Block I/O 요청)를 네트워크로 복제합니다. 블록 계층과 bio 구조를 이해해야 합니다.
일상 비유: DRBD는 실시간 복사기가 달린 공유 금고와 비슷합니다. 금고(Primary)에 무언가를 넣을 때마다 즉시 복사기가 동작하여 원격 금고(Secondary)에 동일한 사본을 만듭니다. Protocol C는 "원격 금고에 사본이 안전하게 보관되었다는 확인을 받아야만 원본 금고의 문을 닫는" 방식이고, Protocol A는 "복사기에 넣기만 하면 원본 금고 문을 바로 닫는" 방식입니다.

핵심 요약

  • DRBD — "네트워크 RAID 1"로 불리는 블록 레벨 복제 솔루션입니다. 두 (또는 그 이상) 노드의 블록 디바이스를 실시간으로 동기화합니다.
  • Primary/Secondary — Primary 노드만 읽기/쓰기 마운트가 가능합니다. Secondary는 복제본을 유지하며 대기합니다. (dual-primary 모드는 특수한 경우)
  • Protocol A/B/C — A(비동기), B(반동기), C(동기). C는 Secondary가 디스크에 쓴 후에야 Primary의 쓰기가 완료됩니다.
  • Activity Log — 현재 쓰기 중인 블록 범위를 추적합니다. 크래시 복구 시 전체 디스크 대신 AL 범위만 재동기화합니다.
  • Bitmap — 변경된 블록을 비트맵으로 추적합니다. 재연결 시 변경분만 동기화(Resync)하여 효율적입니다.
  • Online Verify — 서비스 중단 없이 Primary/Secondary 데이터 일치 여부를 해시 비교로 검증합니다.
  • Quorum — 3노드 이상 구성에서 과반수 동의가 있는 노드만 Primary가 됩니다. Split-Brain을 예방합니다.
  • LINSTOR — DRBD 위에서 동작하는 소프트웨어 정의 스토리지(SDS) 레이어입니다. Kubernetes CSI와 통합됩니다.

단계별 이해

  1. DRBD 기본 개념 파악 — DRBD는 블록 디바이스(/dev/drbd0)를 제공합니다. 이 위에 파일시스템을 올립니다.

    Primary 노드에 마운트된 파일시스템의 쓰기가 Secondary로 복제됩니다.

  2. 복제 프로토콜 이해 — Protocol C(동기)가 기본값입니다. 쓰기 완료 = Primary와 Secondary 모두 디스크에 기록된 시점입니다.

    Protocol A는 Primary 로컬 I/O 완료 = 쓰기 완료로 처리하여 성능이 높지만 Primary 장애 시 데이터 손실 가능성이 있습니다.

  3. Wire Protocol 이해 — DRBD 노드 간 TCP 연결 위에 P_DATA, P_WRITE_ACK 등의 패킷이 교환됩니다.

    커넥션 핸드셰이크, 데이터 복제, 제어 메시지가 모두 이 와이어 프로토콜 위에서 동작합니다.

  4. Activity Log와 Bitmap — AL은 현재 활성 쓰기 영역을, Bitmap은 피어와 달라진 블록을 추적합니다.

    크래시 후 AL 범위만 재동기화하고, 네트워크 단절 후에는 Bitmap 기반으로 변경분만 복제합니다.

  5. Pacemaker 통합 — DRBD OCF RA와 Pacemaker가 함께 동작합니다. 노드 장애 시 Pacemaker가 DRBD를 Promote(Secondary→Primary)하고 서비스를 이전합니다.

    NFS 서버나 Samba/ksmbd가 DRBD 디바이스 위에서 실행되므로, 페일오버 시 자동으로 스토리지도 이전됩니다.

  6. LINSTOR 확장 — LINSTOR는 여러 DRBD 볼륨을 중앙에서 관리합니다. Kubernetes에서 CSI를 통해 영구 볼륨을 자동 프로비저닝합니다.

    대규모 NAS 환경에서 볼륨 생성/삭제/크기 조정을 자동화합니다.

DRBD 개요

DRBD(Distributed Replicated Block Device)는 Linux 블록 계층에서 동작하는 네트워크 스토리지 복제 솔루션입니다. 개발사 LINBIT이 오픈소스로 공개하였으며, Linux 커널 2.6.33(2010년)에 메인라인으로 포함되었습니다.

특성내용
메인라인 포함Linux 2.6.33 (2010년, DRBD 8.3)
소스 위치drivers/block/drbd/
개발사LINBIT (오스트리아)
최신 버전DRBD 9.x (다중 복제 지원, OOB 메타데이터)
사용자 도구drbdadm, drbdsetup, drbdmeta
설정 파일/etc/drbd.conf, /etc/drbd.d/*.res
장치 노드/dev/drbd<n>
메타데이터인밴드 또는 외부(별도 디바이스/파티션)
복제 방향DRBD 8: 1:1 (2노드), DRBD 9: 1:N (최대 32노드)
프로토콜독자적 Wire Protocol (TCP 기반)
DRBD 8 vs 9: 메인라인 커널(drivers/block/drbd/)에는 DRBD 8.x가 포함되어 있습니다. DRBD 9.x는 out-of-tree 모듈로, LINBIT 저장소에서 별도로 제공됩니다. DRBD 9는 Multi-Volume, 3노드 이상 복제, Quorum 등의 기능을 추가합니다. 실무에서는 대부분 DRBD 9.x를 사용합니다.
Primary 노드 (활성) 응용 프로그램 (NFS/SMB/DB) VFS / 파일시스템 (ext4/XFS) drbd.ko (/dev/drbd0) bio 처리 + Activity Log + Bitmap 복제 스케줄링 + Wire Protocol 로컬 디스크 메타데이터 (AL+ Bitmap+GI) drbdadm/drbdsetup Pacemaker / Corosync OCF RA: ocf:linbit:drbd Wire Protocol P_DATA / P_WRITE_ACK Secondary 노드 (대기) 응용 프로그램 (대기 중) drbd.ko (/dev/drbd0) 수신 → Activity Log → 로컬 기록 Bitmap 갱신 → ACK 전송 로컬 디스크 (복제본) 메타데이터 Pacemaker / Corosync Promote 대기 (Secondary)

DRBD 복제 아키텍처

DRBD는 블록 디바이스 계층에서 동작하는 커널 모듈로, 상위 파일시스템이나 애플리케이션에는 일반 블록 디바이스처럼 보입니다. 내부적으로 로컬 디스크 I/O와 네트워크 복제를 동시에 처리합니다.

역할 모델

역할I/O 허용마운트전환 명령
Primary읽기/쓰기가능drbdadm primary r0
Secondary읽기 전용 (복제 수신만)불가drbdadm secondary r0
Dual-Primary양쪽 읽기/쓰기GFS2/OCFS2 필요allow-two-primaries yes
Dual-Primary 주의: 양쪽 모두 쓰기가 가능하므로 반드시 클러스터 파일시스템(GFS2, OCFS2)을 사용해야 합니다. ext4/XFS 같은 단일 마운트 파일시스템은 데이터 손상을 야기합니다. 실무에서는 가급적 단일 Primary 모드를 권장합니다.

연결 상태

연결 상태설명데이터 복제
StandAlone피어와 연결 끊김, 재연결 시도 안 함불가
Disconnecting연결 해제 중불가
Unconnected연결 대기 중 (재시도 간격)불가
ConnectingTCP 연결 시도 중불가
Connected핸드셰이크 완료, 정상 복제 중정상
SyncSource동기화 소스 (데이터 전송 중)Resync 중
SyncTarget동기화 대상 (데이터 수신 중)Resync 중
VerifyS / VerifyT온라인 검증 소스/대상검증 중
# 상태 확인 명령
$ drbdadm status r0
r0 role:Primary
  disk:UpToDate
  peer role:Secondary
    replication:Established peer-disk:UpToDate

# 상세 통계와 함께 확인
$ drbdsetup status r0 --verbose --statistics
r0 role:Primary suspended:no
  volume:0 minor:0 disk:UpToDate quorum:yes
    size:104857600 read:1024 written:51200 al-writes:128 bm-writes:0
  node2 role:Secondary
    volume:0 replication:Established peer-disk:UpToDate
      received:0 sent:51200 out-of-sync:0 pending:0 unacked:0

복제 프로토콜 (Protocol A/B/C)

DRBD는 데이터 신뢰성과 성능 간의 트레이드오프를 조정하는 세 가지 복제 프로토콜을 제공합니다.

Protocol A (비동기) 1. bio 수신 2. 로컬 디스크 I/O 제출 3. TCP 송신 버퍼에 복사 4. 로컬 I/O 완료 시 ACK TCP 전송은 백그라운드 Secondary 쓰기는 나중에 최고 처리량 Primary 장애 시 TCP 버퍼 내 미전송 데이터 손실 용도: 지리적 DR (WAN) RTT 영향: 없음 RPO: 수 초 ~ 수십 초 Protocol B (반동기) 1. bio 수신 2. 로컬 디스크 I/O 제출 3. 피어에 P_DATA 전송 4. 피어 TCP 수신 확인 5. 로컬+네트워크 완료 시 ACK Secondary 메모리에 수신됨 디스크 쓰기는 비동기 A보다 안전 Secondary 전원 장애 시 메모리 내 데이터 손실 RTT 영향: 1 RTT RPO: 0 (정상 시) Protocol C (동기) 1. bio 수신 2. 로컬 디스크 I/O 제출 3. 피어에 P_DATA 전송 4. 피어 디스크 쓰기 완료 대기 5. P_WRITE_ACK 수신 6. 양쪽 완료 시 ACK 완전한 데이터 보호 네트워크 RTT만큼 쓰기 레이턴시 증가 RTT 영향: 1 RTT (필수) RPO: 0 (보장)
속성Protocol AProtocol BProtocol C
완료 조건로컬 I/O + TCP 송신 버퍼로컬 I/O + 피어 TCP 수신로컬 I/O + 피어 디스크 I/O
쓰기 레이턴시로컬 디스크 레이턴시max(로컬, RTT)max(로컬, RTT + 피어 I/O)
처리량 (IOPS)로컬과 동일로컬의 70~90%로컬의 50~80%
데이터 손실 위험Primary 장애 시Secondary 전원 장애 시양쪽 동시 장애 시만
RPO수 초0 (정상 시)0 (보장)
권장 환경WAN / DR중간 거리프로덕션 HA (기본)
Protocol 선택 기준: LAN 환경의 HA 클러스터에서는 Protocol C가 기본이며 권장됩니다. WAN(수 ms 이상 RTT)에서는 Protocol A를 사용하고, DRBD Proxy를 결합하여 버퍼링을 강화합니다. Protocol B는 "금융 거래처럼 피어가 데이터를 받은 것은 확인하되, 디스크 기록까지 기다리지 않는" 중간 요구사항에 적합합니다.

Wire Protocol 패킷 구조

DRBD 노드 간 통신은 TCP 위에서 독자적인 Wire Protocol을 사용합니다. 각 패킷은 고정 크기 헤더와 가변 크기 페이로드로 구성됩니다.

DRBD 패킷 헤더 (p_header) magic(4B) + command(2B) + length(4B) + volume(2B) = 12 바이트 magic: 0x83740267 또는 0x8372 (DRBD 9) command: P_DATA P_WRITE_ACK 등 length: 페이로드 가변 크기 (바이트) volume: 볼륨 번호 Multi-Volume 구분 연결 핸드셰이크 순서 P_CONNECTION_FEATURES P_AUTH_CHALLENGE P_AUTH_RESPONSE P_STATE / P_UUIDS Connected! 데이터 복제 흐름 (Protocol C) Primary: P_DATA 전송 Secondary: 로컬 디스크 기록 Secondary: P_WRITE_ACK Primary: 완료! 재동기화 흐름 P_RS_DATA_REQUEST P_RS_DATA_REPLY P_RS_WRITE_ACK Bitmap 비트 해제

주요 패킷 타입

패킷 명령방향설명
P_DATAPrimary → Secondary쓰기 데이터 전송 (섹터 오프셋 + 데이터)
P_WRITE_ACKSecondary → Primary데이터 기록 완료 확인 (Protocol C)
P_RECV_ACKSecondary → Primary데이터 수신 확인 (Protocol B)
P_BARRIERPrimary → Secondary쓰기 에포크 경계 (순서 보장)
P_BARRIER_ACKSecondary → Primary에포크 완료 확인
P_DATA_REQUESTSecondary → Primary읽기 요청 (피어에서 읽기, 드물게 사용)
P_RS_DATA_REQUESTSyncTarget → SyncSource재동기화 데이터 요청
P_RS_DATA_REPLYSyncSource → SyncTarget재동기화 데이터 응답
P_CSUM_RS_REQUESTSyncSource → SyncTarget체크섬 기반 재동기화 요청
P_OV_REQUESTVerifyS → VerifyT온라인 검증 해시 요청
P_OV_REPLYVerifyT → VerifyS온라인 검증 해시 응답
P_STATE양방향역할/디스크/연결 상태 변경 알림
P_UUIDS양방향Generation Identifier 교환
/* include/linux/drbd.h — 패킷 헤더 구조 (DRBD 8) */
struct p_header80 {
    u32 magic;      /* DRBD_MAGIC = 0x83740267 */
    u16 command;    /* P_DATA, P_WRITE_ACK 등 */
    u16 length;     /* 페이로드 길이 */
} __packed;

/* DRBD 9 확장 헤더 */
struct p_header100 {
    u32 magic;      /* DRBD_MAGIC_100 = 0x8372 */
    u16 volume;     /* Multi-Volume 지원 */
    u16 command;
    u32 length;
    u32 pad;        /* 8바이트 정렬 */
} __packed;

/* P_DATA 페이로드 — 쓰기 데이터 */
struct p_data {
    u64 sector;     /* 시작 섹터 */
    u64 block_id;   /* epoch_entry 식별자 */
    u32 seq_num;    /* 시퀀스 번호 (순서 보장) */
    u32 dp_flags;   /* DP_HARDBARRIER, DP_FLUSH 등 */
    /* 뒤이어 실제 데이터 바이트 */
} __packed;
Generation Identifier (GI): DRBD는 각 노드의 데이터 세대를 UUID로 관리합니다. 재연결 시 GI를 비교하여 "누가 최신인지", "Split-Brain인지"를 판단합니다. GI는 4개의 UUID로 구성됩니다: current-uuid, bitmap-uuid, history-uuid[0], history-uuid[1].

커널 내부 구조

DRBD 커널 모듈(drbd.ko)은 블록 디바이스 드라이버로서 struct request_queue에 자신의 make_request 함수를 등록합니다.

핵심 구조체

/* DRBD 리소스 — 복제 단위의 최상위 컨테이너 */
struct drbd_resource {
    char                    *name;          /* 리소스 이름 (예: "r0") */
    struct list_head         devices;        /* 볼륨 목록 */
    struct list_head         connections;    /* 피어 연결 목록 */
    enum drbd_role           role;           /* R_PRIMARY / R_SECONDARY */
    struct mutex             state_mutex;    /* 상태 전환 직렬화 */
    struct kref              kref;           /* 참조 카운트 */
};

/* DRBD 디바이스 — /dev/drbd<n> */
struct drbd_device {
    struct drbd_resource  *resource;    /* 소속 리소스 */
    struct gendisk       *vdisk;       /* 블록 디바이스 구조체 */
    struct request_queue *rq;          /* 요청 큐 */
    struct drbd_backing_dev *ldev;    /* 로컬 백엔드 디바이스 */
    unsigned long         flags;       /* 상태 플래그 */
    unsigned int          vnr;         /* 볼륨 번호 */

    /* 비트맵: 변경된 블록 추적 */
    struct drbd_bitmap  *bitmap;
    /* Activity Log: 현재 쓰기 활성 구간 */
    struct lru_cache   *act_log;    /* al-extents LRU 캐시 */
    struct list_head     active_ee;   /* 처리 중인 쓰기 에포크 */
    struct list_head     sync_ee;     /* 동기화 중인 에포크 */
    struct list_head     done_ee;     /* 완료된 에포크 */
};

/* DRBD 피어 디바이스 — 연결된 원격 노드 하나 */
struct drbd_peer_device {
    struct drbd_device     *device;
    struct drbd_connection *connection;
    enum drbd_repl_state   repl_state; /* L_ESTABLISHED, L_SYNC_SOURCE 등 */
    enum drbd_disk_state   disk_state; /* D_UP_TO_DATE, D_INCONSISTENT 등 */
};

/* DRBD 연결 — 피어 노드 하나와의 TCP 연결 */
struct drbd_connection {
    struct drbd_resource   *resource;
    struct socket          *data_socket;    /* 데이터 소켓 */
    struct socket          *meta_socket;    /* 메타 소켓 (제어 메시지) */
    struct task_struct     *receiver;       /* 수신 스레드 */
    struct task_struct     *sender;         /* 송신 스레드 */
    struct task_struct     *worker;         /* 워커 스레드 */
    enum drbd_conn_state  cstate;          /* 연결 상태 */
    struct list_head       peer_devices;    /* 이 연결의 피어 디바이스 목록 */
};
drbd_resource ("r0") role, state_mutex, kref drbd_device (vol 0) vdisk, bitmap, act_log /dev/drbd0, ldev drbd_connection data/meta socket receiver/sender/worker drbd_peer_device repl_state, disk_state device + connection 연결 devices connections peer_devices peer_devices

bio 처리 흐름

/* DRBD make_request — 쓰기 요청 처리 */
static void drbd_make_request(struct request_queue *q, struct bio *bio)
{
    struct drbd_device *device = q->queuedata;

    if (bio_data_dir(bio) == WRITE) {
        /* 1. Activity Log에 쓰기 구간 등록 */
        drbd_al_begin_io(device, &peer_req->i);

        /* 2. 비트맵에 변경 구간 표시 (피어와 다름 기록) */
        drbd_bm_set_bits(device, sector, size);

        /* 3. 로컬 디스크에 bio 제출 */
        drbd_submit_peer_request(device, peer_req, bio->bi_opf);

        /* 4. 복제: 피어에게 P_DATA 패킷 전송 */
        drbd_send_dblock(peer_device, peer_req);

        /* Protocol C: P_WRITE_ACK 대기 (drbd_recv_ack) */
        /* Protocol A: TCP 송신 버퍼에 복사되면 완료 */
        /* Protocol B: 피어 TCP 수신 확인(P_RECV_ACK)까지 대기 */
    } else {
        /* 읽기: 로컬 디스크에서만 처리 */
        drbd_submit_bio(device, bio);
    }
}

스레드 모델

스레드생성 위치역할
drbd_receiverdrbd_receiver.c피어로부터 데이터 수신, P_DATA 처리, ACK 전송
drbd_senderdrbd_sender.cP_DATA 전송, 재연결 처리, Resync 스케줄링
drbd_workerdrbd_worker.c워크 큐 처리, Resync I/O, 비트맵 갱신
drbd_asenderdrbd_main.cACK 전송 전용 (Protocol C 성능 최적화)
/* drbd_receiver.c — 수신 스레드 메인 루프 */
static int drbd_receiver(struct drbd_thread *thi)
{
    struct drbd_connection *connection = thi->connection;

    while (get_t_state(thi) == RUNNING) {
        /* 패킷 헤더 읽기 */
        drbd_recv_header(connection, &pi);

        /* 명령별 핸들러 디스패치 */
        switch (pi.cmd) {
        case P_DATA:
            receive_Data(connection, &pi);
            break;
        case P_RS_DATA_REPLY:
            receive_RSDataReply(connection, &pi);
            break;
        case P_BARRIER:
            receive_Barrier(connection, &pi);
            break;
        /* ... 기타 패킷 핸들러 ... */
        }
    }
    return 0;
}

디스크 상태 머신

상태코드설명전환 조건
DisklessD_DISKLESS디스크 없음 (detached)attach 전 또는 detach 후
AttachingD_ATTACHING디스크 연결 중drbdadm attach 실행
FailedD_FAILED디스크 I/O 오류로컬 디스크 오류 발생
NegotiatingD_NEGOTIATING피어와 협상 중connect 시 상태 교환
InconsistentD_INCONSISTENT데이터 불일치Resync 시작 전/중
OutdatedD_OUTDATED최신이 아님피어가 더 최신 GI 보유
ConsistentD_CONSISTENT일관성 있음피어 연결 없이 정상 종료
UpToDateD_UP_TO_DATE최신 상태Resync 완료 후 정상 동작

Activity Log (AL)

Activity Log는 DRBD에서 가장 중요한 메타데이터 구조 중 하나입니다. 현재 활성 쓰기가 진행 중인 디스크 영역을 추적하여, 크래시 후 복구 시간을 극적으로 단축합니다.

Activity Log 동작 원리 전체 디스크 (예: 100GB = 25600 AL extents) AL #42 AL #1024 AL #5120 AL 메타데이터 (디스크 상) transaction_nr: 순환 트랜잭션 번호 extent[0..N]: 활성 extent 번호 배열 4MB extent = 1024 섹터 범위 LRU 교체: 새 영역 쓰기 시 가장 오래된 extent 교체 al-extents: 기본 1237, 최대 65534 크래시 복구 시 AL 없이: 전체 디스크 재동기화 = 100GB 전체 비교 (수 시간) AL 사용: 활성 extent만 재동기화 = 1237 x 4MB = 약 5GB (수 분) 복구 시간 97% 이상 단축!

AL 설정

# /etc/drbd.d/r0.res — Activity Log 설정
resource r0 {
  disk {
    # AL extent 수 (기본 1237, 최대 65534)
    # 하나의 extent = 4MB 범위
    al-extents 6433;      # 6433 x 4MB = 약 25GB 활성 범위

    # AL 업데이트 방식
    al-updates yes;       # 기본값: 매 쓰기마다 AL 갱신
    # al-updates no;      # 성능 최대화, 크래시 시 전체 Resync 필요
  }
}

# AL 통계 확인
$ drbdsetup status r0 --verbose --statistics
  volume:0 ... al-writes:12847 bm-writes:0
# al-writes: AL 트랜잭션 수 (높으면 랜덤 I/O 패턴)

# AL extent 크기 계산
# al-extents가 클수록 → 크래시 복구 범위 넓어짐 (느려짐)
# al-extents가 작으면 → LRU 교체 빈번 (AL I/O 오버헤드)
# 최적값: 워킹셋 크기 / 4MB = 필요한 al-extents
al-updates no 주의: al-updates no로 설정하면 AL 쓰기 오버헤드가 사라져 성능이 향상되지만, Primary 크래시 시 전체 디스크를 Resync해야 합니다. 배터리 백업(BBU) RAID 컨트롤러 등 안전한 캐시가 있는 환경에서만 사용하세요.

AL 내부 구현

/* drbd_actlog.c — Activity Log 핵심 함수 */

/* 쓰기 전: AL에 extent 등록 */
void drbd_al_begin_io(struct drbd_device *device,
                       struct drbd_interval *i)
{
    unsigned int first = i->sector >> (AL_EXTENT_SHIFT - 9);
    unsigned int last  = i->sector + (i->size >> 9) - 1;
    last >>= (AL_EXTENT_SHIFT - 9);

    wait_event(device->al_wait,
              _al_get(device, first, last));

    /* AL 트랜잭션을 메타 디바이스에 기록 */
    drbd_al_begin_io_commit(device);
}

/* 쓰기 완료 후: AL에서 extent 해제 */
void drbd_al_complete_io(struct drbd_device *device,
                          struct drbd_interval *i)
{
    lc_put(device->act_log, &al_ext->lce);
    wake_up(&device->al_wait);
}

Bitmap과 재동기화 알고리즘

DRBD의 비트맵(Bitmap)은 피어와 달라진 블록을 추적하는 핵심 메타데이터입니다. 네트워크 단절 후 재연결 시 비트맵 기반으로 변경분만 효율적으로 재동기화합니다.

1. 네트워크 단절 중 Primary: 쓰기 계속 수행 변경된 블록 → Bitmap 1로 표시 빨간색 = 변경된 블록 (out-of-sync) 2. 재연결 GI(UUID) 교환 Bitmap 비교: 동기화 방향 결정 SyncSource / SyncTarget 역할 배정 out-of-sync 크기 계산 3. Resync 수행 Bitmap의 1비트 블록만 전송 SyncSource → SyncTarget 전송 완료 → 비트 0으로 해제 완료 → Connected + UpToDate 일반 Resync Bitmap 비트=1인 블록을 무조건 전송 네트워크 대역폭 소모 큼 설정: resync-rate로 속도 제한 빠르지만 대역폭 비효율 체크섬 기반 Resync (csums) Bitmap 비트=1인 블록의 해시를 먼저 비교 해시가 같으면 전송 생략 (이미 동일) 설정: csums-alg sha256 네트워크 절약, WAN에 적합

Bitmap 및 Resync 설정

# /etc/drbd.d/r0.res — Resync 설정
resource r0 {
  net {
    # 체크섬 기반 Resync (변경되지 않은 블록 전송 생략)
    csums-alg sha256;       # 또는 crc32c (빠름), sha1

    # 데이터 무결성 검증 (선택사항)
    data-integrity-alg sha256;  # 전송 데이터 해시 검증
  }

  disk {
    # Resync 속도 제한
    resync-rate 200M;       # 초당 200MB (기본: 250K)

    # 가변 Resync 속도 (DRBD 8.4+)
    c-plan-ahead 20;        # 20 0.1초 단위 = 2초 미리 보기
    c-fill-target 50;       # 50 섹터 목표 충진량
    c-min-rate 10M;         # 최소 10MB/s 보장
    c-max-rate 500M;        # 최대 500MB/s
  }
}

# Resync 진행 상황 확인
$ drbdadm status r0
r0 role:Primary
  disk:UpToDate
  peer role:Secondary
    replication:SyncSource peer-disk:Inconsistent
    done:42.8     # 42.8% 완료

# Resync 강제 시작 (전체 동기화)
$ drbdadm invalidate-remote r0   # 피어를 무효화하고 전체 Resync
$ drbdadm invalidate r0          # 자신을 무효화하고 피어에서 받기
가변 속도 Resync (c-plan-ahead): 고정 resync-rate 대신 가변 속도를 사용하면 서비스 I/O 부하에 따라 Resync 속도를 자동 조절합니다. 서비스 I/O가 바쁘면 Resync를 늦추고, 한가하면 빠르게 진행합니다. DRBD 8.4+에서 지원됩니다.

Online Verify (무정지 무결성 검증)

Online Verify는 서비스를 중단하지 않고 Primary와 Secondary의 데이터 일치 여부를 블록 단위 해시 비교로 검증하는 기능입니다. 정기적으로 실행하여 Silent Data Corruption(비트 부패)을 조기에 발견합니다.

VerifySource (Primary) 1. 블록 읽기 2. SHA-256 해시 계산 3. P_OV_REQUEST 전송 5. 피어 해시와 비교 일치: 다음 블록으로 진행 불일치: out-of-sync 표시 (이후 Resync로 수정) VerifyTarget (Secondary) 1. P_OV_REQUEST 수신 2. 동일 블록 읽기 3. SHA-256 해시 계산 4. P_OV_REPLY로 해시 전송 데이터 전송 없음 (해시만) P_OV_REQUEST (섹터 범위) P_OV_REPLY (SHA-256 해시)
# /etc/drbd.d/r0.res — Online Verify 설정
resource r0 {
  net {
    verify-alg sha256;      # 검증 해시 알고리즘
  }
}

# 수동 Online Verify 실행
$ drbdadm verify r0

# 진행 상황 확인
$ drbdadm status r0
r0 role:Primary
  disk:UpToDate
  peer role:Secondary
    replication:VerifyS peer-disk:UpToDate
    done:67.3

# cron으로 주간 스케줄 (매주 일요일 새벽 2시)
$ crontab -e
0 2 * * 0 /usr/sbin/drbdadm verify r0

# 검증 결과 확인
$ dmesg | grep "Online verify"
drbd r0: Online verify done. sector count=204800000 oos=0  # oos=0: 불일치 없음
drbd r0: Online verify done. sector count=204800000 oos=8  # oos=8: 8 섹터 불일치

# 불일치 발견 시 수동 Resync
$ drbdadm disconnect r0
$ drbdadm connect r0
# → 자동으로 out-of-sync 블록만 재동기화
Online Verify vs csums-alg: verify-alg는 정기적 무결성 검증에, csums-alg는 Resync 시 네트워크 절약에 사용됩니다. 둘 다 해시를 사용하지만 목적이 다릅니다. 프로덕션에서는 둘 모두 설정하는 것을 권장합니다.

Multi-Volume 리소스

DRBD 9는 하나의 리소스 내에 여러 볼륨을 포함할 수 있습니다. 모든 볼륨이 같은 TCP 연결을 공유하여 연결 오버헤드를 줄이고, 볼륨 간 쓰기 순서를 보장합니다.

# /etc/drbd.d/multi.res — Multi-Volume 설정
resource multi {
  options {
    quorum majority;
    on-no-quorum io-error;
  }

  volume 0 {                     # 데이터 볼륨
    device /dev/drbd0;
    disk   /dev/vg0/data;
    meta-disk internal;
  }

  volume 1 {                     # 로그 볼륨
    device /dev/drbd1;
    disk   /dev/vg0/log;
    meta-disk internal;
  }

  volume 2 {                     # 메타데이터 볼륨
    device /dev/drbd2;
    disk   /dev/vg0/meta;
    meta-disk internal;
  }

  on node1 { address 10.0.0.1:7789; }
  on node2 { address 10.0.0.2:7789; }
  on node3 { address 10.0.0.3:7789; }
}
특성단일 볼륨Multi-Volume
TCP 연결 수리소스당 2개 (data + meta)리소스당 2개 (공유)
쓰기 순서 보장볼륨 내에서만모든 볼륨에 걸쳐 보장
역할 전환개별리소스 단위 일괄 전환
볼륨 추가N/A온라인 추가 가능 (DRBD 9)
용도단순 복제DB (데이터+WAL+메타) 일괄 복제
DB 운영 패턴: PostgreSQL이나 MySQL 운영 시 데이터 디렉터리, WAL/binlog, 임시 테이블스페이스를 각각 별도 DRBD 볼륨으로 분리하면, I/O 특성에 맞는 개별 튜닝이 가능하면서도 원자적 페일오버가 보장됩니다.

WAN 환경과 DRBD Proxy

WAN(Wide Area Network) 환경에서 DRBD를 사용할 때의 핵심 과제는 높은 RTT(Round-Trip Time)와 제한된 대역폭입니다. DRBD Proxy는 이 문제를 버퍼링과 압축으로 해결합니다.

Primary 노드 drbd.ko Protocol A (TCP 버퍼 즉시 완료) 쓰기 레이턴시: 낮음 LAN (RTT < 1ms) 로컬 Proxy 버퍼링 (수 GB) lz4/zstd 압축 TCP 최적화 WAN 버퍼링 흡수 WAN RTT: 50~200ms 원격 Proxy 디컴프레스 버퍼 → DRBD 전달 흐름 제어 Secondary 노드 drbd.ko 복제 수신 로컬 디스크 기록 DR 사이트 원격 데이터센터
# /etc/drbd.d/dr.res — WAN DRBD Proxy 설정
resource dr {
  protocol A;                    # WAN에서는 Protocol A 필수

  net {
    sndbuf-size 0;               # TCP 자동 튜닝
    rcvbuf-size 0;
    max-buffers 36k;             # 충분한 버퍼 (36000)
    max-epoch-size 36k;
  }

  # Proxy 설정
  proxy {
    memlimit 2G;                 # 프록시 버퍼 메모리 2GB
    plugin {
      zstd 4;                    # zstd 압축 레벨 4
    }
  }

  on primary-site {
    address 10.0.0.1:7789;
    proxy on primary-site {
      inside  127.0.0.1:7789;   # DRBD → 로컬 Proxy
      outside 203.0.113.1:7789; # 외부 주소 (WAN)
    }
  }

  on dr-site {
    address 10.0.1.1:7789;
    proxy on dr-site {
      inside  127.0.0.1:7789;
      outside 198.51.100.1:7789;
    }
  }
}

# Proxy 상태 확인
$ drbd-proxy-ctl -c "show drbd-proxy"
... buffer usage: 23% (471MB/2048MB)
... compression ratio: 3.2:1
... transfer rate: 45MB/s (compressed)
환경ProtocolProxyRPO처리량
LAN (RTT < 1ms)C불필요0로컬의 50~80%
캠퍼스 (RTT 1~5ms)B 또는 C선택0RTT에 반비례
WAN (RTT 10~100ms)A필수수 초대역폭 한계
대륙 간 (RTT > 100ms)A필수 + 압축수십 초대역폭 x 압축률

Quorum 메커니즘

DRBD 9.x에서 도입된 Quorum은 3노드 이상 클러스터에서 Split-Brain을 예방하는 메커니즘입니다. 과반수(N/2 + 1)의 노드가 동의해야 Primary가 될 수 있습니다.

Node 1 (Primary) disk: UpToDate Quorum: 3/3 votes I/O: 정상 Node 2 (Secondary) disk: UpToDate 복제: 정상 Promote 대기 Node 3 (Secondary) disk: UpToDate 복제: 정상 Promote 대기 네트워크 분리 시나리오 (Node1 vs Node2+3) Node 1: 1 vote (과반수 미달) → I/O 중단 (on-no-quorum io-error) → Primary 유지 불가 Node 2+3: 2 votes (과반수 달성) → Node 2를 새 Primary로 Promote → 서비스 계속 (Split-Brain 방지)
# /etc/drbd.d/r0.res — 3노드 Quorum 설정
resource r0 {
  options {
    quorum majority;           # 과반수 Quorum
    on-no-quorum io-error;     # Quorum 없을 때 I/O 오류 반환
    # on-no-quorum suspend-io;  # 또는 I/O를 대기 상태로 유지
    # on-no-quorum io-fence-peer; # 또는 피어를 펜싱

    # Quorum 최소 redundancy 설정 (DRBD 9.1+)
    quorum-minimum-redundancy 1; # 최소 1개 피어 복제본 필요
  }

  volume 0 {
    device /dev/drbd0;
    disk /dev/sdb1;
    meta-disk internal;
  }

  on node1 { address 192.168.10.1:7789; }
  on node2 { address 192.168.10.2:7789; }
  on node3 { address 192.168.10.3:7789; }
}
시나리오투표수Quorum처리
3/3 노드 정상3 votes달성 (2 필요)정상 동작
2/3 노드 정상2 votes달성정상 동작 (1노드 장애 허용)
1/3 노드만 남음1 vote미달I/O 중단 또는 오류
네트워크 분리 (1 vs 2)1 vs 22측 달성Split-Brain 예방
5노드 중 3노드 정상3 votes달성 (3 필요)정상 동작
5노드 중 2노드 정상2 votes미달I/O 중단
2노드 Quorum: 2노드 클러스터에서는 과반수가 2이므로 한 노드 장애 시 Quorum을 달성할 수 없습니다. 이 경우 quorum-minimum-redundancy 0으로 설정하거나, 외부 Quorum 디바이스(SCSI Persistent Reservation)를 사용해야 합니다. 가장 좋은 방법은 3번째 노드를 diskless로 추가하는 것입니다.

Split-Brain 탐지 및 복구

Split-Brain은 네트워크 분리로 두 노드가 동시에 Primary가 되어 독립적으로 데이터를 변경한 상황입니다. DRBD는 재연결 시 이를 탐지하고 복구 정책을 적용합니다.

Split-Brain 탐지

# dmesg에서 Split-Brain 확인
$ dmesg | grep "Split-Brain"
drbd r0/0 drbd0: Split-Brain detected, dropping connection!

# DRBD 상태 확인
$ drbdadm status
r0 role:Primary
  disk:UpToDate
  peer connection:StandAlone    # 연결 끊김 상태

# Split-Brain 복구 — 승자/패자 결정 후 수동 해결

# 패자 노드에서 (데이터 포기)
$ drbdadm disconnect r0
$ drbdadm secondary r0
$ drbdadm -- --discard-my-data connect r0

# 승자 노드에서
$ drbdadm connect r0
# → Resync 시작: 승자 → 패자 방향으로 데이터 동기화

자동 복구 정책

# /etc/drbd.d/r0.res 자동 복구 설정
resource r0 {
  net {
    # Split-Brain 발생 시 자동 복구 정책
    after-sb-0pri discard-younger-primary;  # Primary 없을 때: 더 최근에 Primary된 쪽 포기
    after-sb-1pri discard-secondary;        # Primary 1개일 때: Secondary 데이터 포기
    after-sb-2pri disconnect;               # Primary 2개일 때: 연결 해제 (수동 처리)
  }

  handlers {
    # Split-Brain 알림 핸들러
    split-brain "/usr/lib/drbd/notify-split-brain.sh admin@example.com";
  }
}
정책0pri 상황1pri 상황2pri 상황
disconnect연결 해제연결 해제연결 해제
discard-younger-primary더 최근 Primary 포기--
discard-secondary-Secondary 포기-
discard-least-changes변경 적은 쪽 포기--
call-pri-lost-after-sb--핸들러 호출
Split-Brain 데이터 손실: 자동 복구 정책은 한쪽 노드의 데이터를 포기합니다. after-sb-2pri disconnect를 기본으로 하고, 반드시 관리자가 두 노드의 데이터를 비교한 후 수동으로 승자를 결정하는 것이 안전합니다. Quorum을 사용하면 Split-Brain 자체를 예방할 수 있습니다.

Pacemaker/Corosync 통합

Pacemaker는 DRBD OCF Resource Agent를 통해 DRBD 역할(Primary/Secondary)과 상위 서비스(NFS, Samba 등)를 함께 관리합니다. 노드 장애 시 자동 페일오버가 이루어집니다.

Pacemaker 설정 예제 (2노드 HA)

# Corosync 클러스터 초기화
$ pcs host auth node1 node2
$ pcs cluster setup ha-cluster node1 node2
$ pcs cluster start --all

# STONITH 설정 (필수 — 없으면 Split-Brain 위험)
$ pcs stonith create fence_node1 fence_ipmilan \
    ipaddr=192.168.1.101 login=admin passwd=secret \
    pcmk_host_list=node1
$ pcs stonith create fence_node2 fence_ipmilan \
    ipaddr=192.168.1.102 login=admin passwd=secret \
    pcmk_host_list=node2

# DRBD 리소스 에이전트 등록
$ pcs resource create drbd_r0 ocf:linbit:drbd \
    drbd_resource=r0 \
    op monitor interval=20 role=Promoted \
    op monitor interval=30 role=Unpromoted

# Master/Slave 프로모션 가능한 리소스 그룹
$ pcs resource promotable drbd_r0 \
    promoted-max=1 promoted-node-max=1 \
    clone-max=2 clone-node-max=1 \
    notify=true

# 파일시스템 마운트 리소스 (DRBD Primary 노드에서만)
$ pcs resource create fs_r0 Filesystem \
    device="/dev/drbd0" \
    directory="/mnt/drbd" \
    fstype="xfs"

# NFS 서버 리소스
$ pcs resource create nfs_server systemd:nfs-server

# NFS IP (Floating IP)
$ pcs resource create nfs_ip IPaddr2 \
    ip=192.168.1.100 cidr_netmask=24

# 의존성 설정: DRBD Primary → 파일시스템 → NFS → IP
$ pcs constraint order promote drbd_r0-clone then start fs_r0
$ pcs constraint order start fs_r0 then start nfs_server
$ pcs constraint order start nfs_server then start nfs_ip

# 같은 노드에서 실행하도록 colocation 설정
$ pcs constraint colocation add fs_r0 with promoted drbd_r0-clone
$ pcs constraint colocation add nfs_server with fs_r0
$ pcs constraint colocation add nfs_ip with nfs_server

페일오버 흐름

# 수동 페일오버 테스트
$ pcs node standby node1

# Pacemaker 자동 처리 순서:
# 1. node1 STONITH (서버 전원 차단)
# 2. node2에서 drbd_r0 Promote (Secondary → Primary)
# 3. /dev/drbd0을 /mnt/drbd에 마운트
# 4. nfs-server 시작
# 5. Floating IP를 node2로 이전
# → NFS 클라이언트는 IP 그대로 재연결

# 페일오버 소요 시간 (목표)
# STONITH: 10~30초 (IPMI 기반)
# DRBD Promote: 즉시 (UpToDate인 경우)
# XFS 마운트: 1~5초 (저널 리플레이)
# NFS 서비스: 2~5초
# 총 소요: 약 15~45초

# 클러스터 상태 확인
$ pcs status
$ drbdadm status

LINSTOR 볼륨 관리

LINSTOR는 DRBD 기반 소프트웨어 정의 스토리지(SDS) 솔루션입니다. LINBIT에서 개발했으며, 여러 DRBD 볼륨을 중앙에서 프로비저닝하고 Kubernetes CSI와 통합됩니다.

Kubernetes / 사용자 애플리케이션 PVC 요청 → StorageClass: linstor-drbd LINSTOR CSI Driver LINSTOR Controller (REST API :3370) Satellite + DRBD (node1) Satellite + DRBD (node2) Satellite + DRBD (node3)
# LINSTOR Controller와 Satellite 설치
$ systemctl enable --now linstor-controller  # 중앙 관리 서버
$ systemctl enable --now linstor-satellite   # 각 노드

# 노드 등록
$ linstor node create node1 192.168.1.1
$ linstor node create node2 192.168.1.2
$ linstor node create node3 192.168.1.3

# 스토리지 풀 생성 (LVM thin 기반)
$ linstor storage-pool create lvmthin node1 DrbdPool vg0/thin_pool
$ linstor storage-pool create lvmthin node2 DrbdPool vg0/thin_pool
$ linstor storage-pool create lvmthin node3 DrbdPool vg0/thin_pool

# 리소스 그룹 생성 (자동 배치 정책)
$ linstor resource-group create ha-group \
    --place-count 2 \
    --storage-pool DrbdPool

# 볼륨 그룹 생성
$ linstor volume-group create ha-group

# 리소스 생성 (자동 2개 복제)
$ linstor resource-group spawn-resources ha-group myVol 100GiB

# 생성된 DRBD 디바이스 확인
$ linstor resource list
$ drbdadm status myVol

Kubernetes CSI 통합

# linstor-csi Helm 차트로 설치
$ helm repo add linstor https://charts.linstor.io
$ helm install linstor-csi linstor/linstor-csi \
    --set linstor.controllerEndpoint=http://linstor-controller:3370

# StorageClass 생성
$ kubectl apply -f - <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: linstor-drbd
provisioner: linstor.csi.linbit.com
parameters:
  linstor.csi.linbit.com/storagePool: DrbdPool
  linstor.csi.linbit.com/placementCount: "2"
  linstor.csi.linbit.com/allowRemoteVolumeAccess: "false"
EOF

# PVC 생성 시 자동 DRBD 볼륨 프로비저닝
$ kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: linstor-drbd
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 50Gi
EOF

DRBD + CTDB + NFS/SMB HA NAS 구성

DRBD는 블록 복제를 담당하고, Pacemaker/Corosync가 역할 전환을 제어하며, NFS/Samba(ksmbd)가 파일 서비스를 제공하는 완전한 HA NAS 아키텍처입니다.

===================== HA NAS 구성 예제 =====================
# 하드웨어 구성
# node1, node2: 각 1Gbps 이더넷 (서비스), 별도 10GbE (DRBD 복제)
# /dev/sdb: DRBD 백엔드 디스크
# /dev/sdc: STONITH SBD 디바이스 (공유 iSCSI)

# 1단계: DRBD 초기화
$ drbdadm create-md r0        # 메타데이터 초기화
$ drbdadm up r0               # 리소스 활성화
$ drbdadm primary --force r0  # 초기 Primary 지정

# 2단계: 파일시스템 생성 (Primary 노드에서)
$ mkfs.xfs /dev/drbd0
$ mount /dev/drbd0 /mnt/drbd

# 3단계: NFS exports 설정
$ echo '/mnt/drbd 192.168.0.0/24(rw,sync,no_subtree_check)' >> /etc/exports

# 4단계: Pacemaker 자원 설정 (위의 Pacemaker 예제 참조)
# drbd_r0 → fs_r0 → nfs_server → nfs_ip 순서로 의존성 설정

# 5단계: 클라이언트 연결
$ mount -t nfs -o vers=4.2 192.168.1.100:/mnt/drbd /mnt/nas
# Floating IP(192.168.1.100)에 마운트 → 페일오버 시 자동 재연결
구성 요소역할장애 시 동작
DRBD블록 레벨 실시간 복제Secondary → Primary 승격
Pacemaker클러스터 리소스 관리자동 페일오버 오케스트레이션
Corosync노드 멤버십, 하트비트노드 장애 감지 알림
STONITH노드 펜싱 (강제 차단)Split-Brain 방지 보장
XFS/ext4파일시스템저널 리플레이 후 마운트
NFS/Samba파일 공유 서비스새 Primary에서 재시작
Floating IP서비스 가상 IP새 노드로 IP 이전

성능 튜닝

네트워크 튜닝

# /etc/drbd.d/r0.res — 네트워크 성능 최적화
resource r0 {
  net {
    max-buffers 20000;         # 최대 버퍼 수 (기본 8000)
    max-epoch-size 20000;      # 에포크 최대 크기
    sndbuf-size 0;             # TCP 자동 튜닝 (0=auto, 권장)
    rcvbuf-size 0;             # TCP 자동 튜닝
    protocol C;

    # 무결성 검증 (디버깅용, 성능 영향)
    # data-integrity-alg sha256;

    # 압축 (WAN용)
    # use-rle yes;  # DRBD 8: 비트맵 RLE 압축
  }
}

디스크 튜닝

# /etc/drbd.d/r0.res — 디스크 성능 최적화
resource r0 {
  disk {
    al-extents 6433;           # Activity Log 크기 (최대 65534)
    al-updates yes;            # AL 갱신 (no=위험하지만 빠름)

    # 배터리 백업 RAID 컨트롤러가 있는 경우만
    # md-flushes no;           # 메타데이터 플러시 비활성화
    # disk-flushes no;         # 데이터 플러시 비활성화
    # disk-barrier no;         # 디스크 배리어 비활성화

    # Resync 속도
    resync-rate 500M;          # 고정 속도 (기본 250K)
    c-plan-ahead 20;           # 가변 속도 활성화
    c-fill-target 100;
    c-min-rate 50M;
    c-max-rate 1000M;

    # 읽기 밸런싱 (DRBD 8.4+)
    read-balancing least-pending;  # 부하 적은 노드에서 읽기
  }
}

# 시스템 레벨 튜닝
$ echo deadline > /sys/block/sdb/queue/scheduler       # I/O 스케줄러
$ echo 256 > /sys/block/sdb/queue/nr_requests          # 큐 깊이
$ sysctl -w net.core.rmem_max=16777216                # TCP 수신 버퍼 최대
$ sysctl -w net.core.wmem_max=16777216                # TCP 송신 버퍼 최대
$ sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
$ sysctl -w net.ipv4.tcp_wmem="4096 87380 16777216"

성능 벤치마크

환경Protocol순차 쓰기 (MB/s)랜덤 쓰기 (IOPS)레이턴시 (us)
로컬 SSD (비교 기준)N/A52075,00020
DRBD + 10GbEC45042,00080
DRBD + 10GbEA51070,00025
DRBD + 25GbEC49058,00055
DRBD + RDMAC50065,00035
RDMA 지원: DRBD 9는 RDMA(InfiniBand, RoCE) 전송을 지원합니다. TCP 대비 레이턴시가 크게 줄어 Protocol C에서도 로컬에 근접한 성능을 달성할 수 있습니다. transport rdmanet 블록에 추가합니다.

커널 소스 구조

경로내용핵심 함수
drivers/block/drbd/drbd_main.c블록 디바이스 등록, make_requestdrbd_make_request(), drbd_init()
drivers/block/drbd/drbd_receiver.c피어 데이터 수신 처리receive_Data(), drbd_receiver()
drivers/block/drbd/drbd_sender.c데이터 전송, Resync 스케줄링drbd_send_dblock()
drivers/block/drbd/drbd_bitmap.c변경 비트맵 관리drbd_bm_set_bits(), drbd_bm_clear_bit()
drivers/block/drbd/drbd_worker.c워커 스레드, 재동기화 I/Odrbd_worker(), w_e_end_data_req()
drivers/block/drbd/drbd_nl.cNetlink 사용자 공간 인터페이스drbd_adm_new_resource()
drivers/block/drbd/drbd_actlog.cActivity Log 구현drbd_al_begin_io(), drbd_al_complete_io()
drivers/block/drbd/drbd_state.c역할/디스크 상태 머신drbd_change_state()
drivers/block/drbd/drbd_req.c요청 처리, 에포크 관리drbd_request_prepare()
drivers/block/drbd/drbd_int.h내부 헤더 (구조체 정의)모든 핵심 구조체 선언
# 커널 소스 탐색
$ find drivers/block/drbd/ -name "*.c" | wc -l
12

# DRBD 모듈 정보
$ modinfo drbd
filename:       /lib/modules/.../kernel/drivers/block/drbd/drbd.ko
version:        8.4.11
description:    drbd - Distributed Replicated Block Device v8.4.11
author:         Philipp Reisner <phil@linbit.com>, Lars Ellenberg <lars@linbit.com>
license:        GPL

# 소스 코드 통계
$ wc -l drivers/block/drbd/*.c drivers/block/drbd/*.h
  3215 drbd_main.c
  5834 drbd_receiver.c
  2138 drbd_sender.c
  1685 drbd_bitmap.c
  1894 drbd_worker.c
  2345 drbd_nl.c
   687 drbd_actlog.c
  1456 drbd_state.c
  2879 drbd_int.h
# 총 약 25,000줄

디버깅 및 모니터링

# DRBD 전체 상태 확인
$ drbdadm status
$ cat /proc/drbd

# 이벤트 로그 실시간 모니터링
$ drbdadm events2 --statistics r0

# 비트맵 동기화 진행 상황
$ drbdsetup show r0/0  # 볼륨 상세 정보
$ drbdsetup status r0 --verbose --statistics

# 커널 로그에서 DRBD 메시지 확인
$ dmesg | grep drbd
$ journalctl -k -u drbd

# 성능 통계
$ cat /sys/kernel/debug/drbd/r0/0/proc_drbd

# Resync 강제 시작 (전체 동기화)
$ drbdadm invalidate-remote r0

# 모니터링 스크립트 (Prometheus Exporter)
# drbd_exporter: DRBD 메트릭을 Prometheus로 수집
$ drbd_exporter --listen 0.0.0.0:9370

# 주요 모니터링 지표
# - drbd_connection_state (Connected/StandAlone)
# - drbd_disk_state (UpToDate/Inconsistent)
# - drbd_out_of_sync_bytes (남은 동기화 바이트)
# - drbd_send_rate / drbd_recv_rate (전송/수신 속도)
# - drbd_al_writes (Activity Log 쓰기 수)

트러블슈팅 체크리스트

증상원인해결 방법
StandAlone 상태Split-Brain 또는 수동 disconnectGI 비교 후 --discard-my-data connect
Resync 느림resync-rate 낮음 또는 I/O 경합resync-rate 증가, c-plan-ahead 활성화
Inconsistent 상태 지속Resync 미완료 또는 실패drbdadm invalidate r0로 강제 재동기화
WFConnection 상태TCP 연결 실패방화벽(7789포트), 네트워크 연결 확인
높은 al-writes랜덤 I/O 패턴, al-extents 부족al-extents 증가 (워킹셋 크기/4MB)
Protocol C 레이턴시 높음네트워크 RTT 높음전용 네트워크 사용, RDMA 고려
SplitBrain detected양쪽 모두 Primary로 동작승자 결정 후 패자에서 --discard-my-data
Diskless 상태로컬 디스크 오류디스크 교체 후 drbdadm attach r0
# bpftrace로 DRBD bio 처리 레이턴시 측정
$ bpftrace -e '
kprobe:drbd_make_request { @start[tid] = nsecs; }
kretprobe:drbd_make_request /@start[tid]/ {
    @us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}'

RDMA 전송 계층

DRBD 9는 TCP/IP 외에 RDMA(Remote Direct Memory Access) 전송을 지원합니다. InfiniBand, RoCE(RDMA over Converged Ethernet), iWARP를 통해 네트워크 복제 레이턴시를 극적으로 줄일 수 있습니다.

RDMA 아키텍처

Primary Node 애플리케이션 DRBD 모듈 TCP/IP 소켓 RDMA Verbs TCP 스택 RNIC HW 네트워크 Secondary Node 대기 (Standby) DRBD 모듈 TCP/IP 소켓 RDMA Verbs RNIC HW
전송레이턴시대역폭CPU 사용요구사항
TCP/IP (1GbE)~100µs~120MB/s높음표준 NIC
TCP/IP (10GbE)~50µs~1.2GB/s높음10G NIC
TCP/IP (25GbE)~30µs~3GB/s매우 높음25G NIC
RoCEv2 (25GbE)~5µs~3GB/s매우 낮음RoCE NIC
InfiniBand (HDR)~1µs~25GB/s최소IB HCA + Switch
# /etc/drbd.d/r0.res — RDMA 전송 설정
resource r0 {
    net {
        # RDMA 전송 사용 (SDP 또는 ssocks)
        transport rdma;

        # RDMA 관련 파라미터
        sndbuf-size     1048576;   # 1MB 송신 버퍼
        rcvbuf-size     1048576;   # 1MB 수신 버퍼
        max-buffers     8192;      # 최대 전송 버퍼 수
        max-epoch-size  8192;      # 에포크당 최대 쓰기
    }

    on node1 {
        device    /dev/drbd0;
        disk      /dev/sdb1;
        address   192.168.10.1:7789;  # RDMA 주소
    }
    on node2 {
        device    /dev/drbd0;
        disk      /dev/sdb1;
        address   192.168.10.2:7789;
    }
}
RDMA 장점: RDMA는 CPU를 우회하여 NIC 하드웨어가 직접 메모리 간 데이터를 전송합니다. Protocol C(동기 복제) 사용 시 TCP 대비 쓰기 레이턴시가 10~20배 감소하여, 데이터베이스 HA 환경에서 동기 복제의 성능 페널티를 최소화합니다.

DRBD Reactor 자동화

DRBD Reactor는 DRBD 이벤트에 반응하여 자동으로 서비스를 시작/중지하는 데몬입니다. Pacemaker/Corosync보다 가벼운 HA 솔루션으로, 2노드 구성에 적합합니다.

# /etc/drbd-reactor.d/r0.toml
[[promoter]]
id = "r0-promoter"

[promoter.resources.r0]
# Primary 승격 시 실행할 서비스
start = [
    "systemctl start postgresql",
    "systemctl start keepalived",
]
# Secondary 강등 시 실행할 서비스
stop = [
    "systemctl stop keepalived",
    "systemctl stop postgresql",
]

# Quorum 손실 시 동작
on-quorum-loss = "freeze-io"

# 자동 승격 조건
[promoter.resources.r0.auto-promote]
# 디스크 상태가 UpToDate인 노드가 자동 Primary
diskless = false
DRBD Reactor 이벤트 흐름 DRBD 이벤트 drbdsetup events2 drbd-reactor 이벤트 파싱 + 룰 매칭 TOML 설정 기반 승격 → 서비스 시작 연결 복구 → 재동기화 Quorum 손실 → I/O 정지 Pacemaker vs DRBD Reactor 비교 Pacemaker: 범용 클러스터 (복잡, 다중 리소스) | Reactor: DRBD 특화 (간결, 2노드 최적) Reactor: systemd 서비스 1개, 설정 파일 1개, 장애 감지 <1초
기능Pacemaker/CorosyncDRBD Reactor
아키텍처범용 클러스터 매니저DRBD 이벤트 기반 데몬
설정 복잡도높음 (CIB XML)낮음 (TOML)
지원 노드 수다중 노드 (16+)2~4 노드
장애 감지 시간~5초 (기본값)<1초
의존성Pacemaker + Corosync + resource-agentsdrbd-reactor 단일 패키지
리소스 관리VIP, 파일시스템, 서비스 등systemd 서비스 연동

DRBD 9 메시 복제

DRBD 9는 2노드 제한을 넘어 최대 32개 노드 간 메시 복제를 지원합니다. 모든 노드가 서로 직접 연결되는 full-mesh 토폴로지를 사용합니다.

DRBD 9: 4노드 메시 복제 Node 1 Primary Node 2 Secondary Node 3 Secondary Node 4 Secondary Primary→Secondary (동기 복제) Secondary↔Secondary (비트맵 교환)
# /etc/drbd.d/r0.res — 4노드 메시 설정
resource r0 {
    net {
        protocol C;
        # 메시 연결에서 각 노드 쌍은 독립적인 TCP 연결
    }

    # connection-mesh: 모든 노드 쌍을 자동 연결
    connection-mesh {
        hosts node1 node2 node3 node4;
    }

    on node1 {
        node-id 0;
        address 10.0.0.1:7789;
        volume 0 {
            device    /dev/drbd0;
            disk      /dev/sdb1;
            meta-disk internal;
        }
    }
    on node2 {
        node-id 1;
        address 10.0.0.2:7789;
        volume 0 {
            device    /dev/drbd0;
            disk      /dev/sdb1;
            meta-disk internal;
        }
    }
    on node3 {
        node-id 2;
        address 10.0.0.3:7789;
        volume 0 {
            device    /dev/drbd0;
            disk      /dev/sdb1;
            meta-disk internal;
        }
    }
    on node4 {
        node-id 3;
        address 10.0.0.4:7789;
        volume 0 {
            device    /dev/drbd0;
            disk      /dev/sdb1;
            meta-disk internal;
        }
    }
}
메시 복제 동작 원리:
  • Primary 노드가 쓰기를 받으면 모든 Secondary에 동시에 복제합니다
  • Secondary 간에는 비트맵 정보만 교환하여 재동기화 최적화에 활용합니다
  • Quorum은 과반수(N/2+1) 노드가 UpToDate여야 쓰기를 허용합니다
  • 장애 노드 복구 시 비트맵 기반 증분 재동기화로 빠르게 복구합니다
노드 수TCP 연결 수Quorum 최소장애 허용용도
21없음*0 (fencing 필요)기본 HA
3321Quorum 지원 HA
4631고가용성
51032지리적 분산
2노드 Quorum: 2노드에서는 과반수를 결정할 수 없으므로 quorum majority가 의미가 없습니다. 대신 quorum off + STONITH(fencing)를 사용하거나, Diskless 3번째 노드를 추가하여 Quorum을 확보합니다.

I/O 흐름 상세

DRBD의 쓰기 I/O는 커널 블록 계층을 통해 처리됩니다. drbd_make_request()가 bio를 가로채어 로컬 디스크와 원격 노드에 동시에 전달합니다.

DRBD Protocol C 쓰기 흐름 1. 애플리케이션 write() 2. VFS → 파일시스템 → bio 3. drbd_make_request(bio) 로컬 원격 4a. 로컬 디스크 쓰기 5a. 로컬 완료 통보 4b. 네트워크 전송 5b. 원격 디스크 쓰기 6b. WriteAck 수신 7. 양쪽 완료 → bio_endio()
/* drivers/block/drbd/drbd_req.c — 요청 처리 핵심 (간략화) */
static blk_qc_t drbd_make_request(struct request_queue *q,
                                     struct bio *bio)
{
    struct drbd_device *device = q->queuedata;
    struct drbd_request *req;

    /* 1. DRBD 요청 구조체 할당 */
    req = drbd_req_new(device, bio);

    /* 2. Activity Log 업데이트 (쓰기만) */
    if (bio_data_dir(bio) == WRITE) {
        drbd_al_begin_io(device, &req->i);
        /* AL 엔트리 확보: I/O 스로틀 가능 */
    }

    /* 3. 로컬 디스크 제출 */
    if (device->ldev) {
        req->private_bio = bio_alloc_clone(bio, GFP_NOIO);
        submit_bio_noacct(req->private_bio);
    }

    /* 4. 원격 복제 제출 */
    if (device->state.conn >= C_CONNECTED) {
        drbd_send_dblock(device->peer, req);
        /* Protocol C: WriteAck 대기 상태 진입 */
    }

    /* 5. 완료 추적: 양쪽 모두 완료될 때까지 대기 */
    drbd_req_complete(req, &m);
    return BLK_QC_T_NONE;
}
Protocol별 완료 조건:
  • Protocol A (비동기): 로컬 디스크 완료 + 네트워크 송신 버퍼 전달 = bio 완료
  • Protocol B (반동기): 로컬 디스크 완료 + 원격 수신 확인(RecvAck) = bio 완료
  • Protocol C (동기): 로컬 디스크 완료 + 원격 디스크 완료(WriteAck) = bio 완료

재동기화 심화

재동기화 유형

유형트리거동작소요 시간
전체 재동기화 (Full Sync)수동 invalidate, 첫 연결전체 디스크 복사디스크 크기 / 네트워크 속도
비트맵 재동기화 (Bitmap Resync)일시적 연결 끊김 후 재연결변경된 영역만 복사변경량 / 네트워크 속도
온라인 검증 (Online Verify)cron / 수동SHA-256 비교, 불일치만 복사디스크 크기 / 읽기 속도
c-plan-ahead 적응형자동네트워크 대역폭에 맞춰 조절동적

적응형 재동기화 (c-plan-ahead)

# 적응형 재동기화 설정
disk {
    # 고정 속도 재동기화 (기본)
    resync-rate 100M;  # 100MB/s 상한

    # 적응형 재동기화 (권장)
    c-plan-ahead  20;     # 20초 미리보기 (0=비활성)
    c-delay-target 10;    # 목표 전송 지연 (10 × 0.1초 = 1초)
    c-fill-target  0;      # 목표 버퍼 채움량 (0=자동)
    c-max-rate     250M;  # 최대 전송 속도
    c-min-rate     10M;   # 최소 전송 속도
}
c-plan-ahead 동작 원리: 적응형 재동기화는 현재 네트워크 상태를 모니터링하여 재동기화 속도를 자동 조절합니다. 애플리케이션 트래픽이 많으면 재동기화를 줄이고, 유휴 시 최대 속도로 재동기화합니다. 이로써 운영 중 서비스 영향을 최소화하면서도 빠른 복구를 달성합니다.

데이터 무결성

DRBD는 네트워크 전송 중 데이터 손상을 탐지하기 위한 체크섬 기능을 제공합니다.

# 네트워크 데이터 무결성 설정
net {
    # 전송 데이터 체크섬 (선택적)
    data-integrity-alg sha256;  # crc32c, sha1, sha256

    # TCP keepalive
    ping-int      10;     # 10초 간격 ping
    ping-timeout  5;      # 5 × 0.1초 = 0.5초 타임아웃
    timeout       60;     # 6초 연결 타임아웃
}

disk {
    # 재동기화/검증 체크섬
    resync-after   0;         # 자동 재동기화 순서
    verify-alg     sha256;     # 온라인 검증 알고리즘
    csums-alg      sha256;     # 재동기화 체크섬 (변경 탐지)
}
알고리즘CPU 비용정밀도용도
crc32c매우 낮음 (HW 가속)32비트빠른 무결성 검사
sha1중간160비트일반 검증
sha256높음256비트고신뢰 검증
data-integrity-alg 성능 영향: 모든 네트워크 패킷에 체크섬을 계산하므로 CPU 오버헤드가 발생합니다. 고성능 환경에서는 crc32c(CPU 가속 활용)를 사용하거나, 비활성화하고 TCP 체크섬에 의존하는 것을 고려하세요.

DRBD + Kubernetes

LINSTOR CSI 플러그인을 통해 Kubernetes에서 DRBD 볼륨을 PersistentVolume으로 사용할 수 있습니다.

# StorageClass 정의 (LINSTOR CSI)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: linstor-drbd
provisioner: linstor.csi.linbit.com
parameters:
  # DRBD 복제 수
  autoPlace: "2"
  # 스토리지 풀
  storagePool: "lvm-thin"
  # DRBD 리소스 그룹
  resourceGroup: "drbd-default"
  # 파일시스템
  csi.storage.k8s.io/fstype: "xfs"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

---
# PVC 요청
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: linstor-drbd
  resources:
    requests:
      storage: 100Gi
Kubernetes: Pod → PVC → StorageClass LINSTOR CSI Driver (Volume 프로비저닝) LINSTOR Controller + Satellite (리소스 관리) DRBD 복제 (Node 1) DRBD 복제 (Node 2) DRBD 복제 (Node 3)
LINSTOR CSI 장점:
  • 볼륨 자동 프로비저닝/삭제 (Dynamic PV)
  • Pod 마이그레이션 시 자동 DRBD 연결 전환
  • 볼륨 스냅샷/복원 (VolumeSnapshot API)
  • 볼륨 확장 (Online resize)
  • 토폴로지 인식 스케줄링 (Pod가 DRBD 복제본이 있는 노드에 배치)

고급 튜닝 파라미터

파라미터기본값권장 (SSD)설명
al-extents12376433Activity Log 크기 (4MB 단위, 워킹셋 커버)
max-buffers20488192최대 미완료 네트워크 버퍼
max-epoch-size20488192에포크당 최대 쓰기 요청
sndbuf-size0 (자동)1048576TCP 송신 버퍼 (1MB)
rcvbuf-size0 (자동)1048576TCP 수신 버퍼 (1MB)
unplug-watermark16128블록 계층 언플러그 트리거
c-plan-ahead0 (비활성)20적응형 재동기화 미리보기 (초)
c-max-rate-250M적응형 최대 재동기화 속도
disk-barrieryesno (배터리 RAID)디스크 배리어 사용 여부
disk-flushesyesno (배터리 RAID)디스크 플러시 사용 여부
# AL 크기 계산 공식
# al-extents = 워킹셋(GB) × 256
# 예: 25GB 워킹셋 → 25 × 256 = 6400 (최대 6433)

# 네트워크 튜닝 (DRBD 전용 인터페이스)
ip link set eth1 txqueuelen 10000
ethtool -G eth1 rx 4096 tx 4096
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"

# I/O 스케줄러 설정 (NVMe는 none, HDD는 mq-deadline)
echo none > /sys/block/sdb/queue/scheduler
echo 256 > /sys/block/sdb/queue/nr_requests

# DRBD 디바이스 I/O 스케줄러 (항상 none)
echo none > /sys/block/drbd0/queue/scheduler
disk-barrier/disk-flushes 비활성화 주의: 배터리 백업이 없는 상태에서 이 옵션을 끄면, 정전 시 데이터 손실이 발생할 수 있습니다. 반드시 BBU(Battery Backup Unit) RAID 컨트롤러나 UPS가 있을 때만 비활성화하세요.

재해 복구 시나리오

장애 시나리오별 복구 절차

시나리오증상복구 절차데이터 손실
Secondary 디스크 장애Diskless/Inconsistent디스크 교체 → attach → 자동 재동기화없음
Primary 디스크 장애I/O 에러, Diskless 전환Secondary를 Primary 승격, 원래 Primary 디스크 교체없음 (Protocol C)
네트워크 단절StandAlone/WFConnection네트워크 복구 → connect → 비트맵 재동기화없음
Split-Brain양쪽 Primary패자 선택 → --discard-my-data connect패자 측 변경분 손실
Primary 서버 전체 장애Secondary만 생존Secondary Primary 승격 → 서비스 시작Protocol A: 일부 가능
양쪽 서버 동시 장애모든 노드 다운먼저 복구된 노드 Primary → 나머지 재동기화마지막 쓰기 일부
# 시나리오 1: Secondary 디스크 교체 후 복구
$ drbdadm create-md r0               # 새 디스크에 메타데이터 생성
$ drbdadm attach r0                   # 디스크 연결
$ drbdadm status r0                   # Inconsistent 확인
# 자동으로 비트맵 기반 재동기화 시작

# 시나리오 2: Split-Brain 복구
# 패자 노드에서:
$ drbdadm disconnect r0
$ drbdadm secondary r0
$ drbdadm connect --discard-my-data r0
# 승자 노드에서:
$ drbdadm connect r0
# 패자 → 승자 방향으로 전체 재동기화

# 시나리오 3: Primary 비상 승격 (Secondary에서)
$ drbdadm primary --force r0          # 강제 Primary 승격
$ mount /dev/drbd0 /data              # 파일시스템 마운트
$ systemctl start postgresql          # 서비스 시작
강제 Primary 승격 주의: --force 옵션은 데이터 일관성 검사를 건너뜁니다. 원래 Primary가 아직 살아있으면 Split-Brain이 발생합니다. 반드시 원래 Primary가 완전히 중단되었음을 확인한 후에만 사용하세요.

참고 자료

자료유형설명
docs.linbit.com공식 문서LINBIT DRBD/LINSTOR 공식 문서
DRBD User's Guide가이드DRBD 9 사용자 가이드
drivers/block/drbd/소스DRBD 커널 모듈 소스 코드
github.com/LINBIT/drbd소스DRBD 9 커널 모듈 GitHub
LINSTOR Server소스LINSTOR 볼륨 관리 서버
LWN: DRBD overview기사DRBD 아키텍처 개요

Wire Protocol 심화 — 소켓 구조와 핸드셰이크

DRBD 연결은 두 개의 TCP 소켓으로 구성됩니다. 데이터 소켓(data socket)은 실제 블록 데이터(P_DATA, P_RS_DATA_REPLY 등)를 전송하고, 메타 소켓(meta socket)은 제어 메시지(P_PING, P_STATE, ACK 패킷 등)를 처리합니다. 이 분리 설계는 대용량 데이터 전송이 제어 메시지를 지연시키지 않도록 보장합니다.

Primary 노드 sender 스레드 asender 스레드 data socket meta socket Secondary 노드 receiver 스레드 worker 스레드 data socket meta socket 핸드셰이크 시퀀스 1. TCP 연결 (data socket + meta socket) 2. P_CONNECTION_FEATURES (프로토콜 버전, 기능 교환) 3. P_AUTH_CHALLENGE (HMAC 챌린지, shared-secret 설정 시) 4. P_AUTH_RESPONSE (HMAC 응답) 5. P_STATE + P_UUIDS (양방향 상태 및 GI 교환) 6. P_SIZES (디스크 크기, 세그먼트 크기 협상) Connected! 복제 시작

이중 소켓 설계 상세

소켓전송 내용스레드설계 이유
data socketP_DATA, P_RS_DATA_REPLY, P_BARRIER, bulk 데이터sender / receiver대용량 블록 데이터 전용, TCP_CORK 활용
meta socketP_WRITE_ACK, P_RECV_ACK, P_PING, P_PING_ACK, P_STATEasender / worker저지연 제어 메시지, 데이터 전송과 독립적 처리
/* drbd_receiver.c — 연결 핸드셰이크 구현 (간략화) */
static int conn_connect(struct drbd_connection *connection)
{
    struct socket *sock, *msock;

    /* 1. 데이터 소켓과 메타 소켓 동시에 연결 시도 */
    sock = drbd_try_connect(connection, connection->my_addr,
                              connection->peer_addr);
    msock = drbd_try_connect(connection, connection->my_addr,
                               connection->peer_addr);

    /* 2. TCP_NODELAY 설정 (제어 메시지 즉시 전송) */
    tcp_sock_set_nodelay(msock->sk);

    /* 3. data socket에 TCP_CORK (Nagle 유사 배치 전송) */
    tcp_sock_set_cork(sock->sk, 1);

    connection->data_socket = sock;
    connection->meta_socket = msock;

    /* 4. 프로토콜 버전 교환 */
    drbd_send_features(connection);

    /* 5. 선택적 인증 (shared-secret) */
    if (connection->net_conf->shared_secret[0])
        drbd_do_auth(connection);

    /* 6. 상태/UUID 교환 → 동기화 방향 결정 */
    drbd_send_uuids(peer_device);
    drbd_send_state(peer_device);

    return 0;
}
TCP_CORK vs TCP_NODELAY: data socket에 TCP_CORK를 설정하면 커널이 작은 패킷을 모아서 한꺼번에 전송하여 대역폭 효율을 높입니다. 반면 meta socket에는 TCP_NODELAY를 설정하여 ACK 패킷이 지연 없이 즉시 전송되도록 합니다. 이 조합이 DRBD의 처리량과 레이턴시를 동시에 최적화합니다.

패킷 수명 주기

/* P_DATA 패킷의 전체 수명 주기 (Protocol C) */

/* Primary (sender 스레드) */
drbd_send_dblock(peer_device, req) {
    /* p_data 헤더 구성 */
    p.sector   = cpu_to_be64(req->i.sector);
    p.block_id = (u64)req;           /* 요청 추적용 식별자 */
    p.seq_num  = atomic_inc_return(&connection->packet_seq);
    p.dp_flags = bio_flags_to_wire(req);

    /* data socket으로 헤더 + 데이터 전송 */
    drbd_send_command(peer_device, sock, P_DATA,
                       &p, sizeof(p), req->data, req->size);
}

/* Secondary (receiver 스레드) */
receive_Data(connection, pi) {
    /* 1. 페이로드 수신 */
    peer_req = read_in_block(peer_device, pi);

    /* 2. Activity Log 업데이트 */
    drbd_al_begin_io(device, &peer_req->i);

    /* 3. 로컬 디스크에 기록 */
    drbd_submit_peer_request(device, peer_req);

    /* 4. 디스크 쓰기 완료 대기 */
    wait_for_completion(&peer_req->done);

    /* 5. P_WRITE_ACK 전송 (meta socket) */
    drbd_send_ack(peer_device, P_WRITE_ACK, peer_req);
}

Activity Log 내부 구현 심화

Activity Log는 LRU(Least Recently Used) 캐시 위에 구축된 트랜잭션 로그입니다. AL extent가 교체될 때마다 메타데이터 디바이스에 트랜잭션을 기록하며, 이 로그는 순환 버퍼 형태로 관리됩니다.

Activity Log 트랜잭션 로그 내부 구조 인메모리 LRU 캐시 (act_log) ext #42 ext #108 ext #1024 ext #5120 ... Hot Area 추적: 각 extent에 참조 카운트(refcnt) 유지 refcnt > 0: 활성 I/O 진행 중 (교체 불가) refcnt = 0: 교체 후보 (LRU tail) 1 extent = 4MB (AL_EXTENT_SHIFT=22) 기본 1237개 → 약 4.8GB 활성 범위 최대 65534개 → 약 256GB 활성 범위 교체 시: wait_event(al_wait) 대기 디스크 트랜잭션 로그 (순환 버퍼) txn #1001: ext 42→108 txn #1002: ext 77→1024 txn #1003: ext 200→5120 txn #1004: (현재 쓰기) 구조: transaction_nr (순환) slot_nr (교체 위치) extent_nr (새 extent 번호) 교체
/* drbd_actlog.c — AL 트랜잭션 구조 */
struct al_transaction_on_disk {
    __be32 magic;            /* DRBD_AL_MAGIC = 0xDRBD_AL */
    __be32 tr_number;        /* 순환 트랜잭션 번호 */
    __be32 crc32c;           /* 트랜잭션 CRC */
    __be16 n_updates;        /* 이 트랜잭션의 슬롯 갱신 수 */
    __be16 context_size;     /* 전체 context 크기 */
    __be16 context_start_slot_nr;
    /* 뒤이어 update[] 배열: (slot_nr, extent_nr) 쌍 */
    /* 뒤이어 context[] 배열: 전체 AL 슬롯 스냅샷 (복구용) */
} __packed;

/* AL 트랜잭션 기록 과정 */
static int drbd_al_begin_io_commit(struct drbd_device *device)
{
    struct lru_cache *al = device->act_log;

    /* 1. 변경된 LRU 슬롯을 트랜잭션 버퍼에 기록 */
    al_write_transaction(device);

    /* 2. 메타 디바이스에 동기 쓰기 (REQ_FUA) */
    /*    원자성 보장: 전체 트랜잭션이 쓰이거나 안 쓰이거나 */
    drbd_md_sync_page_io(device, md_sector, REQ_OP_WRITE | REQ_FUA);

    /* 3. 트랜잭션 번호 증가 */
    device->al_tr_number++;

    /* 크래시 복구: 메타 디바이스에서 가장 최근 유효 트랜잭션을 찾아 */
    /* AL 상태를 재구성 → 해당 extent 범위만 Resync */

    return 0;
}
AL 핫 에어리어 최적화: 순차 쓰기 워크로드에서는 AL extent가 순서대로 활성화/비활성화되므로 AL 트랜잭션이 적습니다. 반면 랜덤 쓰기에서는 빈번한 LRU 교체로 AL 쓰기가 급증합니다. al-extents를 워킹셋 크기에 맞게 늘리면 교체 빈도가 줄어 메타데이터 I/O 오버헤드가 감소합니다.
al-extents 값커버 범위AL 메타 크기권장 환경
1237 (기본)~4.8 GB~40 KB소규모 워크로드, HDD
3833~15 GB~120 KB중규모 DB, SSD
6433~25 GB~200 KB대규모 DB, NVMe
16337~64 GB~512 KB대용량 랜덤 워크로드
65534 (최대)~256 GB~2 MB전체 디스크 범위 커버

Bitmap 내부 구조와 OOS 추적 심화

DRBD 비트맵은 디스크 상의 각 4KB 블록(BM_BLOCK_SIZE)에 대해 1비트를 할당하여 피어와의 동기화 상태를 추적합니다. 비트맵은 인메모리 캐시와 온디스크 영속 저장소에 동시에 존재합니다.

Bitmap 이중 구조: 인메모리 + 온디스크 인메모리 비트맵 (drbd_bitmap) page[0] page[1] page[2]* page[3] ... * dirty 페이지 (디스크에 미기록) 1 PAGE = 4096 바이트 = 32768 비트 1 비트 = 4KB 디스크 블록 1 PAGE = 128MB 디스크 범위 추적 100GB 디스크 → 비트맵 크기: 3.125MB 1TB 디스크 → 비트맵 크기: 32MB DRBD 9: 피어별 독립 비트맵 유지 온디스크 비트맵 (메타데이터 영역) 메타데이터 레이아웃: Super Block (256 bytes) Activity Log (순환 트랜잭션 로그) Bitmap (피어별 OOS 비트맵) Generation Identifiers (UUID) internal meta-disk: 디스크 끝 영역 external meta-disk: 별도 디바이스 정상 종료 시 인메모리 → 디스크 플러시 플러시
/* drbd_bitmap.c — 비트맵 핵심 함수 */

/* 쓰기 시: 변경된 섹터 범위를 비트맵에 표시 */
int drbd_bm_set_bits(struct drbd_device *device,
                      unsigned long s, unsigned long e)
{
    /* s, e: 시작/끝 비트맵 비트 번호 */
    /* 4KB 단위: sector >> (BM_BLOCK_SHIFT - 9) */
    unsigned long *p_addr;
    int changed = 0;

    for (bitnr = s; bitnr <= e; bitnr++) {
        p_addr = __bm_map_pidx(device->bitmap, bm_bit_to_page_idx(bitnr));
        if (!test_and_set_bit(bitnr % BITS_PER_PAGE, p_addr)) {
            changed++;
            bm_set_page_need_writeout(device->bitmap, bm_bit_to_page_idx(bitnr));
        }
    }
    return changed;
}

/* Resync 완료 시: 동기화된 블록의 비트 해제 */
int drbd_bm_clear_bit(struct drbd_device *device, unsigned long bitnr)
{
    unsigned long *p_addr = __bm_map_pidx(device->bitmap,
                                          bm_bit_to_page_idx(bitnr));
    clear_bit(bitnr % BITS_PER_PAGE, p_addr);
    bm_set_page_need_writeout(device->bitmap, bm_bit_to_page_idx(bitnr));
    return 0;
}

/* 디스크에 비트맵 기록 (연결 해제/모듈 언로드 시) */
int drbd_bm_write(struct drbd_device *device)
{
    struct drbd_bitmap *b = device->bitmap;
    int i;

    for (i = 0; i < b->bm_number_of_pages; i++) {
        if (bm_test_page_need_writeout(b, i)) {
            bm_page_io(device, i, REQ_OP_WRITE);
            bm_clear_page_need_writeout(b, i);
        }
    }
    return 0;
}
DRBD 9 피어별 비트맵: DRBD 9에서 3노드 이상 메시 복제 시, 각 피어에 대해 독립적인 비트맵이 유지됩니다. 예를 들어 3노드 클러스터에서 Node 1은 Node 2용 비트맵과 Node 3용 비트맵을 각각 관리합니다. 이를 통해 "Node 2는 최신이지만 Node 3은 뒤처진" 상태를 정확히 추적할 수 있습니다.

Online Verify 알고리즘 상세

Online Verify는 서비스 중단 없이 전체 디스크를 블록 단위로 해시 비교하여 Silent Data Corruption(비트 로트)을 감지합니다. VerifySource가 주도적으로 검증을 진행하며, 양쪽 데이터가 불일치하면 해당 블록을 out-of-sync 비트맵에 기록합니다.

/* drbd_worker.c — Online Verify 핵심 흐름 (간략화) */

/* VerifySource (Primary): 검증 주도 */
static int w_ov_next(struct drbd_work *w, int cancel)
{
    struct drbd_peer_device *peer_device = w->peer_device;
    sector_t sector = peer_device->ov_position;
    unsigned int size = BM_BLOCK_SIZE;  /* 4KB 또는 설정값 */

    /* 1. 로컬 블록 읽기 */
    struct page *page = drbd_alloc_page();
    drbd_backing_dev_read(device, sector, page, size);

    /* 2. 해시 계산 (verify-alg: sha256 등) */
    crypto_shash_digest(device->verify_tfm, page_data, size, digest);

    /* 3. P_OV_REQUEST + 로컬 해시를 피어에 전송 */
    drbd_send_ov_request(peer_device, sector, size);

    /* ov_position을 다음 블록으로 이동 */
    peer_device->ov_position += (size >> 9);

    return 0;
}

/* VerifyTarget (Secondary): 해시 비교 응답 */
static int receive_OVRequest(struct drbd_connection *conn,
                              struct packet_info *pi)
{
    /* 1. 동일 섹터의 로컬 블록 읽기 */
    drbd_backing_dev_read(device, sector, page, size);

    /* 2. 로컬 해시 계산 */
    crypto_shash_digest(device->verify_tfm, page_data, size, local_digest);

    /* 3. P_OV_REPLY로 로컬 해시 전송 */
    drbd_send_ov_reply(peer_device, sector, size, local_digest);

    return 0;
}

/* VerifySource: 수신한 해시와 로컬 해시 비교 */
static int receive_OVReply(struct drbd_connection *conn,
                             struct packet_info *pi)
{
    if (memcmp(local_digest, peer_digest, digest_size) != 0) {
        /* 불일치: out-of-sync 비트맵에 표시 */
        drbd_bm_set_bits(device, sector_to_bm_bit(sector),
                          sector_to_bm_bit(sector + (size >> 9) - 1));
        peer_device->ov_last_oos_size += size;
        drbd_err(device, "Online verify: sector %llu mismatch\n", sector);
    }
    peer_device->ov_left--;

    if (peer_device->ov_left == 0) {
        /* 검증 완료 */
        drbd_info(device, "Online verify done. oos=%lu\n",
                  peer_device->ov_last_oos_size);
    }
    return 0;
}
매개변수기본값설명
verify-alg(미설정)해시 알고리즘 (sha256, sha1, crc32c). 미설정 시 Online Verify 비활성
ov-timeout0 (무제한)검증 타임아웃 (초). 0이면 전체 디스크 검증까지 진행
검증 단위BM_BLOCK_SIZE (4KB)한 번에 비교하는 블록 크기
네트워크 부하해시 크기만 전송SHA-256: 블록당 32바이트 전송 (데이터 전송 없음)
디스크 부하순차 읽기양쪽 노드 모두 전체 디스크 순차 읽기
Online Verify 스케줄링 전략: cron으로 주간/월간 실행하여 비트 로트를 조기에 발견합니다. 검증은 순차 읽기이므로 랜덤 I/O 워크로드에 미치는 영향이 적습니다. 불일치가 발견되면 이후 disconnect/connect 시 자동으로 해당 블록만 Resync됩니다. 즉시 수정하려면 drbdadm disconnect r0 && drbdadm connect r0를 실행합니다.

Split-Brain 정책과 GI(Generation Identifier) 심화

Split-Brain 탐지의 핵심은 Generation Identifier(GI) 비교입니다. GI는 4개의 UUID로 구성되며, 재연결 시 양쪽 GI를 교환하여 "누가 더 최신인지", "양쪽이 독립적으로 변경되었는지"를 결정합니다.

GI(Generation Identifier) 비교 과정 Node A의 GI (4개 UUID) current-uuid: A-UUID-1 (현재 세대) bitmap-uuid: A-UUID-2 (피어 비트맵 기준) history-uuid[0]: A-UUID-3 history-uuid[1]: A-UUID-4 Node B의 GI (4개 UUID) current-uuid: B-UUID-1 bitmap-uuid: B-UUID-2 history-uuid[0]: B-UUID-3 history-uuid[1]: B-UUID-4 GI 비교 결정 트리 A.cur == B.cur → 양쪽 동일 (Connected) A.cur == B.bitmap → B가 뒤처짐 (A→B Resync) A.bitmap == B.cur → A가 뒤처짐 (B→A Resync) 양쪽 모두 다름 → Split-Brain 감지! UUID 전부 0 → 초기 전체 동기화 필요 history에서 매칭 → 중간 세대 복구 가능

Split-Brain 정책 상세

정책상황동작위험도자동화 적합
disconnect모든 상황연결 해제, 수동 개입 필요없음 (안전)낮음 (수동 복구)
discard-younger-primary0pri더 최근에 Primary가 된 쪽 데이터 폐기중간높음
discard-older-primary0pri더 오래전에 Primary였던 쪽 데이터 폐기중간높음
discard-zero-changes0pri변경이 없는 쪽 데이터 폐기 (변경 없으면 안전)낮음높음
discard-least-changes0pri변경량이 적은 쪽 데이터 폐기높음중간
discard-secondary1pri현재 Secondary 데이터 폐기중간높음
call-pri-lost-after-sb2pripri-lost 핸들러 호출 (fencing 등)높음높음
# 최대 안전 Split-Brain 설정 (프로덕션 권장)
resource r0 {
  net {
    # 0pri: 양쪽 모두 Secondary인 경우
    after-sb-0pri discard-zero-changes;  # 변경 없는 쪽 안전하게 폐기

    # 1pri: 한쪽만 Primary인 경우
    after-sb-1pri discard-secondary;     # Secondary 데이터 폐기

    # 2pri: 양쪽 모두 Primary인 경우 (가장 위험)
    after-sb-2pri disconnect;            # 연결 해제, 수동 처리 필수
  }

  handlers {
    # Split-Brain 알림
    split-brain "/usr/lib/drbd/notify-split-brain.sh admin@example.com";

    # pri-lost 핸들러 (call-pri-lost-after-sb 사용 시)
    pri-lost-after-sb "echo 1 > /proc/sysrq-trigger; reboot -f";
    # 경고: 위 핸들러는 노드를 강제 재부팅합니다
  }
}

# GI 수동 확인
$ drbdadm get-gi r0
UUID: current=A1B2C3D4:bitmap=E5F6G7H8:history1=...
# 양쪽 UUID를 비교하여 Split-Brain 유형 판단 가능
call-pri-lost-after-sb 핸들러: 이 핸들러는 Split-Brain 상태에서 "패자"로 결정된 Primary 노드에서 실행됩니다. 일반적으로 노드를 재부팅하거나 DRBD를 강제 Secondary로 전환합니다. 반드시 STONITH/fencing과 함께 사용하여 패자 노드가 더 이상 I/O를 수행하지 못하도록 보장해야 합니다.

DRBD Proxy WAN 복제 심화

DRBD Proxy는 DRBD 노드 사이에 위치하여 버퍼링, 압축, 대역폭 제어를 제공합니다. Protocol A와 결합하여 WAN 환경에서도 쓰기 레이턴시를 최소화하면서 안정적인 복제를 보장합니다.

프록시 버퍼링 메커니즘

파라미터기본값설명튜닝 가이드
memlimit0 (무제한)프록시 버퍼 메모리 상한쓰기 버스트 × 2~3배 (예: 4GB)
sndbuf-size0 (자동)프록시→WAN TCP 송신 버퍼BDP(대역폭 × RTT) 이상
rcvbuf-size0 (자동)프록시←WAN TCP 수신 버퍼BDP 이상
compression없음압축 알고리즘 및 레벨zstd 3~6 (압축률/CPU 균형)
bwlimit0 (무제한)대역폭 제한 (KB/s)WAN 링크의 70~80%
# 고급 DRBD Proxy 설정 (대륙 간 복제)
resource dr-wan {
  protocol A;

  proxy {
    memlimit 4G;                   # 4GB 버퍼 (쓰기 버스트 흡수)
    plugin {
      zstd 6;                      # zstd 압축 레벨 6 (높은 압축률)
    }
    bwlimit 100000;                # 100MB/s 대역폭 제한
  }

  net {
    # BDP 계산: 100Mbps × 100ms RTT = 1.25MB
    # TCP 버퍼는 BDP의 2배 이상 권장
    sndbuf-size 4194304;           # 4MB 송신 버퍼
    rcvbuf-size 4194304;           # 4MB 수신 버퍼
    max-buffers 36000;
    max-epoch-size 36000;
    ko-count 7;                    # 7회 타임아웃 후 연결 해제
    ping-int 10;
    ping-timeout 50;               # 5초 ping 타임아웃 (WAN 고려)
  }

  on dc-primary {
    address 10.0.0.1:7789;
    proxy on dc-primary {
      inside 127.0.0.1:7789;
      outside 203.0.113.1:7800;
    }
  }
  on dc-dr {
    address 10.1.0.1:7789;
    proxy on dc-dr {
      inside 127.0.0.1:7789;
      outside 198.51.100.1:7800;
    }
  }
}

# Proxy 실시간 모니터링
$ drbd-proxy-ctl -c "show drbd-proxy"
  buffer usage:     847MB / 4096MB (20.7%)
  compression ratio: 4.1:1
  data rate (in):    125 MB/s (애플리케이션 쓰기 속도)
  data rate (out):    31 MB/s (압축 후 WAN 전송)
  pending bytes:     523MB (아직 미전송)

# BDP(Bandwidth-Delay Product) 계산 도구
# BDP = Bandwidth(bytes/s) × RTT(seconds)
# 예: 1Gbps × 50ms = 125MB/s × 0.05s = 6.25MB
# TCP 버퍼 최소: BDP × 2 = 12.5MB
프록시 버퍼 가득 참 시 동작: memlimit에 도달하면 프록시는 DRBD 로컬 노드로의 TCP 흐름을 정지(backpressure)시켜 Primary의 쓰기를 일시적으로 차단합니다. 이는 Protocol A에서도 발생할 수 있으며, 이 경우 쓰기 레이턴시가 급증합니다. memlimit은 WAN 대역폭 대비 쓰기 버스트를 충분히 흡수할 수 있도록 설정해야 합니다.

에포크(Epoch)와 배리어(Barrier) 처리

DRBD는 에포크(epoch) 개념으로 쓰기 순서를 보장합니다. P_BARRIER 패킷이 에포크 경계를 표시하며, Secondary는 에포크 내의 모든 쓰기가 완료된 후에야 P_BARRIER_ACK를 전송합니다.

에포크(Epoch)와 배리어(Barrier) 순서 보장 시간 Epoch 1 P_DATA P_DATA P_DATA P_BARRIER Epoch 2 P_DATA P_DATA P_BARRIER Secondary 처리 (수신 측) 1. Epoch 1의 P_DATA 수신 → 병렬 디스크 쓰기 시작 2. P_BARRIER 수신 → Epoch 1의 모든 쓰기 완료 대기 3. 디스크 플러시(REQ_PREFLUSH) → 순서 보장 4. P_BARRIER_ACK 전송 → Epoch 2 처리 시작
/* drbd_receiver.c — 에포크/배리어 처리 */

/* 에포크 구조체: 하나의 배리어 구간 */
struct drbd_epoch {
    struct list_head   list;       /* 에포크 체인 */
    unsigned int       barrier_nr; /* 배리어 번호 */
    atomic_t           epoch_size; /* 이 에포크의 쓰기 수 */
    atomic_t           active;     /* 활성 I/O 수 */
    unsigned long      flags;      /* BARRIER_ACKED 등 */
};

/* P_BARRIER 수신 시 */
static int receive_Barrier(struct drbd_connection *conn,
                            struct packet_info *pi)
{
    struct drbd_epoch *epoch = conn->current_epoch;

    /* 현재 에포크의 모든 쓰기가 완료될 때까지 대기 */
    wait_event(conn->ee_wait,
              atomic_read(&epoch->active) == 0);

    /* 디스크 플러시 (쓰기 순서 보장) */
    if (test_bit(DE_CONTAINS_A_BARRIER, &epoch->flags))
        drbd_flush(device);

    /* P_BARRIER_ACK 전송 */
    drbd_send_barrier_ack(conn, epoch->barrier_nr,
                          atomic_read(&epoch->epoch_size));

    /* 새 에포크 시작 */
    conn->current_epoch = drbd_new_epoch(conn);

    return 0;
}

/* max-epoch-size 초과 시 자동 배리어 삽입 */
/* 설정: max-epoch-size 8192 → 8192 쓰기마다 자동 배리어 */
/* 목적: 에포크가 너무 커지면 Secondary가 OOM 위험 */
에포크와 성능: max-epoch-size가 크면 배리어 빈도가 줄어 처리량이 향상되지만, Secondary의 메모리 사용량이 증가합니다. max-buffers와 함께 조절하여 Secondary OOM을 방지합니다. NVMe SSD 환경에서는 8192~16384가 적절합니다.

성능 벤치마크 방법론

DRBD 성능을 정확히 측정하려면 fio(Flexible I/O Tester)를 사용하여 체계적으로 벤치마크해야 합니다. 기준선(로컬 디스크)과 DRBD 디바이스를 동일 조건으로 비교합니다.

# ========= DRBD 성능 벤치마크 스크립트 =========

# 1. 기준선: 로컬 디스크 성능 (DRBD 없이)
# 순차 쓰기 처리량
$ fio --name=seq-write --filename=/dev/sdb1 \
    --ioengine=libaio --direct=1 --bs=1M \
    --rw=write --numjobs=1 --iodepth=32 \
    --size=10G --runtime=60 --time_based

# 랜덤 4K 쓰기 IOPS
$ fio --name=rand-write --filename=/dev/sdb1 \
    --ioengine=libaio --direct=1 --bs=4k \
    --rw=randwrite --numjobs=4 --iodepth=32 \
    --size=10G --runtime=60 --time_based

# 쓰기 레이턴시 (clat: 완료 레이턴시)
$ fio --name=lat-write --filename=/dev/sdb1 \
    --ioengine=libaio --direct=1 --bs=4k \
    --rw=randwrite --numjobs=1 --iodepth=1 \
    --size=1G --runtime=60 --time_based

# 2. DRBD 디바이스 동일 테스트
$ fio --name=drbd-seq-write --filename=/dev/drbd0 \
    --ioengine=libaio --direct=1 --bs=1M \
    --rw=write --numjobs=1 --iodepth=32 \
    --size=10G --runtime=60 --time_based

$ fio --name=drbd-rand-write --filename=/dev/drbd0 \
    --ioengine=libaio --direct=1 --bs=4k \
    --rw=randwrite --numjobs=4 --iodepth=32 \
    --size=10G --runtime=60 --time_based

$ fio --name=drbd-lat-write --filename=/dev/drbd0 \
    --ioengine=libaio --direct=1 --bs=4k \
    --rw=randwrite --numjobs=1 --iodepth=1 \
    --size=1G --runtime=60 --time_based

# 3. Protocol A vs C 비교
# Protocol A 설정 후 동일 테스트 반복
$ drbdadm net-options --protocol=A r0
# ... fio 테스트 실행 ...
$ drbdadm net-options --protocol=C r0
# ... fio 테스트 실행 ...

# 4. 결과 비교 핵심 지표
# - BW (MB/s): 순차 쓰기 대역폭
# - IOPS: 랜덤 쓰기 초당 I/O
# - clat p99 (us): 99th 백분위 완료 레이턴시
# - DRBD 오버헤드 = (로컬 - DRBD) / 로컬 × 100%
테스트로컬 NVMeDRBD Proto C (10GbE)DRBD Proto A (10GbE)DRBD Proto C (RDMA)
순차 쓰기 1M (MB/s)3200115030502800
랜덤 쓰기 4K (IOPS)450K120K420K380K
쓰기 레이턴시 p99 (us)151802245
순차 읽기 1M (MB/s)3500350035003500
오버헤드 (순차 쓰기)-64%5%13%
벤치마크 주의사항: DRBD 벤치마크 시 반드시 --direct=1(O_DIRECT)을 사용하여 페이지 캐시를 우회해야 합니다. 페이지 캐시 사용 시 로컬과 DRBD 차이가 거의 없어 보이지만, 실제 서비스 부하에서는 캐시 미스 시 Protocol C 레이턴시가 드러납니다. 또한 워밍업(--ramp_time=10)을 적용하여 초기 불안정 구간을 제외합니다.

클라우드 환경의 DRBD

AWS, Azure, GCP 등 퍼블릭 클라우드에서 DRBD를 운영할 때의 고려사항과 클라우드 네이티브 스토리지 복제 서비스와의 비교입니다.

비교 항목DRBD on CloudAWS EBS Multi-AttachAzure Shared DiskGCE Regional PD
복제 방식블록 레벨, 커널 모듈단일 EBS, 여러 인스턴스 연결단일 디스크, 여러 VM 연결2개 존 간 자동 복제
동기/비동기선택 가능 (A/B/C)N/A (단일 볼륨)N/A (단일 볼륨)동기 (자동)
파일시스템ext4/XFS (단일 Primary)클러스터 FS 필요 (GFS2)클러스터 FS 필요ext4/XFS (단일 마운트)
가용 영역(AZ) 간가능 (Protocol C + 낮은 RTT)동일 AZ만동일 리전 내2개 AZ 자동
리전 간 DR가능 (Protocol A + Proxy)EBS Snapshot 복사ASR (비동기)디스크 스냅샷 복사
비용EC2/VM 비용 + 네트워크EBS 비용 × 인스턴스 수Shared Disk 프리미엄Regional PD 2배 비용
레이턴시 오버헤드Protocol C: +RTT없음 (단일 볼륨)없음자동 관리
유연성매우 높음 (커널 레벨)낮음 (AWS 종속)낮음 (Azure 종속)낮음 (GCP 종속)
# AWS EC2에서 DRBD 구성 (AZ 간 Protocol C)

# 1. 인스턴스 배치: 같은 리전, 다른 AZ
#    node1: us-east-1a (10.0.1.10)
#    node2: us-east-1b (10.0.2.10)
#    AZ 간 RTT: ~1ms (Protocol C 적합)

# 2. EBS gp3 볼륨 생성 (각 노드에 독립 볼륨)
$ aws ec2 create-volume --availability-zone us-east-1a \
    --size 100 --volume-type gp3 --iops 16000 --throughput 1000

# 3. DRBD 설정 (AZ 간 전용 서브넷 사용)
resource r0 {
  protocol C;             # AZ 간 1ms RTT → Protocol C 적합
  net {
    max-buffers 8192;
    max-epoch-size 8192;
    sndbuf-size 0;
    rcvbuf-size 0;
  }
  disk {
    al-extents 6433;
    # EBS gp3는 내부적으로 복제됨 → disk-flushes 유지
    disk-flushes yes;
  }
  on node1 {
    address 10.0.1.10:7789;
    device /dev/drbd0;
    disk /dev/nvme1n1;     # EBS gp3
    meta-disk internal;
  }
  on node2 {
    address 10.0.2.10:7789;
    device /dev/drbd0;
    disk /dev/nvme1n1;
    meta-disk internal;
  }
}

# 4. 클라우드 환경 특수 고려사항
# - EC2 Enhanced Networking(ENA) 필수: 25Gbps+ 대역폭
# - Placement Group 사용: 클러스터 또는 Spread 배치
# - EBS 최적화 인스턴스 사용: io2 Block Express for 고성능
# - Security Group: TCP 7789 포트 허용
# - STONITH: AWS Fencing Agent (fence_aws) 사용
클라우드 DRBD vs 네이티브 복제: 클라우드 네이티브 복제(EBS, Azure Managed Disk)는 인프라 관리가 불필요하지만, 단일 CSP에 종속됩니다. DRBD는 멀티 클라우드, 하이브리드 클라우드(온프레미스+클라우드), 클라우드 간 DR 구성이 가능합니다. 특히 LINSTOR CSI를 통해 Kubernetes 워크로드에서 클라우드 중립적인 영구 스토리지를 제공할 수 있습니다.

drbd_request 수명 주기와 완료 처리

DRBD의 모든 I/O 요청은 drbd_request 구조체로 관리됩니다. 이 구조체는 bio 수신부터 완료(bio_endio)까지의 전체 수명 주기를 추적합니다.

/* drivers/block/drbd/drbd_int.h — 요청 구조체 */
struct drbd_request {
    struct drbd_device  *device;
    struct bio          *master_bio;  /* 원본 bio */
    struct bio          *private_bio; /* 로컬 디스크 제출용 복제 bio */

    struct drbd_interval i;       /* 섹터 범위 (start, size) */
    unsigned long       rq_state; /* 요청 상태 비트마스크 */

    /* rq_state 비트 플래그: */
    /* RQ_LOCAL_PENDING   — 로컬 I/O 진행 중 */
    /* RQ_LOCAL_COMPLETED — 로컬 I/O 완료 */
    /* RQ_NET_PENDING     — 네트워크 전송 대기 */
    /* RQ_NET_SENT        — 네트워크 전송 완료 */
    /* RQ_NET_DONE        — ACK 수신 완료 */
    /* RQ_IN_ACT_LOG      — Activity Log에 등록됨 */

    unsigned int        seq_num;  /* 시퀀스 번호 (순서 보장) */
    unsigned long       start_jif; /* 요청 시작 시각 (레이턴시 측정) */
    struct list_head    tl_requests; /* Transfer Log 체인 */
};

/* 요청 완료 판단: 프로토콜별 완료 조건 */
static bool drbd_req_is_done(struct drbd_request *req)
{
    unsigned long s = req->rq_state;

    /* 로컬 I/O 완료 확인 */
    if (!(s & RQ_LOCAL_COMPLETED))
        return false;

    /* Protocol별 네트워크 완료 조건 */
    switch (connection->net_conf->wire_protocol) {
    case DRBD_PROT_A:
        /* TCP 송신 버퍼에 전달되면 완료 */
        return (s & RQ_NET_SENT);
    case DRBD_PROT_B:
        /* 피어 TCP 수신 확인(P_RECV_ACK) 필요 */
        return (s & RQ_NET_DONE);
    case DRBD_PROT_C:
        /* 피어 디스크 쓰기 완료(P_WRITE_ACK) 필요 */
        return (s & RQ_NET_DONE);
    }
    return false;
}

/* 완료 처리: bio_endio 호출 */
static void drbd_req_complete(struct drbd_request *req)
{
    if (drbd_req_is_done(req)) {
        /* Activity Log 해제 */
        if (req->rq_state & RQ_IN_ACT_LOG)
            drbd_al_complete_io(req->device, &req->i);

        /* 원본 bio 완료 콜백 */
        bio_endio(req->master_bio);

        /* 요청 구조체 해제 */
        drbd_req_free(req);
    }
}
상태 전이트리거동작
생성 → RQ_LOCAL_PENDINGdrbd_make_request()bio 복제 후 로컬 디스크 제출
→ RQ_LOCAL_COMPLETED로컬 디스크 I/O 완료 인터럽트로컬 완료 기록
생성 → RQ_NET_PENDINGdrbd_send_dblock()네트워크 전송 대기열 진입
→ RQ_NET_SENTTCP 송신 버퍼 전달 완료Protocol A: 네트워크 완료
→ RQ_NET_DONE (P_RECV_ACK)피어 TCP 수신 확인Protocol B: 네트워크 완료
→ RQ_NET_DONE (P_WRITE_ACK)피어 디스크 쓰기 완료Protocol C: 네트워크 완료
RQ_LOCAL_COMPLETED + 네트워크 완료drbd_req_complete()bio_endio() 호출, 요청 해제
Transfer Log: 모든 활성 drbd_request는 Transfer Log(TL)에 연결됩니다. 네트워크 장애 시 TL에 남아있는 요청들의 비트맵 비트를 설정하여, 재연결 시 해당 블록이 재동기화 대상이 됩니다. TL은 DRBD의 데이터 일관성을 보장하는 핵심 메커니즘입니다.

Multi-Volume 구성: Stacked vs Side-by-Side

DRBD에서 여러 블록 디바이스를 복제하는 두 가지 패턴이 있습니다. Side-by-Side(DRBD 9 Multi-Volume)와 Stacked(DRBD 8 레거시) 방식입니다.

특성Side-by-Side (Multi-Volume)Stacked (레거시)
DRBD 버전DRBD 9+ (권장)DRBD 8 (호환용)
TCP 연결리소스당 2개 (data + meta) 공유스택 단계별 2개씩
쓰기 순서모든 볼륨 간 순서 보장각 스택 계층 독립
역할 전환리소스 단위 일괄각 계층 개별 전환
구성 복잡도낮음높음
사용 사례DB (데이터+WAL+메타)3사이트 DR (2단계 복제)
# Side-by-Side (DRBD 9 Multi-Volume) — 권장 방식
resource db-cluster {
  options {
    quorum majority;
    on-no-quorum io-error;
  }

  volume 0 {                       # PostgreSQL 데이터
    device /dev/drbd10;
    disk /dev/vg0/pg_data;
    meta-disk internal;
  }

  volume 1 {                       # PostgreSQL WAL
    device /dev/drbd11;
    disk /dev/vg0/pg_wal;
    meta-disk internal;
  }

  volume 2 {                       # PostgreSQL pg_stat_tmp
    device /dev/drbd12;
    disk /dev/vg0/pg_tmp;
    meta-disk internal;
  }

  on node1 { address 10.0.0.1:7789; }
  on node2 { address 10.0.0.2:7789; }
  on node3 { address 10.0.0.3:7789; }
}

# Stacked (DRBD 8 레거시) — 3사이트 DR 용도
# 1단계: 로컬 HA (LAN, Protocol C)
resource r0-local {
  protocol C;
  volume 0 {
    device /dev/drbd0;
    disk /dev/sdb1;
    meta-disk internal;
  }
  on node1 { address 10.0.0.1:7789; }
  on node2 { address 10.0.0.2:7789; }
}

# 2단계: 원격 DR (WAN, Protocol A)
resource r0-dr {
  protocol A;
  stacked-on-top-of r0-local {    # 1단계 위에 쌓기
    device /dev/drbd10;
    address 127.0.0.1:7790;
  }
  on dr-site {
    device /dev/drbd10;
    disk /dev/sdb1;
    address 198.51.100.1:7790;
    meta-disk internal;
  }
}
Side-by-Side 장점: DRBD 9의 Multi-Volume은 단일 TCP 연결로 여러 볼륨을 관리하므로 연결 오버헤드가 적고, 모든 볼륨의 쓰기 순서가 보장됩니다. DB 환경에서 데이터 파일과 WAL이 원자적으로 복제되어 크래시 복구 후 일관성이 보장됩니다.

DRBD와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.