Alert

이 글은 Claude Code의 도움을 받아 작성되었습니다

TL;DR

  • File to DB는 파싱과 입력 품질이 핵심 난이도, DB to DB는 소스 부하와 증분 추출이 핵심 난이도
  • 적재 성능은 row-by-row INSERT → batch INSERT → bulk load → 병렬 외부 테이블 순으로 차이가 큼
  • ORACLE_LOADER는 Oracle 외부 테이블 access driver, gpload/gpfdist는 Greenplum 병렬 적재 유틸리티
  • 같은 source-target 매핑이라도 적재 경로에 따라 성능이 수십~수백 배 다를 수 있음

1. File to DB

파일(CSV, 고정폭, 로그 등)을 DB에 적재하는 패턴이다.

흐름
파일 수신 → 포맷 해석 → 레코드 분리 → 컬럼 파싱
→ 타입 변환 → 정제/검증 → 에러 레코드 분리 → 타깃 DB 적재
파일은 “신뢰할 수 없는 입력”이다

DB 테이블은 컬럼, 타입, 제약 조건이라는 형식이 있다. 반면 파일은 구조를 정해 놓았더라도 항상 품질 문제가 생긴다.

  • delimiter가 깨지거나 escape 규칙이 엇갈림
  • fixed-width offset이 밀림
  • 문자셋 불일치 (UTF-8 vs EUC-KR 등)
  • 빈 값과 null 표현 방식이 불일정

file to db의 첫 번째 문제는 적재가 아니라 파싱이다.

설계 시 결정해야 하는 것
  • bad record가 있으면 전체를 실패시킬 것인가, reject file로 분리할 것인가
  • header/trailer 검증 여부
  • 중복 레코드 처리 전략
  • staging table을 거칠 것인가, 바로 적재할 것인가
  • commit 단위 (건수 / 파일 단위)
병목 지점
  • 파일 읽기 (대용량 I/O)
  • 파싱과 타입 변환 (CPU)
  • 정렬, join, deduplication (메모리/디스크)
  • row-by-row INSERT (DB 쪽 병목)
  • commit 단위가 작으면 로그 부하 증가

2. DB to DB

소스 DB에서 데이터를 추출해 타깃 DB에 적재하는 패턴이다. 양쪽 다 구조화된 시스템이라 단순해 보이지만, 소스가 살아 있는 운영 시스템인 경우가 많아 오히려 설계 난이도가 높다.

흐름
소스 DB 연결 → 추출 범위 결정 → 조회 실행
→ 네트워크 전송 → 타입/스키마 매핑 → 타깃 적재 → 정합성 검증
핵심 문제: 소스 시스템 부하

파일은 받은 뒤 마음대로 처리하면 되지만, DB는 조회 한 번이 source 시스템의 CPU, I/O, lock, 네트워크에 영향을 준다.

  • source 쿼리를 언제 돌릴 것인가 (배치 창)
  • full load인가 incremental load인가
  • watermark를 무엇으로 잡을 것인가 (timestamp, sequence, key)
  • source consistency를 어떤 수준까지 보장할 것인가
타입 체계의 차이

서로 다른 DBMS 간 이관에서는 타입 번역 문제가 생긴다.

  • 날짜/타임존 표현 차이
  • 숫자 정밀도와 scale 차이
  • char/varchar padding 규칙 차이
  • empty string과 null 의미 차이
증분 처리

full load는 느려도 단순하다. 실무에서 어려운 것은 incremental load다.

  • 마지막 추출 시점 이후 데이터만 가져와야 함
  • UPDATE와 INSERT 구분
  • DELETE 반영 전략
  • source timestamp의 신뢰성
  • 재실행 시 duplicate/missing 방지

3. File to DB vs DB to DB

항목File to DBDB to DB
입력의 신뢰도낮음 (포맷 오류 가능)상대적으로 높음
주요 리스크파싱 실패, bad record소스 부하, 증분 기준 오류
핵심 설계 포인트포맷 해석, reject 처리추출 범위, incremental 기준
성능 병목파싱 + 적재조회 + 네트워크 + 적재
재실행 기준파일 / 배치 단위watermark / commit 단위
운영 핵심 질문파일이 정상 도착했는가소스를 안전하게 읽었는가

같은 target DB 적재라도 source가 바뀌면 완전히 다른 운영 문제를 갖게 된다.


4. 실행 구조: 2-Tier vs 3-Tier

범용 아키텍처 패턴

2-Tier/3-Tier는 소프트웨어 아키텍처에서 널리 쓰이는 계층 분리 패턴이다. 핵심은 클라이언트와 서버 사이에 중간 계층을 두느냐의 차이다.

계층2-Tier3-Tier
Tier 1클라이언트 (UI + 로직)클라이언트 (UI)
Tier 2서버 (DB)애플리케이션 서버 (비즈니스 로직)
Tier 3서버 (DB)

웹에서는 Web Server / WAS / DB Server 분리가 대표적이다. ETL에서도 같은 원리가 적용되는데, 중간 계층의 역할이 WAS 대신 메타데이터 저장소(Repository) 가 된다.

ETL에서의 2-Tier
개발 클라이언트 ↔ 실행 환경 (밀접하게 연결)

개발자가 작업을 만들고 바로 실행하는 구조다. Python 스크립트로 CSV를 읽어 DB에 INSERT하거나, Airflow DAG이 단일 서버에서 ETL을 직접 실행하는 경우가 여기에 해당한다. 설치와 운영이 단순하고 소규모 팀에서 빠르게 시작하기 좋지만, 팀 단위 협업, 권한 분리, 중앙 통제가 어려워 규모가 커지면 한계가 온다.

ETL에서의 3-Tier
Designer Client → Repository (메타데이터/버전 관리) → Runtime Server (실행)

설계하는 곳, 작업 정의를 저장하는 곳, 실제 배치가 도는 곳이 분리된다. Informatica PowerCenter(Designer / Repository / Integration Service), AWS Glue(Glue Studio / Data Catalog / Job Runner) 등이 대표적이다.

3-Tier의 핵심은 역할 분리다.

역할하는 일
ETL 개발자Designer에서 매핑/변환/SQL 로직 작성 → Repository에 체크인
ETL 운영자Repository에서 검토 → Runtime Server에 배포, 스케줄 설정, 모니터링, 장애 대응
DBARepository DB와 Runtime Server 인프라 관리. ETL 로직 작성은 하지 않음

2-Tier에서는 이 역할을 한 사람이 다 하는 경우가 많다. 3-Tier는 개발자를 **“만드는 사람”**에 집중시키고 **“돌리는 사람”**과 분리할 수 있는 구조를 제공한다.

Repository는 ETL 작업의 정의 자체를 저장하고 관리하는 중앙 저장소다. 코드 프로젝트에서 Git이 하는 역할과 비슷하다.

Repository가 관리하는 것:

  • 매핑 정의 (source 컬럼 A → target 컬럼 B)
  • 변환 규칙 (타입 변환, 필터, 집계 로직)
  • 연결 정보 (어떤 DB에 어떤 인터페이스로 붙는지)
  • 작업 의존성 (A 작업 끝나면 B 실행)
  • 변경 이력 (누가 언제 뭘 바꿨는지)

Repository가 없으면(2-Tier) 작업 정의가 개발자 로컬이나 서버 파일 시스템에 흩어져서 “누가 뭘 바꿨는지”, “운영에 어떤 버전이 올라가 있는지” 추적이 어려워진다.

비교
항목2-Tier3-Tier
구성 복잡도낮음높음
역할 분리개발/운영/인프라를 한 사람이 담당개발자/운영자/DBA 분리 가능
작업 정의 관리로컬 파일, 서버에 직접 저장Repository에서 중앙 관리 + 버전 이력
배포직접 복사/실행Repository → Runtime Server 배포 절차
모니터링개발자가 로그 직접 확인운영자가 중앙 모니터링 화면에서 확인
적합한 환경소규모 팀, 단순 배치다수 개발자, 운영 통제 필요한 환경

3-Tier가 "더 좋은" 것은 아니다

3-Tier는 “더 통제 가능한” 구조다. 소규모 환경에서 3-Tier를 도입하면 오버엔지니어링이 된다. 데이터 이동 패턴의 리스크를 보고, 그 리스크를 감당할 수 있는 실행 구조를 고르면 된다.


5. 적재 방식에 따른 성능 차이

데이터를 target DB에 넣는 방식에 따라 성능이 크게 달라진다. ODBC vs JDBC같은 연결 인터페이스로 DB에 붙는 것과, 실제로 데이터를 넣는 적재 방식은 별개의 문제다.

느림 ←─────────────────────────────────────────────────────────→ 빠름

row-by-row     batch INSERT       bulk load          병렬 외부 테이블
INSERT         (N건씩 묶음)       (DB 전용 유틸리티)       (gpfdist 등)
방식동작특징
row-by-row INSERT한 건씩 INSERT + COMMIT가장 느림. 트랜잭션 로그 부하 큼
batch INSERTN건씩 묶어서 INSERT중간 성능. JDBC addBatch()
bulk loadDB 전용 유틸리티로 대량 적재로그 최소화, 제약 조건 지연 검사 등 DB 엔진 레벨 최적화
병렬 외부 테이블파일을 DB가 직접 병렬로 읽음가장 빠름. DB 아키텍처에 의존

6. Oracle 적재 도구

ORACLE_LOADER

Oracle의 외부 테이블(External Table) access driver다. DB 외부의 파일을 SQL로 조회할 수 있는 가상 테이블로 정의한다. 데이터를 DB에 복사하지 않고 파일을 직접 읽는 방식이다.

CREATE TABLE ext_employees (
    emp_id    NUMBER,
    emp_name  VARCHAR2(100),
    salary    NUMBER
)
ORGANIZATION EXTERNAL (
    TYPE ORACLE_LOADER
    DEFAULT DIRECTORY data_dir
    ACCESS PARAMETERS (
        RECORDS DELIMITED BY NEWLINE
        FIELDS TERMINATED BY ','
    )
    LOCATION ('employees.csv')
);
 
-- 일반 테이블처럼 조회
SELECT * FROM ext_employees WHERE salary > 50000;
 
-- 내부 테이블로 적재
INSERT INTO employees SELECT * FROM ext_employees;
SQL*Loader

CLI 기반 bulk load 유틸리티다. 제어 파일(control file)로 파일 포맷과 매핑을 정의하고, 대량 데이터를 빠르게 적재한다.

도구역할
SQL*LoaderCLI bulk load. 제어 파일 기반
ORACLE_LOADER외부 테이블 access driver. SQL로 파일 접근
Data PumpOracle 간 논리적 백업/복원 (expdp/impdp)

7. Greenplum 적재 도구

Greenplum의 MPP 아키텍처를 활용하면 파일 적재를 병렬로 처리할 수 있다.

gpfdist — 파일 분배 서버

각 Segment가 파일을 병렬로 직접 읽을 수 있도록 분배하는 경량 HTTP 서버다. Coordinator를 거치지 않으므로 단일 COPY보다 수배~수십 배 빠르다.

                    ┌→ Segment 1 (파일 일부 읽기)
파일 → gpfdist ─────┼→ Segment 2 (파일 일부 읽기)
                    └→ Segment 3 (파일 일부 읽기)
gpfdist -d /data/files -p 8080 &
CREATE EXTERNAL TABLE ext_logs (
    log_time TIMESTAMP,
    message  TEXT
)
LOCATION ('gpfdist://etl-host:8080/logs/*.csv')
FORMAT 'CSV' (HEADER);
 
-- 병렬 적재
INSERT INTO logs SELECT * FROM ext_logs;
gpload — gpfdist 래퍼

gpfdist + INSERT/UPDATE + 에러 처리를 YAML 설정 파일로 감싼 유틸리티다. 반복적인 적재 작업을 자동화할 때 사용한다.

VERSION: 1.0.0.1
DATABASE: warehouse
USER: etl_user
HOST: gp-coordinator
PORT: 5432
GPLOAD:
  INPUT:
    - SOURCE:
        FILE: ['/data/sales_*.csv']
    - FORMAT: csv
    - HEADER: true
  OUTPUT:
    - TABLE: public.sales
    - MODE: INSERT
  PRELOAD:
    - REUSE_TABLES: true
gpload -f load_sales.yml

gpload의 내부 동작

gpload는 내부적으로 gpfdist를 띄우고, 외부 테이블을 생성하고, INSERT/UPDATE를 실행한 뒤 정리하는 과정을 자동으로 수행한다. gpfdist의 상위 래퍼다.


관련 노트