안녕하세요.

 

업무를 수행하다 보면 시스템의 가용성과 연속성을 보장하기 위한 인프라 아키텍처 설계를 긴밀하게 논의하게 됩니다.

그 중에서도 가장 흔히 비교되는 전통적인 HA(Active-Standby) 구조Oracle RAC(Active-Active) 구조

핵심 차이점과 작동 원리를 정리합니다.

실무 설계 검토 시 명확한 기준을 제시하는 데 도움이 되기를 바랍니다.

1. 개념 정의

① HA (High Availability, 고가용성) 구조

  • 정의: 시스템이 장애 없이 오랜 기간 지속해서 정상 가동될 수 있도록 하는 모든 아키텍처적 지향점을 뜻합니다.
  • 실무적 의미: 일반적으로 RDBMS 환경에서 HA라고 하면 OS 레벨의 클러스터웨어(예: IBM HACMP, HP Serviceguard, Veritas Cluster Server 등)를 이용한 Active-Standby(주-예비) 형태의 이중화 구조를 의미하는 경우가 많습니다.

② Oracle RAC (Real Application Clusters)

  • 정의: Oracle 사가 독자적으로 개발한 공유 디스크 기반의 데이터베이스 클러스터링 기술입니다.
  • 실무적 의미: 여러 대의 서버(Node)가 하나의 동일한 물리 데이터베이스(Storage)를 공유하며, 모든 서버가 동시에 활성화되어 서비스를 처리하는 Active-Active(주-주) 구조를 의미합니다.

2. 핵심 아키텍처 차이점 비교

두 구조의 결정적인 차이는 "두 대의 서버가 동시에 하나의 데이터에 접근하여 서비스를 처리할 수 있는가?"와 "장애 조치(Failover) 시 데이터 중단 시간이 발생하는가?"에 있습니다.

① 데이터 처리 방식 (Active 상태)

  • 전통적 HA (Active-Standby): 평소에는 Active 서버 1대만 스토리지를 마운트하여 서비스를 처리합니다. Standby 서버는 대기 상태로 유지되며 데이터베이스가 켜져 있지 않습니다.
  • Oracle RAC (Active-Active): 2대 이상의 서버가 물리 디스크를 공유(Shared Disk)하며 동시에 데이터베이스 인스턴스를 기동합니다. 사용자는 어떤 서버로 접속하든 동일한 데이터를 조회하고 수정할 수 있어 부하 분산(Load Balancing)이 자연스럽게 이루어집니다.

② 장애 발생 시 조치 방식 (Failover)

  • 전통적 HA (Active-Standby): Active 서버 장애 시, Standby 서버가 스토리지 소유권을 넘겨받고(Volume Takeover), IP를 전환한 뒤, 데이터베이스 엔진을 처음부터 부팅(Startup)하는 과정을 거칩니다. 이 과정에서 반드시 몇 분간의 서비스 중단(Downtime)이 발생합니다.
  • Oracle RAC (Active-Active): 1번 서버가 죽더라도 2번 서버는 이미 켜져 있고 데이터베이스를 서비스 중인 상태입니다. 죽은 서버의 세션만 살아있는 서버로 즉시 이동하므로 사용자는 단 몇 초간의 지연(Failover)만 느낄 뿐 서비스가 끊기지 않습니다.

③ 데이터 동기화와 캐시 퓨전 (Cache Fusion)

  • 전통적 HA: 데이터를 동기화할 필요가 없습니다. 한 번에 한 서버만 디스크를 쓰기 때문입니다.
  • Oracle RAC: 여러 서버가 동시에 같은 데이터를 수정하려 하면 데이터 정합성이 깨질 수 있습니다. Oracle은 이를 해결하기 위해 서버 간 고속 전용 네트워크(Interconnect)를 통해 메모리(SGA) 상에서 데이터를 주고받는 캐시 퓨전(Cache Fusion) 기술을 사용합니다. 디스크 I/O를 거치지 않고 메모리 간 동기화를 수행하여 병목을 최소화합니다.

3. 요약 및 비교표

비교 항목 전통적 HA (Active-Standby) Oracle RAC (Active-Active)
운영 방식 1대 운영, 1대 대기 모든 서버 동시 운영 (2대 이상 확장 가능)
부하 분산 불가능 (대기 서버 자원 낭비) 가능 (동시 처리로 성능 확장 가능)
Failover 시간 수 분 소요 (DB 재부팅 및 복구 시간 필요) 수 초 소요 (이미 기동된 서버로 세션 이동)
스토리지 연결 한 번에 한 서버만 마운트 가능 모든 서버가 동시에 공유 마운트
구축 비용 상대적으로 저렴 (표준 OS 기능 활용) 매우 고가 (Oracle RAC 라이선스 필요)

실무용 아키텍처 가이드 

두 구조의 차이점이나 선택 기준을 묻는다면 기술적 메커니즘을 엮어 아래와 같이 정제된 언어로 답변하시는 것이 좋습니다.

Q. 전통적인 HA 구조와 Oracle RAC 구조의 가장 큰 차이점과 설계 시 고려사항은 무엇인가요?

"가장 본질적인 차이는 서비스의 연속성과 자원 효율성에 있습니다.

전통적인 Active-Standby HA 구조는 장애 시 Standby 서버가 스토리지 권한을 넘겨받고 데이터베이스를 재부팅해야 하므로 수 분간의 다운타임이 불가피하며, 대기 서버의 자원이 낭비된다는 단점이 있습니다.

반면 Oracle RAC는 공유 디스크 아키텍처를 기반으로 모든 노드가 동시에 서비스를 처리하는 Active-Active 구조입니다. 한 노드에 장애가 발생해도 이미 기동 중인 타 노드로 즉시 세션이 Failover되어 다운타임을 최소화하고, 평시에는 부하 분산을 통해 자원을 효율적으로 활용할 수 있습니다.

다만, RAC는 고가의 라이선스 비용이 발생하며, 노드 간 데이터 정합성을 맞추기 위한 인터커넥트(Cache Fusion) 부하가 존재하므로, 애플리케이션 설계 시 동시다발적인 동일 블록 수정 패턴을 제어하는 튜닝 전략이 동반되어야 합니다."

안녕하세요.

 

데이터 아키텍처(DA) 업무를 수행하며 시스템 성능 최적화와 데이터 흐름 분석을 진행하다 보면,

간혹 테이블 뒤에 숨겨진 '트리거(Trigger)' 때문에 골머리를 앓는 경우가 많습니다.

 

최근 진행 중인 대규모 통합 프로젝트에서도 특정 테이블에 DML이 발생할 때마다 원인 모를 성능 저하와 데이터 왜곡이 발생해 추적해 보니, 복잡하게 얽힌 트리거들이 원인이었습니다.

 

오늘은 데이터베이스 트리거의 개념과 실무 관점에서의 장단점, 그리고 왜 트리거 남용이 성능 재앙을 불러오는지 정리해 보겠습니다.

1. 데이터베이스 트리거(Trigger)란?

트리거는 테이블에 INSERT, UPDATE, DELETE 등 DML 이벤트가 발생했을 때, 데이터베이스 엔진에 의해 자동으로 실행(Fire)되도록 정의된 프로시저의 일종입니다.

개발자가 애플리케이션 레벨에서 별도로 호출하지 않아도 데이터베이스 내부에서 알아서 연속적인 데이터 처리를 수행한다는 특징이 있습니다.

2. 트리거의 장점 (기능적 이점)

트리거가 실무에서 활용되는 이유는 명확한 기능적 장점이 있기 때문입니다.

  • 강력한 데이터 무결성 보장: 애플리케이션의 버그나 개발자의 실수로 특정 비즈니스 규칙(예: 상품 삭제 시 장바구니 데이터 동시 삭제 등)을 누락하더라도, DB 레벨에서 강제로 무결성을 유지할 수 있습니다.
  • 실시간 감사 및 로그 기록(Auditing): 중요 테이블의 데이터가 변경될 때마다 이전 값과 이후 값을 별도의 이력(History) 테이블에 자동으로 기록하는 아키텍처를 구현하기에 매우 편리합니다.
  • 업무 로직의 집중화: 데이터와 밀접하게 연관된 유효성 검증 로직을 데이터베이스 내부로 캡슐화하여 여러 애플리케이션에서 공통으로 적용할 수 있습니다.

3. 트리거의 단점 및 실무적 한계 (DA가 기피하는 이유)

장점에도 불구하고, 대용량 트랜잭션을 처리하는 엔터프라이즈 환경에서 DA들이 트리거 사용을 지양하는 데는 치명적인 이유들이 있습니다.

① 데이터 흐름의 블랙박스화 (분석의 난해함)

가장 큰 문제입니다. 애플리케이션 코드(Java, Python 등)나 호출한 SQL 문만 봐서는 데이터가 왜 변경되었는지 알 수 없습니다. 소스 코드 상에서는 분명 A 테이블에만 데이터를 넣었는데, 전혀 무관해 보이는 B 테이블, C 테이블의 데이터가 바뀌어 있는 현상이 발생합니다.

  • 결과: 장애 발생 시 로직 추적 및 디버깅 시간이 기하급수적으로 증가하며, 시스템을 처음 분석하는 DA나 개발자에게 엄청난 분석 오버헤드를 줍니다.

② 성능 저하 및 트랜잭션 장기화 (락 오버헤드)

트리거는 호출한 원본 DML과 동일한 트랜잭션 범위(Transaction Scope) 내에서 실행되는 경우가 많습니다.

  • A 테이블에 1건 삽입 > 트리거 발동 > B 테이블 수정 > C 테이블 검증 등 도미노 현상이 일어납니다.
  • 이로 인해 하나의 트랜잭션이 완료되기까지 시간이 길어지고, 관련 테이블들에 락(Lock)이 오랫동안 유지되어 동시성(Concurrency)이 극도로 저하됩니다. 대량 배치 작업 시에는 성능 마비 수준의 병목이 발생합니다.

③ 연쇄 트리거(Cascading)와 무한 루프 위험

A 테이블 트리거가 B 테이블을 건드리고, B 테이블 트리거가 다시 A 테이블을 건드리는 구조가 형성되면 시스템이 무한 루프에 빠지거나 예측 불가능한 연쇄 반응을 일으켜 데이터 정합성이 완전히 깨질 수 있습니다.

📌실무용 아키텍처 가이드

아키텍처 검토 회의에서 트리거에 대한 질문을 받으면 아래 템플릿을 토대로 답변하시는 것을 강력히 추천합니다.

Q. 실무에서 트리거 사용 시 발생할 수 있는 문제점과 이에 대한 견해는 무엇인가요?

"트리거는 데이터베이스 레벨에서 실시간 무결성을 보장한다는 장점이 있지만, '트랜잭션의 은닉성(블랙박스화)'과 '성능 저하'라는 치명적인 단점이 있습니다.

애플리케이션 로직 흐름과 분리되어 자동으로 실행되므로 데이터 변경 원인을 추적하고 디버깅하기 어렵게 만들며, 원본 DML과 트랜잭션을 공유하기 때문에 락(Lock) 유지 시간이 길어져 시스템 동시성을 떨어뜨립니다.

따라서 복잡한 비즈니스 로직은 애플리케이션 레이어나 명시적인 저장 프로시저(Stored Procedure)에서 처리하는 것이 아키텍처 관점에서 투명성과 확장성을 확보하는 올바른 방향이며, 트리거는 데이터 변경 이력 로깅 등 제한적인 용도로만 최소화하여 사용해야 합니다."

4. 결론

인덱스가 '보이는 길'이라면, 트리거는 '보이지 않는 함정'이 될 수 있습니다. 시스템 통합 단계에서 성능 이슈를 잡고 완벽한 데이터 계보(Data Lineage)를 파악하기 위해서는 테이블 뒤에 숨은 트리거들을 수면 위로 끌어올려 통합하거나 제거하는 작업이 반드시 선행되어야 합니다.

안녕하세요.

 

업무를 수행하다 보면 테이블의 기본키(PK, Primary Key)를 변경하거나,

성능 향상을 위해 인덱스를 재구성해야 하는 상황을 마주하게 됩니다.

 

대다수의 RDBMS에서 PK는 자동으로 Clustered 인덱스로 생성됩니다.

그런데 이 Clustered 인덱스의 구조적 특징을 정확히 모른 채 테이블 구조를 변경하면,

해당 테이블에 걸려 있는 다른 보조 인덱스(Non-Clustered 인덱스)들까지 통째로 흔들리는 성능 이슈가 발생할 수 있습니다.

 

실무 마이그레이션 사례를 통해 두 인덱스의 구조적 관계와 주의점을 알아보겠습니다.

1. 실무 가상 사례 (Use Case)

사용자 정보와 매핑 데이터를 관리하는 대규모 통합 프로젝트 과정에서 발생한 일입니다. 기존 레거시 시스템의 회원 테이블은 UUID(36자리 문자열)를 PK로 사용하고 있었고, 서비스 조회를 위해 이메일(Email), 전화번호(Phone) 컬럼에 각각 보조 인덱스(Non-Clustered)가 생성되어 있었습니다.

-- 대용량 회원 테이블 구조 (기존)
CREATE TABLE members (
    user_uuid VARCHAR(36) PRIMARY KEY, -- Clustered 인덱스 자동 생성
    email VARCHAR(100),                -- Non-Clustered 인덱스 A
    phone VARCHAR(20),                 -- Non-Clustered 인덱스 B
    ...
);

새로운 아키텍처 표준에 따라 데이터 정렬 및 저장 효율성을 높이기 위해,

PK를 문자열(UUID)에서 순차 증가형 정수(BIGINT AUTO_INCREMENT)로 변경하는 마이그레이션을 단행했습니다.

 

단순히 PK 하나만 바꾸는 작업이라고 생각했으나, 변경 스크립트를 실행하자마자 시스템은 몇 시간 동안 락(Lock)이 걸렸고, 변경 이후에는 Email이나 Phone으로 조회하는 일반 로그인/인증 쿼리 성능까지 무더기로 급하강하는 현상이 발생했습니다.

 

2. 느릴 수밖에 없는 이유: 내부 포인터 구조의 비밀

이유는 Non-Clustered 인덱스가 Clustered 인덱스의 키값을 주소로 참조하기 때문입니다.

① 포인터의 종속성 (Dependency)

  • Clustered 인덱스: 테이블당 단 하나만 존재하며, 리프 노드가 실제 데이터 페이지(Row) 그 자체입니다. 즉, 인덱스 정렬 순서가 곧 물리적 데이터 저장 순서입니다.
  • Non-Clustered 인덱스: 실제 데이터의 물리적 주소(RowID)를 직접 가지지 않습니다. 대신 데이터가 포함된 Clustered 인덱스의 키값(위 사례에서는 user_uuid)을 리프 노드에 포인터로 가지고 있습니다.

② PK 변경 시 발생하는 도미노 현상 (Page Split & Rebuild)

위 사례처럼 PK를 변경하거나 무작위 문자열(UUID) 형태로 삽입이 발생하면 다음과 같은 연쇄 반응이 일어납니다.

  1. Clustered 인덱스 재정렬: 실제 데이터의 물리적 위치를 새로 바뀐 PK 순서에 맞춰 물리적으로 재배치(정렬)합니다. 이 과정에서 페이지 분할(Page Split)이 대량 발생합니다.
  2. 보조 인덱스 전면 재구축(Rebuild): 물리적 위치와 PK 값이 바뀌었으므로, 이 테이블에 걸려 있던 모든 Non-Clustered 인덱스(Email, Phone)의 리프 노드 포인터 정보도 전부 갱신(Rebuild)되어야 합니다.

결국 PK 하나를 건드렸을 뿐인데, 테이블 전체 데이터의 물리적 이동과 더불어 수많은 보조 인덱스들이 통째로 다시 써지는 오버헤드가 발생한 것입니다.

3. 실무용 아키텍처 가이드

이 구조적 차이와 현상을 면접이나 아키텍처 설계 회의에서 설명할 때는 아래 템플릿을 활용해 보세요.

Q. Clustered 인덱스와 Non-Clustered 인덱스의 구조적 관계를 설명하고, PK 설계 시 주의점을 말해주세요.

"Clustered 인덱스는 리프 노드가 실제 데이터 페이지인 구조이며, Non-Clustered 인덱스는 실제 데이터의 물리적 주소 대신 Clustered 인덱스의 키값을 포인터로 참조합니다.

이러한 구조적 종속성 때문에 Clustered 인덱스의 키(PK) 크기가 지나치게 크거나 값이 자주 변경되면, 이를 참조하는 모든 보조 인덱스의 크기가 커지고 CUD 발생 시 동반 재구축 오버헤드가 발생합니다.

따라서 아키텍처 설계 시 Clustered 인덱스(PK)는 **1) 크기가 작고, 2) 값이 변하지 않으며, 3) 순차적으로 증가하는 정수형 타입(Auto Increment 등)**을 선택하는 것이 Non-Clustered 인덱스들의 탐색 및 변경 성능을 안정적으로 유지하는 최선의 전략입니다."

4. 결론 및 조치 사항

대규모 데이터 통합 프로젝트에서 데이터 모델을 설계할 때, 업무적 식별자(예: 주민번호, UUID, 복합 문자열 키)를 그대로 PK(Clustered 인덱스)로 잡는 것은 매우 위험합니다.

업무적 식별자는 인조 키(Surrogate Key)인 정수형 순차 키에게 PK 자리를 양보하고, 자신은 Non-Clustered 인덱스(Unique)로 물러나는 것이 전체적인 인덱스 아키텍처의 안정성을 확보하는 DA의 지혜입니다.

+ Recent posts