운영 환경에서 DB Link를 조심해야 하는 이유 – 장애·보안·성능 리스크 완벽 정리


운영 환경에서 DB Link를 조심해야 하는 이유, “그냥 두 DB를 연결해서 데이터 가져오면 편한 거 아닌가요?”라고 생각하셨다면 이 글을 반드시 읽어보셔야 합니다. DB Link는 당장은 편리하지만, 운영 환경에서 시스템이 커지고 트래픽이 늘어날수록 장애 전파, 보안 취약점, 성능 병목, 트랜잭션 불일치라는 네 가지 시한폭탄이 동시에 터질 수 있는 설계입니다. 실제로 국내외 대형 장애 사례 중 상당수가 DB Link로 인한 연쇄 장애에서 시작되었습니다. 이 글에서는 DB Link가 정확히 어떤 원리로 동작하는지부터, 왜 위험한지, 그리고 어떤 대안으로 대체해야 하는지를 코드와 실제 사례를 통해 낱낱이 설명합니다.


목차

  1. DB Link란 무엇인가 – 기초 개념과 동작 원리
  2. 운영 환경에서 DB Link를 조심해야 하는 핵심 이유
  3. DB Link가 가져오는 단기적 편의와 장기적 부채
  4. 실제 장애 패턴과 발견하기 어려운 함정들
  5. 실전 대안 설계 – DB Link를 대체하는 방법
  6. 전문가 관점 – 불가피한 경우의 안전한 사용 지침

1. DB Link란 무엇인가 – 기초 개념과 동작 원리

DB Link(Database Link) 란 하나의 데이터베이스에서 다른 데이터베이스의 객체(테이블, 뷰, 프로시저 등)에 직접 접근할 수 있도록 연결 통로를 만드는 데이터베이스 기능입니다. Oracle에서 가장 일반적으로 사용되지만, PostgreSQL의 FDW(Foreign Data Wrapper), MySQL의 Federated 스토리지 엔진 등 대부분의 RDBMS에서 유사한 기능을 제공합니다.

DB Link의 기본 동작 구조

[로컬 DB 서버 - A사 시스템]          [원격 DB 서버 - B사 시스템]
┌──────────────────────┐             ┌──────────────────────┐
│  SELECT *            │   DB Link   │                      │
│  FROM orders@remote  │ ─────────── │  orders 테이블       │
│  WHERE status='완료' │  네트워크   │  (원격 접근)         │
└──────────────────────┘             └──────────────────────┘
      로컬 세션                          원격 세션 생성

DB Link를 사용하면 마치 같은 DB에 있는 테이블처럼 테이블명@링크명 형태로 원격 테이블에 쿼리를 날릴 수 있습니다.

Oracle DB Link 생성과 사용 예시

sql

-- DB Link 생성
CREATE DATABASE LINK remote_db_link
  CONNECT TO remote_user IDENTIFIED BY "password123"
  USING '(DESCRIPTION=
    (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.100)(PORT=1521))
    (CONNECT_DATA=(SERVICE_NAME=REMOTEDB)))';

-- DB Link를 통한 원격 테이블 조회
SELECT o.order_id, o.customer_id, c.customer_name
FROM   orders@remote_db_link o         -- 원격 DB의 주문 테이블
JOIN   customers c ON o.customer_id = c.id  -- 로컬 DB의 고객 테이블
WHERE  o.status = '완료'
  AND  o.created_at >= SYSDATE - 7;

-- 원격 테이블에 데이터 삽입
INSERT INTO sync_log@remote_db_link (event_time, event_type, detail)
VALUES (SYSDATE, 'SYNC', '일일 정산 완료');
COMMIT;

PostgreSQL FDW 예시

sql

-- 외부 서버 등록
CREATE EXTENSION postgres_fdw;

CREATE SERVER remote_pg_server
  FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '192.168.1.200', port '5432', dbname 'analytics_db');

-- 사용자 매핑
CREATE USER MAPPING FOR local_user
  SERVER remote_pg_server
  OPTIONS (user 'remote_user', password 'remote_pass');

-- 외부 테이블 선언
CREATE FOREIGN TABLE remote_sales (
    sale_id     BIGINT,
    amount      NUMERIC(15, 2),
    sale_date   DATE
)
SERVER remote_pg_server
OPTIONS (schema_name 'public', table_name 'sales');

-- 마치 로컬 테이블처럼 조회
SELECT * FROM remote_sales WHERE sale_date >= '2024-01-01';

왜 DB Link를 사용하게 되는가

DB Link가 운영 환경에 등장하는 경위는 대부분 비슷합니다. 처음에는 빠른 데이터 통합이 필요해서, 또는 레거시 시스템 간 데이터를 공유해야 해서, 혹은 개발 일정 압박으로 인해 “일단 빠르게” 연결하는 방식으로 도입됩니다. 문제는 이 “일단”이 몇 년씩 이어지면서 시스템 전체에 단단히 박혀버린다는 것입니다.


2. 운영 환경에서 DB Link를 조심해야 하는 핵심 이유

운영 환경에서 DB Link를 조심해야 하는 이유는 크게 네 가지로 압축됩니다. 각각은 독립적인 문제이기도 하지만, 동시에 복합적으로 작용하여 시스템 전체를 마비시킬 수 있습니다.

① 장애 전파 – 원격 DB가 죽으면 나도 죽는다

DB Link의 가장 치명적인 문제는 원격 데이터베이스의 장애가 로컬 데이터베이스로 즉시 전파된다는 점입니다.

[장애 전파 시나리오]

원격 DB 서버 (B시스템)
    │
    ├─ 네트워크 단절 발생
    ├─ DB 과부하로 응답 지연 (30초 타임아웃)
    └─ DB 서버 다운

         ↓ DB Link 연결

로컬 DB 세션들이 원격 응답을 기다리며 대기(WAITING) 상태
    │
    ├─ 세션 수 급증 → 커넥션 풀 고갈
    ├─ 대기 중인 쿼리가 락(Lock) 보유
    └─ 다른 정상 쿼리들도 락 대기 → 연쇄 장애

         ↓

로컬 DB 서버 (A시스템) 전체 서비스 중단

이 시나리오에서 B시스템은 A시스템의 핵심 서비스와 전혀 무관한 부가 기능을 위한 DB였을 수도 있습니다. 그러나 DB Link로 연결되어 있다는 이유만으로 B의 장애가 A 전체를 멈춥니다. 이를 장애 격리 실패(Failure Isolation Failure) 라고 합니다.

sql

-- 이 쿼리 하나가 원격 DB 응답을 30초간 기다리면서
-- 해당 세션의 트랜잭션 전체가 블로킹됩니다
SELECT COUNT(*)
FROM   statistics@remote_db_link  -- 원격 DB가 느려지는 순간
WHERE  report_date = TRUNC(SYSDATE);
-- 이 쿼리를 실행한 배치 프로그램이 커넥션을 점유하고,
-- 커넥션 풀이 가득 차서 다른 서비스 요청이 모두 실패하기 시작합니다.

② 분산 트랜잭션의 위험 – 2PC의 함정

DB Link를 통해 두 DB에 걸친 트랜잭션을 수행할 때, Oracle은 2단계 커밋(2-Phase Commit, 2PC) 프로토콜을 사용합니다. 이 프로토콜은 이론적으로는 원자성을 보장하지만, 실제 운영 환경에서는 다음과 같은 심각한 문제를 만들어냅니다.

sql

-- 위험한 분산 트랜잭션 예시
BEGIN
    -- 로컬 DB 업데이트
    UPDATE local_orders
    SET    status = '처리완료'
    WHERE  order_id = 12345;

    -- 원격 DB 업데이트 (DB Link)
    UPDATE order_history@remote_db_link
    SET    sync_status = 'DONE',
           synced_at   = SYSDATE
    WHERE  order_id = 12345;

    COMMIT; -- 2PC 시작: 양쪽 DB에 PREPARE → COMMIT 순서로 진행
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;  -- 원격 DB 롤백이 실패하면? → 데이터 불일치!
END;

2PC 도중 원격 DB가 PREPARE 이후 COMMIT 이전에 응답하지 않으면 트랜잭션은 인-다우트(In-Doubt) 상태에 빠집니다. 로컬 DB는 커밋도 롤백도 하지 못한 채 해당 레코드에 락을 걸고 영구 대기 상태가 됩니다. 이 락은 DBA가 수동으로 개입하지 않으면 절대 해제되지 않습니다.

sql

-- Oracle에서 인-다우트 트랜잭션 확인
SELECT local_tran_id, global_tran_id, state, mixed, advice, tran_comment
FROM   dba_2pc_pending;

-- 수동으로 강제 커밋 또는 롤백 (DBA 직접 개입 필요)
COMMIT   FORCE 'local_tran_id';
ROLLBACK FORCE 'local_tran_id';

DBA가 야간에 인-다우트 트랜잭션을 발견하고 수동으로 처리하는 상황은 운영 현장에서 실제로 빈번하게 발생합니다. 그리고 강제 커밋/롤백 선택을 잘못하면 양쪽 DB 데이터가 영구적으로 불일치 상태가 됩니다.

③ 보안 취약점 – 자격증명 노출과 권한 확대

DB Link는 원격 데이터베이스에 접속하기 위한 사용자명과 비밀번호를 데이터베이스 내부에 저장합니다. 이 구조는 여러 보안 취약점을 만들어냅니다.

sql

-- DB Link 생성 시 자격증명이 평문으로 저장됨
CREATE DATABASE LINK remote_db_link
  CONNECT TO admin_user IDENTIFIED BY "SuperSecret123!"
  USING 'REMOTEDB';

-- DBA 권한이 있는 계정이라면 다음으로 조회 가능
SELECT db_link, username, host
FROM   dba_db_links;
-- username: ADMIN_USER (비밀번호는 암호화되어 있지만 해독 가능)

보안 문제 1: 과도한 권한 부여 DB Link 연결 계정에 필요 이상의 권한이 부여되는 경우가 많습니다. “일단 잘 연결되게” 하려다 보니 DBA 권한이나 SELECT ANY TABLE 같은 과도한 권한을 주는 경우가 발생합니다. 로컬 DB를 통해 원격 DB의 모든 데이터에 접근할 수 있는 상태가 됩니다.

보안 문제 2: 횡적 이동(Lateral Movement) 경로 공격자가 로컬 DB를 침해하면, DB Link를 타고 원격 DB까지 자동으로 접근 가능해집니다. DB Link는 네트워크 방화벽을 우회하는 내부 통로가 됩니다.

보안 문제 3: 자격증명 갱신의 어려움 원격 DB 계정의 비밀번호를 변경하면, 그 계정을 사용하는 모든 DB Link를 찾아서 일일이 재생성해야 합니다. 관리가 소홀해지면 오래된 비밀번호가 수년간 변경되지 않고 유지됩니다.

④ 성능 저하 – 네트워크가 병목이 된다

같은 서버 내 로컬 쿼리와 달리, DB Link 쿼리는 네트워크 레이턴시가 추가됩니다. 단순 조회는 큰 문제가 없어 보이지만, 조인이나 반복 호출이 들어가는 순간 성능이 급격히 저하됩니다.

sql

-- 위험한 패턴: 로컬 데이터 건수만큼 원격 쿼리 발생 (N+1 문제)
FOR rec IN (SELECT order_id FROM local_orders WHERE status = 'PENDING')
LOOP
    -- 루프 1회당 네트워크 왕복 1회 발생
    -- 100건이면 100번의 네트워크 왕복
    SELECT customer_name
    INTO   v_name
    FROM   customers@remote_db_link
    WHERE  customer_id = rec.customer_id;

    -- ... 처리 로직
END LOOP;

로컬 쿼리에서 N+1 문제가 발생해도 마이크로초 단위이지만, DB Link를 통하면 왕복 1회마다 수 밀리초~수십 밀리초가 추가됩니다. 1만 건이면 전체 처리 시간이 수 분에서 수십 분으로 늘어납니다.

또한 DB Link를 통한 조인에서는 옵티마이저가 원격 테이블의 통계 정보를 정확히 알지 못해 비효율적인 실행 계획을 선택하는 경우가 많습니다.

sql

-- 옵티마이저가 잘못된 실행 계획을 선택하는 예시
-- 원격 테이블이 1억 건인데 통계가 없으면 작은 테이블로 오인
SELECT l.order_id, r.customer_name
FROM   local_orders l
JOIN   customers@remote_db_link r ON l.customer_id = r.customer_id
WHERE  l.status = 'COMPLETE';
-- 실제: remote customers = 1억 건 → Nested Loop 선택 → 매우 느림
-- 기대: Hash Join이 적합하나 원격 통계 부재로 잘못된 선택

3. DB Link가 가져오는 단기적 편의와 장기적 부채

단기적으로 느껴지는 편의

DB Link가 운영 환경에 자리를 잡는 데는 분명한 이유가 있습니다. 도입 초기에는 다음과 같은 실질적인 편의를 제공합니다.

빠른 데이터 통합: 별도의 ETL 파이프라인이나 API 개발 없이 SQL 한 줄로 다른 시스템의 데이터를 가져올 수 있습니다. 개발 시간이 획기적으로 줄어듭니다.

익숙한 SQL 문법: 개발자와 DBA 모두 추가적인 기술 학습 없이 기존 SQL 지식만으로 사용할 수 있습니다.

레거시 시스템 연동: 오래된 시스템에 API가 없거나, 소스 코드 수정이 불가능한 경우 DB Link가 유일한 현실적 대안처럼 보이기도 합니다.

장기적으로 쌓이는 기술 부채

그러나 시스템이 성장하면서 DB Link는 다음과 같은 기술 부채를 남깁니다.

의존성 파악 불가: DB Link가 늘어날수록 어떤 시스템이 어떤 시스템에 의존하는지 추적하기 어렵습니다. 원격 DB의 테이블 구조를 바꾸려 해도 “혹시 어딘가에서 DB Link로 쓰고 있지 않을까?” 하는 불안 때문에 변경을 꺼리게 됩니다. 시스템이 경직됩니다.

테스트 환경 구성의 어려움: DB Link는 원격 DB가 실제로 존재하고 접근 가능해야 동작합니다. 개발·스테이징 환경에서 원격 DB를 그대로 복제하거나 Mock하는 것이 어렵기 때문에, DB Link 관련 코드는 테스트가 사실상 불가능한 경우가 많습니다.

장애 원인 분석의 복잡성: 장애가 발생했을 때 원인이 로컬 DB 문제인지, 원격 DB 문제인지, 네트워크 문제인지 즉각 파악하기 어렵습니다. 여러 시스템이 DB Link로 얽혀 있으면 장애 원인 추적이 매우 복잡해집니다.

마이그레이션 장벽: 클라우드 전환이나 DB 엔진 교체 시 DB Link가 걸림돌이 됩니다. AWS RDS나 Azure SQL처럼 관리형 DB 서비스는 DB Link 지원이 제한적이거나 설정이 까다롭습니다.


4. 실제 장애 패턴과 발견하기 어려운 함정들

패턴 1 – 야간 배치가 새벽 서비스를 죽이는 경우

sql

-- 야간 배치 프로그램 (매일 새벽 2시 실행)
INSERT INTO daily_report
SELECT a.dept_code,
       SUM(b.sales_amount) AS total_sales,
       COUNT(b.order_id)   AS order_count
FROM   departments a
JOIN   sales@legacy_db_link b ON a.dept_code = b.dept_code  -- DB Link 사용
WHERE  b.sale_date = TRUNC(SYSDATE) - 1
GROUP BY a.dept_code;

COMMIT;

평소에는 새벽 2시에 10분 안에 끝나는 배치입니다. 그러나 원격 DB에 인덱스 재구성 작업이 걸리는 날, 또는 원격 DB 서버의 I/O가 폭주하는 날이면 이 배치가 2시간을 넘기기 시작합니다. DB Link 세션이 원격 응답을 기다리는 동안 로컬 DB의 커넥션을 점유하고, 새벽 배치 특성상 다른 배치들도 동시에 실행되면서 커넥션 풀이 고갈됩니다. 아침 업무 시작 시간인 9시에 “DB 접속이 안 됩니다”는 장애 신고가 올라옵니다.

패턴 2 – 타임아웃 설정 부재로 인한 영구 대기

sql

-- DB Link의 기본 타임아웃이 없으면 Oracle은 영구 대기
ALTER SESSION SET distributed_lock_timeout = 60; -- 60초로 제한
-- 이 설정이 없으면 기본값(60초)이지만, DB Link 자체 타임아웃은 별도
-- sqlnet.ora에 SQLNET.OUTBOUND_CONNECT_TIMEOUT 미설정 시 영구 대기 가능

-- sqlnet.ora에 반드시 설정해야 할 항목
-- SQLNET.OUTBOUND_CONNECT_TIMEOUT = 10  (연결 타임아웃: 10초)
-- SQLNET.RECV_TIMEOUT = 30              (수신 타임아웃: 30초)
-- SQLNET.SEND_TIMEOUT = 30              (송신 타임아웃: 30초)

타임아웃 설정이 없으면 원격 DB가 응답을 하지 않을 때 해당 세션은 무기한 대기합니다. OS 레벨에서 TCP keepalive가 연결을 끊기 전까지 세션이 살아 있는 것처럼 보이면서 락과 커넥션을 점유합니다.

패턴 3 – 숨겨진 DB Link가 담긴 뷰와 프로시저

sql

-- 일반 뷰처럼 보이지만 내부에 DB Link가 숨어있음
CREATE OR REPLACE VIEW v_customer_summary AS
SELECT c.customer_id,
       c.customer_name,
       o.total_orders,
       o.total_amount
FROM   customers c
JOIN   (
    SELECT customer_id,
           COUNT(*)    AS total_orders,
           SUM(amount) AS total_amount
    FROM   orders@crm_db_link  -- 여기에 숨어있는 DB Link!
    GROUP BY customer_id
) o ON c.customer_id = o.customer_id;

이 뷰를 사용하는 개발자는 단순한 뷰라고 생각하고 여러 쿼리에서 활용합니다. v_customer_summary가 실제로 원격 DB를 호출한다는 사실을 모른 채로요. 원격 DB가 느려지면 이 뷰를 쓰는 모든 화면이 동시에 느려지고, 원격 DB가 다운되면 이 뷰를 조회하는 모든 기능이 오류를 반환합니다. 문제는 해당 뷰를 사용하는 코드가 어디 어디인지 파악조차 쉽지 않다는 점입니다.

패턴 4 – 클라우드 전환 시 발목을 잡는 DB Link

온프레미스 Oracle에서 AWS RDS Oracle로 전환하려 할 때, 기존 시스템에 DB Link가 촘촘하게 얽혀 있으면 마이그레이션 계획 자체가 뒤틀립니다. RDS에서는 특정 DB Link 설정이 불가능하거나, 원격 DB의 Oracle Net 설정을 맞춰줘야 하는 제약이 있습니다. 마이그레이션 프로젝트 초반에 DB Link 현황을 파악하지 못하면 전환 작업 막바지에 치명적인 블로커로 등장합니다.


5. 실전 대안 설계 – DB Link를 대체하는 방법

DB Link의 문제를 인지했다면, 이제 어떻게 대체할지 실전 패턴을 살펴봅니다.

대안 1 – API를 통한 서비스 간 통신

가장 권장되는 현대적 접근법입니다. 각 시스템이 데이터를 직접 노출하지 않고 API(REST 또는 gRPC)를 통해 필요한 데이터만 제공합니다.

java

// DB Link 방식: 원격 DB 직접 조회 (나쁜 예)
// SELECT * FROM customers@remote_db_link WHERE customer_id = ?

// API 방식: HTTP 호출로 대체 (좋은 예)
@Service
public class CustomerIntegrationService {

    private final RestTemplate restTemplate;
    private final CircuitBreaker circuitBreaker;  // 장애 격리 핵심

    public CustomerIntegrationService(RestTemplate restTemplate,
                                       CircuitBreakerRegistry registry) {
        this.restTemplate = restTemplate;
        this.circuitBreaker = registry.circuitBreaker("customerService");
    }

    public CustomerDto getCustomer(Long customerId) {
        // 서킷 브레이커로 감싸서 원격 서비스 장애가 전파되지 않도록 격리
        return circuitBreaker.executeSupplier(() -> {
            String url = "http://customer-service/api/customers/" + customerId;
            return restTemplate.getForObject(url, CustomerDto.class);
        });
    }

    // 서킷 브레이커가 열렸을 때 반환할 폴백
    public CustomerDto getCustomerFallback(Long customerId, Exception ex) {
        log.warn("Customer service unavailable, returning fallback for id: {}", customerId);
        return CustomerDto.unknown(customerId);  // 기본값 반환
    }
}

API 방식의 핵심 장점은 서킷 브레이커(Circuit Breaker) 패턴을 적용할 수 있다는 것입니다. 원격 서비스가 느려지거나 다운되면 서킷 브레이커가 열려서 빠르게 폴백을 반환하고 로컬 시스템의 세션과 스레드를 보호합니다. DB Link에서는 이런 보호 장치를 구현하기가 매우 어렵습니다.

대안 2 – 데이터 복제와 ETL 파이프라인

실시간 조인이 아닌 주기적 동기화로도 충분한 경우에 적합합니다.

[배치 ETL 파이프라인 구조]

원격 DB (소스)
    │
    ├─ Debezium (CDC - Change Data Capture)
    │   └─ 변경된 데이터만 실시간 캡처
    │
    ├─ Apache Kafka (메시지 버스)
    │   └─ 데이터 변경 이벤트 전달
    │
    └─ Kafka Consumer
        └─ 로컬 DB (타겟) 동기화 테이블에 반영

-- 로컬 DB에 복제된 테이블을 조인 (DB Link 없음)
SELECT l.order_id, r.customer_name
FROM   local_orders l
JOIN   synced_customers r ON l.customer_id = r.customer_id;
-- synced_customers: 원격 DB를 로컬에 주기적으로 복제한 테이블

python

# 단순 배치 ETL 예시 (Python + SQLAlchemy)
import schedule
from sqlalchemy import create_engine, text

SOURCE_DB = create_engine("oracle+cx_oracle://user:pass@source-db/orcl")
TARGET_DB = create_engine("postgresql://user:pass@local-db/mydb")

def sync_customers():
    """원격 고객 데이터를 로컬 동기화 테이블에 복제"""
    with SOURCE_DB.connect() as src, TARGET_DB.connect() as tgt:
        # 변경된 데이터만 가져오기 (증분 동기화)
        rows = src.execute(text("""
            SELECT customer_id, customer_name, email, updated_at
            FROM   customers
            WHERE  updated_at >= :last_sync
        """), {"last_sync": get_last_sync_time()}).fetchall()

        if rows:
            tgt.execute(text("""
                INSERT INTO synced_customers
                    (customer_id, customer_name, email, synced_at)
                VALUES (:id, :name, :email, NOW())
                ON CONFLICT (customer_id)
                DO UPDATE SET customer_name = EXCLUDED.customer_name,
                              email         = EXCLUDED.email,
                              synced_at     = NOW()
            """), [{"id": r.customer_id, "name": r.customer_name,
                    "email": r.email} for r in rows])
            tgt.commit()
            update_last_sync_time()

schedule.every(10).minutes.do(sync_customers)

대안 3 – 이벤트 기반 아키텍처 (Apache Kafka)

데이터 변경이 발생할 때 이벤트를 발행하고, 필요한 시스템이 이벤트를 구독하여 자신의 DB에 저장하는 방식입니다.

[이벤트 기반 구조]

A 시스템 (주문)         B 시스템 (통계)
┌─────────────┐         ┌─────────────┐
│ 주문 생성   │─Kafka──→│ 주문 이벤트 │
│             │  이벤트  │ 구독 → 통계 │
│ orders 테이블│         │ DB에 저장   │
└─────────────┘         └─────────────┘
  DB Link 없음             DB Link 없음
  각자의 DB만 조회          각자의 DB만 조회

이 구조에서는 A 시스템과 B 시스템이 서로의 DB를 전혀 모릅니다. Kafka라는 중간 버스를 통해서만 데이터가 흐릅니다. A가 다운되어도 B는 이미 자신의 DB에 데이터를 갖고 있어 독립적으로 운영됩니다.

대안 비교

대안 방식적합한 상황구현 복잡도실시간성장애 격리
REST API서비스 간 실시간 조회중간높음✓ (서킷 브레이커)
gRPC고성능 내부 서비스 통신중간높음
배치 ETL대량 데이터 주기적 동기화낮음낮음
CDC + Kafka실시간에 가까운 데이터 동기화높음중간
메시지 큐비동기 이벤트 기반 통합중간중간
DB Link(레거시 불가피 상황에만)낮음높음

6. 전문가 관점 – 불가피한 경우의 안전한 사용 지침

DB Link를 완전히 제거하지 못하는 경우

레거시 시스템, 단기 마이그레이션 중간 단계, 소규모 내부 운영 도구 등에서는 DB Link를 단기간 불가피하게 유지해야 할 수 있습니다. 그 경우에도 다음 지침을 반드시 준수해야 합니다.

① 타임아웃 설정 강제화

bash

# $ORACLE_HOME/network/admin/sqlnet.ora
SQLNET.OUTBOUND_CONNECT_TIMEOUT = 10   # 연결 시도 타임아웃: 10초
SQLNET.RECV_TIMEOUT             = 30   # 데이터 수신 타임아웃: 30초
SQLNET.SEND_TIMEOUT             = 30   # 데이터 송신 타임아웃: 30초

sql

-- 세션 레벨 분산 트랜잭션 타임아웃
ALTER SYSTEM SET DISTRIBUTED_LOCK_TIMEOUT = 60 SCOPE = BOTH;

② 최소 권한 원칙 적용

sql

-- DB Link 전용 계정에는 필요한 객체에만 SELECT 권한 부여
CREATE USER db_link_readonly IDENTIFIED BY 'ComplexPass123!';

-- 절대 금지: GRANT DBA TO db_link_readonly;
-- 절대 금지: GRANT SELECT ANY TABLE TO db_link_readonly;

-- 필요한 테이블에만 최소 권한
GRANT SELECT ON customers TO db_link_readonly;
GRANT SELECT ON product_catalog TO db_link_readonly;
-- INSERT, UPDATE, DELETE는 가능한 한 부여하지 않음

③ DB Link 사용 현황 주기적 감사

sql

-- 현재 DB Link 목록 전수 조회
SELECT owner, db_link, username, host, created
FROM   dba_db_links
ORDER BY owner, db_link;

-- 현재 활성화된 DB Link 세션 모니터링
SELECT s.sid, s.serial#, s.username, s.status,
       s.sql_id, s.event, s.wait_time_micro / 1000 AS wait_ms
FROM   v$session s
WHERE  s.server = 'SHARED'
   OR  EXISTS (
       SELECT 1 FROM v$dblink d WHERE d.sid = s.sid
   );

-- DB Link를 통한 쿼리 실행 횟수 추적
SELECT db_link_name, executions, elapsed_time/1000 AS elapsed_ms
FROM   v$sql
WHERE  sql_text LIKE '%@%'  -- DB Link 사용 쿼리 식별
ORDER BY elapsed_time DESC;

④ DB Link는 단방향으로만 유지

A → B 방향의 DB Link와 B → A 방향의 DB Link가 동시에 존재하면, 양방향 장애 전파가 가능해지고 분산 트랜잭션의 복잡성이 기하급수적으로 증가합니다. 불가피하게 사용한다면 반드시 단방향으로만 구성하고, 양방향 DB Link는 절대 허용하지 않는 정책을 팀 내에 공유해야 합니다.

⑤ DB Link 사용 범위를 뷰로 제한

sql

-- DB Link를 직접 노출하지 않고 뷰로 캡슐화
-- 뷰 명칭에 외부 데이터임을 명시
CREATE OR REPLACE VIEW ext_v_customers AS
SELECT customer_id, customer_name, email  -- 필요한 컬럼만 노출
FROM   customers@remote_db_link;

-- 이 뷰를 통해서만 접근하도록 DB Link 직접 사용 금지 정책 적용
-- 개발자들이 @remote_db_link를 직접 쓰는 코드를 작성하지 않도록 가이드

뷰로 감싸면 나중에 DB Link를 ETL이나 API로 교체할 때 뷰 정의만 바꾸면 되어 마이그레이션 공수가 크게 줄어듭니다.

추천 모니터링 및 도구

도구 / 방법용도
Oracle AWR / ASHDB Link 관련 대기 이벤트 분석
Datadog / Grafana원격 DB 응답 시간 실시간 모니터링
DebeziumCDC 기반 DB Link 대체 데이터 동기화
Apache Kafka이벤트 기반 시스템 간 데이터 통합
Spring Cloud Circuit BreakerAPI 방식 전환 후 장애 격리
SonarQube 커스텀 룰DB Link 사용 코드 자동 감지·경고

결론

운영 환경에서 DB Link를 조심해야 하는 이유는 장애 전파, 분산 트랜잭션 불안정성, 보안 자격증명 노출, 네트워크 기반 성능 저하라는 네 가지 리스크가 운영 규모에 비례하여 복합적으로 터지기 때문입니다. 당장은 편리하지만, 시스템이 커질수록 DB Link 하나가 전체 서비스의 단일 장애 지점(SPOF)이 되어 돌이키기 어려운 기술 부채가 됩니다. 운영 환경에서 DB Link를 조심해야 하는 이유를 팀 전체가 공유하고, REST API·CDC·메시지 큐 등 장애를 격리할 수 있는 현대적 통합 방식으로 전환을 시작하세요. 지금 당장 전체 교체가 어렵다면, 오늘 소개한 안전 사용 지침을 즉시 적용하고 뷰로 캡슐화하여 향후 마이그레이션 부담을 최소화하는 것이 현실적인 첫걸음입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다