Recent Posts

All Posts →

터미널 git diff를 GitHub처럼 보기: delta와 word-diff

터미널의 git diff는 한 글자만 고쳐도 줄 전체를 빨강/초록으로 칠합니다. delta의 word-diff-regex 설정과 git 내장 word-diff를 조합해서, 한글 문서에서도 바뀐 부분만 GitHub처럼 정밀하게 강조하는 방법을 정리합니다.

GitHub UI에서 diff를 보면 바뀐 단어만 진한 배경색으로 강조됩니다. 터미널의 `git diff`는 그렇지 않습니다. 한 글자만 고쳐도 줄 전체가 빨강/초록으로 칠해집니다.

한글 문서를 쓰거나 교열할 때 이 차이가 특히 크게 느껴집니다. 문장 하나에서 조사만 바꿨는데 diff에서는 삭제 줄과 추가 줄이 통째로 나오니, 눈으로 두 줄을 대조해서 어디가 달라졌는지 찾아야 합니다.

기본 git diff 화면

남긴다 → 기록한다, 덕분에 → 이 있어서, 방식 → 방법, 흔하게 → 흔히. 네 군데가 바뀌었는데 위 화면만 보고는 바로 알기 어렵습니다.

이 글에서는 이 문제를 해결한 설정을 공유합니다. Ubuntu 24.04 + zsh + git 2.43 + scm_breeze 환경에서 확인했습니다.

1. delta 설치와 설정

delta는 git의 pager를 대체하는 도구입니다. pager는 `git diff`나 `git log`의 긴 출력을 한 화면씩 넘겨 보여주는 프로그램으로, 기본값은 `less`입니다. delta는 이 자리를 대신하면서 구문 강조, 줄 번호, 그리고 줄 안에서 바뀐 부분만 강조하는 기능을 제공합니다.

1.1. 설치

apt에도 `git-delta`가 있지만 Ubuntu 24.04에 들어 있는 버전은 2023년 6월에 나온 0.16.5입니다. 최신 버전은 2026년 3월에 나온 0.19.2로 약 3년의 차이가 있습니다. sudo 없이 최신 바이너리를 받는 편이 낫습니다.

VER=0.19.2
curl -L -o /tmp/delta.tar.gz \
  https://github.com/dandavison/delta/releases/download/$VER/delta-$VER-x86_64-unknown-linux-gnu.tar.gz
tar xzf /tmp/delta.tar.gz -C /tmp
install -m755 /tmp/delta-$VER-x86_64-unknown-linux-gnu/delta ~/.local/bin/delta
delta --version

macOS는 brew install git-delta, Arch는 `pacman -S git-delta`로 받으면 됩니다.

1.2. ~/.gitconfig 설정

~/.gitconfig
[core]
	pager = delta

[interactive]
	diffFilter = delta --color-only

[delta]
	navigate = true
	line-numbers = true
	hunk-header-decoration-style = blue box
	file-style = bold yellow
	file-decoration-style = yellow ul
	# 바뀐 부분만 배경색으로 강조 (GitHub UI와 같은 방식)
	minus-emph-style = normal "#7a2323"
	plus-emph-style = normal "#2b5b2b"
	# 한글은 음절, 영문·코드는 단어 단위로 토큰을 나눈다
	word-diff-regex = "[가-힣]|[A-Za-z0-9_]+|[^ \t]"
	max-line-distance = 1.0

[diff]
	colorMoved = default
	algorithm = histogram

[merge]
	conflictStyle = zdiff3

이제 평소처럼 `git diff`를 치면 이렇게 나옵니다.

delta 통합 뷰

바뀐 줄은 은은한 배경색, 그 줄에서 실제로 달라진 부분은 진한 배경색으로 두 단계로 구분됩니다. GitHub의 diff 화면과 같은 방식입니다.

1.3. word-diff-regex

위 설정에서 가장 중요한 줄은 `word-diff-regex`입니다. 이 줄을 빼면 아래처럼 나옵니다.

delta 기본 토큰 단위 강조

delta의 기본값은 `\w+`인데, 유니코드 단어 문자 기준이라 한글 어절 하나가 통째로 토큰이 됩니다. 그래서 `저장하는 역할을 맡는다 → 저장한다`가 어절 세 개를 통째로 칠해 버립니다. 조사 하나만 고쳐도 앞 단어까지 같이 강조되니 정밀도가 떨어집니다.

`[가-힣]`을 앞에 두면 한글은 음절 단위로 쪼개지고, `[A-Za-z0-9_]+`가 뒤에 있어서 영문 식별자와 클래스 이름은 통째로 유지됩니다. 결과적으로 "저장하는 역할을 맡는다 → 저장다"처럼 바뀐 부분만 정확히 좁혀집니다.

Caution

이 정규식은 delta에서만 통합니다. delta는 Rust regex(유니코드 인식)를 쓰지만, git 내장 `--word-diff-regex`는 POSIX ERE라 멀티바이트 문자 범위를 처리하지 못합니다.

$ git diff --word-diff-regex='[가-힣]|[A-Za-z0-9_]+' ...
fatal: invalid regular expression: [가-힣]|[A-Za-z0-9_]+

git 쪽에서는 대신 [^[:space:]](공백이 아닌 모든 문자)를 쓰면 됩니다. 아래 인라인 word-diff 절에서 씁니다.

1.4. 같이 켜면 좋은 설정

위 `~/.gitconfig`에 끼워 둔 나머지 항목도 짧게 설명합니다.

설정 효과

interactive.diffFilter = delta --color-only

`git add -p`에서도 같은 강조가 적용됩니다

diff.colorMoved = default

내용은 그대로고 위치만 옮긴 블록을 별도 색으로 표시합니다. 절이나 함수 순서를 바꿀 때 유용합니다

diff.algorithm = histogram

문단·함수를 옮겼을 때 diff 덩어리가 더 깔끔하게 묶입니다

merge.conflictStyle = zdiff3

충돌 시 양쪽뿐 아니라 공통 조상 내용까지 보여줍니다

delta.navigate = true

pager에서 n / `N`으로 파일 사이를 건너뜁니다

2. 상황별 diff 보기

delta 통합 뷰가 기본이지만, 보는 목적에 따라 다른 방식이 더 편할 때가 있습니다.

2.1. 인라인 word-diff: 한 줄에 겹쳐 보기

delta는 삭제 줄과 추가 줄을 위아래로 나란히 보여줍니다. 문장 교열처럼 한 줄 안의 변화만 확인하고 싶을 때는 git 내장 `--word-diff`가 더 편합니다. 삭제와 추가가 한 줄에 겹쳐 나오므로 문장을 두 번 읽지 않아도 됩니다.

git diff --word-diff=color --word-diff-regex='[^[:space:]]'
인라인 word-diff

남긴(빨강)기록한(초록)`다`처럼 바로 이어져 나옵니다. `--word-diff-regex='[^[:space:]]'`가 없으면 어절 전체가 잡히므로 이 옵션은 꼭 넣어야 합니다.

delta는 --word-diff 출력 형식을 이해하지 못해 색이 깨집니다. 그래서 이 명령은 pager를 `less`로 되돌려야 합니다.

2.2. 좌우 2단 분할

넓은 화면에서 코드를 볼 때는 2단 분할이 낫습니다.

git -c delta.side-by-side=true diff
좌우 2단 분할

기본값으로 켜지 않고 필요할 때만 켜는 편을 권합니다. 한글 문장은 한 줄이 길어서 폭이 반으로 줄면 줄바꿈이 많이 생깁니다.

3. zsh + scm_breeze에 붙이기

scm_breeze를 쓰면 gd(= git diff), gdc(= git diff --cached) 같은 단축 alias가 이미 있습니다. 이들은 git 함수를 거치므로 core.pager = delta 설정을 그대로 탑니다. 즉 `gd`는 아무것도 안 해도 바로 delta로 나옵니다.

문제는 scm_breeze가 기본 제공하는 gdw(= git diff --word-diff)입니다. delta를 타면서 색이 깨지고, 한글 정규식도 안 붙어 있습니다. 이것만 다시 정의합니다.

~/.zshrc`의 scm_breeze `source뒤에 넣습니다.

~/.zshrc
# delta 는 --word-diff 출력 형식을 이해하지 못해 색이 깨지므로
# word-diff 계열만 delta 를 우회하도록 다시 정의한다.
# scm_breeze 의 git 함수를 그대로 호출하므로 gs 가 매긴 파일 번호도 쓸 수 있다.
unalias gdw 2>/dev/null
_scmb_word_diff() {
    GIT_PAGER='less -R' git diff --word-diff=color \
        --word-diff-regex='[^[:space:]]' "$@"
}
gdw()  { _scmb_word_diff "$@"; }            # 단어(한글은 음절) 단위 인라인 diff
gdwc() { _scmb_word_diff --cached "$@"; }   # 스테이징된 변경만
gdws() { _scmb_word_diff -U0 "$@"; }        # 문맥 줄 없이 바뀐 곳만
gd2()  { git -c delta.side-by-side=true diff "$@"; }   # delta 좌우 2단 분할

`unalias gdw`가 필요한 이유는, zsh에서 alias가 같은 이름의 함수보다 먼저 잡히기 때문입니다. `gds`를 쓰지 않고 `gd2`로 이름을 지은 것도 oh-my-zsh의 git 플러그인이 이미 `gds`를 쓰고 있어서입니다.

scm_breeze를 안 쓴다면 `~/.gitconfig`에 alias로 넣어도 됩니다.

~/.gitconfig
[alias]
	wd = "!git -c core.pager=less diff --word-diff=color --word-diff-regex='[^[:space:]]' "
	sd = "!git -c delta.side-by-side=true diff "

4. 정리

명령 하는 일

git diff (gd)

delta 통합 뷰. 바뀐 단어만 진한 배경색

gd2

delta 좌우 2단 분할

gdw

삭제·추가를 한 줄에 겹쳐 보는 인라인 word-diff

gdwc

스테이징된 변경만 word-diff

gdws

문맥 줄 없이 바뀐 곳만 (-U0)

한글 문서를 다룬다면 두 가지만 기억하면 됩니다.

  1. delta에는 word-diff-regex = "[가-힣]|[A-Za-z0-9_]+|[^ \t]"

  2. git 내장 word-diff에는 --word-diff-regex='[^[:space:]]'

기본값 그대로 쓰면 한글은 어절 통째로 강조돼서 GitHub만큼 정밀해지지 않습니다.

5. 참고 자료

Hugging Face 오픈 모델의 호환성을 만드는 사실상의 표준들

Hugging Face에서 받은 오픈 모델을 누가 학습했든 다운로드해서 바로 실행할 수 있는 배경에는 Safetensors 가중치 포맷, transformers의 config 규약, 소수 아키텍처로의 수렴이라는 사실상의 표준이 맞물려 있습니다.

모델 레이어를 다룬 글에서 모델이 "아는 것"은 가중치(weight)라는 숫자값에 압축돼 있다고 정리했습니다. 그 가중치는 결국 파일로 저장되어 Hugging Face에서 공유됩니다. Hugging Face는 오픈 모델과 데이터셋, 데모를 올리고 내려받는 중심 플랫폼으로, 흔히 "머신러닝계의 GitHub"에 비유됩니다. 그런데 이렇게 공유된 모델은 누가 어떤 환경에서 학습했든, 다운로드해서 바로 실행할 수 있는 경우가 많습니다. 이 글은 그 호환성의 토대가 되는 사실상의 표준(de facto standard)들을 정리합니다.

크게 모델 가중치 포맷, 모델 아키텍처 규약, 메타데이터/설정 표준 세 층위로 나눠 볼 수 있습니다.

1. 모델 가중치 저장 포맷

포맷 특징

Safetensors

현재 사실상 표준. 메모리 매핑[1]이 가능하고, pickle(Python 기본 직렬화 방식)을 쓰지 않아 역직렬화 시 임의 코드가 실행되는 보안 취약점이 없습니다.

GGUF

llama.cpp 생태계의 표준. 양자화[2] 정보를 파일 자체에 포함하며, CPU·엣지를 비롯한 다양한 환경의 추론에 강합니다.

PyTorch (.bin/.pt)

pickle 기반의 레거시 포맷. 여전히 많이 쓰이지만 역직렬화 과정에 보안 우려가 있습니다.

신경망의 가중치는 텐서(tensor), 즉 숫자가 여러 차원으로 늘어선 배열에 담깁니다. 스칼라(0차원)·벡터(1차원)·행렬(2차원)을 임의 차원으로 일반화한 것으로, 각 레이어의 가중치 하나하나가 이런 텐서입니다. 텐서마다 배열의 형태(shape, 예: 4096 × 4096)와 각 숫자의 자료형(dtype, 예: float16·bfloat16)이 정해져 있습니다.

Safetensors는 이 텐서의 이름(key)과 shape, dtype, 바이너리 데이터를 단순한 헤더+바이너리 구조로 저장합니다. 그래서 어떤 프레임워크든 쉽게 읽을 수 있습니다.

다국어 지원 능력도 이 가중치 안에 들어 있습니다. 학습 시 Common Crawl, Wikipedia 등에서 수집한 수십~수백 개 언어의 텍스트 패턴이 신경망 파라미터 전체에 걸쳐 분산 표현(distributed representation) 으로 인코딩됩니다. "한국어 사전" 같은 별도 파일이 있는 게 아니라, embedding layer와 attention/FFN layer의 수치 값들에 각 언어의 문법·어휘·의미가 녹아들어 있습니다. 그래서 특정 언어만 빼거나 넣기가 쉽지 않습니다.

2. 모델 아키텍처 규약: transformers 라이브러리의 역할

Hugging Face `transformers`는 수많은 모델 아키텍처를 통일된 인터페이스로 불러오고 실행하게 해주는 오픈소스 Python 라이브러리입니다. Transformer 아키텍처에서 이름을 따왔지만 지금은 그 밖의 구조도 폭넓게 지원하며, 모델을 불러오는 방식의 사실상 표준 역할을 합니다. 모델을 어떻게 읽고 실행할지는 가중치 파일 옆에 놓인 설정 파일들이 정의합니다.

  • config.json — 모델 구조 정의 (model_type, hidden_size, num_attention_heads 등)

  • tokenizer.json / tokenizer_config.json — 토크나이저 정의. BPE(Byte Pair Encoding) 같은 알고리즘으로 다국어 텍스트를 토큰으로 분리하는 토큰 사전(vocabulary, 보통 3만~25만 토큰)을 담습니다. 한국어 "안녕하세요"도, 영어 "hello"도 이 사전의 토큰 조합으로 표현됩니다.

  • generation_config.json — 생성 파라미터

config.json 예시
{
  "model_type": "llama",
  "hidden_size": 4096,
  "num_attention_heads": 32,
  "num_hidden_layers": 32,
  "vocab_size": 32000
}

model_type`이 `"llama"`이면, 라이브러리가 `LlamaForCausalLM 클래스를 자동으로 선택해 가중치를 로드합니다. 그래서 Llama 아키텍처 기반 모델은 누가 학습했든 동일한 코드로 실행됩니다.

3. 추론 엔진 간 호환성

같은 Hugging Face 모델 하나(safetensors + config.json)를 여러 추론 엔진(학습된 모델을 받아 실제로 실행하고 응답을 생성하는 런타임)이 받아 실행할 수 있습니다. 일부는 파일을 그대로 읽고, 일부는 포맷 변환을 거칩니다.

  • transformers: Hugging Face 표준 Python 라이브러리

  • vLLM: 고성능 서빙에 특화

  • TGI(Text Generation Inference): Hugging Face 자체 서빙 엔진

  • Ollama / llama.cpp: GGUF로 변환해 CPU/엣지에서 실행

  • ONNX Runtime: ONNX(Open Neural Network Exchange) 포맷으로 변환해 실행

이것이 가능한 이유는 아키텍처가 표준화되어 있기 때문입니다. 대부분의 오픈 모델이 소수의 아키텍처(Llama, Mistral, Qwen, Gemma 등)를 따르고, 각 추론 엔진이 이 아키텍처들을 미리 구현해 둡니다.

4. 앞 글의 레이어 분류와 겹쳐 보기

앞 글에서는 LLM에 정보가 들어가는 자리를 모델 레이어와 프롬프트 레이어로 나누고, 아키텍처·디코딩 같은 축을 덧붙였습니다. Hugging Face 모델 저장소의 파일 구성을 그 분류에 겹쳐 보면 거의 일대일로 대응합니다.

파일 대응하는 레이어 담는 것

model.safetensors

모델 레이어

학습으로 결정된 가중치(텐서) 그 자체

config.json (model_type)

아키텍처 레이어

"어떤 형태로 정보를 담을 것인가" — 레이어 수, 헤드 수, 아키텍처 종류

tokenizer.json

프롬프트 레이어 (토큰화)

컨텍스트로 들어갈 텍스트를 토큰으로 쪼개는 규칙

generation_config.json

디코딩/샘플링 레이어

temperature, top-p 같은 출력 제어 기본값

바꾸려는 대상에 따라 손대는 파일이 갈립니다. 지식 자체를 바꾸려면 `model.safetensors`를, 샘플링이나 출력 길이 같은 생성 기본값만 바꾸려면 `generation_config.json`을 건드립니다. 앞 글이 "정보가 어디에 저장되는가"의 지도였다면, 이 파일 구성은 그 지도가 디스크에 실제로 어떻게 놓이는지를 보여줍니다.

5. 핵심 정리

공식적인 "표준 기구"가 정한 스펙이라기보다는, 다음 세 가지가 맞물린 결과입니다.

  1. Safetensors — 텐서를 저장하는 단순하고 안전한 바이너리 포맷

  2. transformers의 config 규약 — `config.json`의 `model_type`으로 아키텍처를 식별

  3. 소수 아키텍처로의 수렴 — 대부분의 모델이 Llama/Mistral 계열 아키텍처를 채택

이 세 가지가 맞물려 "누가 학습했든 다운받으면 바로 돌릴 수 있는" 생태계가 만들어졌습니다. ISO 같은 공식 표준이 아니라 사실상의 표준(de facto standard)입니다.

6. 참고 자료


1. 파일을 통째로 읽어 들이지 않고, 디스크의 내용을 필요한 부분만 메모리 주소에 바로 연결해 다루는 방식. 큰 모델도 빠르게 로드할 수 있습니다.
2. 가중치를 더 적은 비트로 표현해(예: 16비트를 4비트로) 모델 용량과 메모리 사용량을 줄이는 기법. 약간의 정확도를 내주고 크기를 줄입니다.

대량 데이터를 입출력하는 DBMS별 명령어와 도구(MySQL, Oracle, SQL Server, PostgreSQL)

MySQL, Oracle, MS SQL Server, PostgreSQL이 제공하는 대량 데이터 입출력 방식(LOAD DATA INFILE, SQL*Loader, BULK INSERT, COPY 등)과 성능 옵션을 정리합니다.

DBMS별로 최적화된 대량 데이터 로딩과 추출 기능을 전용 SQL 구문이나 도구로 제공합니다. 이 방식들은 구현 원리상 자바 애플리케이션으로는 도달하기 어려운 성능을 발휘합니다.

1. MySQL

MySQL에서는 SELECT …​ INTO OUTFILE 명령으로 데이터를 익스포트하고, LOAD DATA INFILE 명령으로 임포트합니다. SELECT …​ INTO OUTFILE 은 다음과 같은 형식으로 사용합니다.

SELECT [칼럼명]
INTO OUTFILE [파일명]
FROM [테이블명]

FIELDSLINES 절을 추가하면 필드와 행 사이를 구분하는 문자를 지정할 수 있습니다. 디폴트로는 탭 문자(\t)로 칼럼 사이를, 새 줄 문자(\n)로 행 사이를 구분합니다. baseball.players 테이블의 내용을 players.csv 라는 파일명으로 출력하는 예시는 아래와 같습니다.

SELECT back_num, name, position
INTO OUTFILE 'players.csv'
FROM baseball.players
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'

LOAD DATA INFILE 명령어는 아래와 같은 형식으로 사용합니다.

LOAD DATA INFILE [파일명] INTO TABLE [테이블명]

마찬가지로 필드와 라인 구분자를 지정할 수 있고, SELECT …​ INTO OUTFILE 과 같은 방식으로 옵션을 지정합니다. players.csv 의 내용을 baseball.players 테이블로 로딩하는 명령어는 아래와 같습니다.

LOAD DATA INFILE 'players.csv'
INTO TABLE baseball.players
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'

대량 적재의 성능은 스토리지 엔진에 따라 손볼 곳이 다릅니다. MySQL 5.5부터 기본 스토리지 엔진이 InnoDB이고 요즘은 대부분 InnoDB를 쓰므로, InnoDB 기준으로 정리합니다. InnoDB 테이블에 대량으로 적재할 때는 innodb_buffer_pool_size 를 충분히 확보하고, 적재 구간에서 자동 커밋과 무결성 검사를 잠시 꺼 부가 작업을 줄이는 방식이 효과적입니다.

SET autocommit = 0;
SET unique_checks = 0;
SET foreign_key_checks = 0;

LOAD DATA INFILE 'players.csv'
INTO TABLE baseball.players
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n';

COMMIT;

적재가 끝나면 꺼 두었던 unique_checksforeign_key_checks 는 다시 켜야 합니다. 참고로 지금은 거의 쓰이지 않는 MyISAM 엔진에서는 bulk_insert_buffer_size, myisam_sort_buffer_size 같은 변수가 대량 삽입 성능에 영향을 줍니다. 다만 이 값들은 InnoDB 테이블에는 적용되지 않습니다.

LOAD DATA INFILE 명령은 mysqlimport 라는 도구로도 수행할 수 있습니다. 이 도구를 쓸 때 네트워크를 통해 파일이 전송된다면 데이터를 압축해 성능 향상을 꾀할 수 있습니다. 예전에는 --compress 옵션을 썼으나, MySQL 8.0.18부터는 이 옵션이 deprecated되어 --compression-algorithms 로 압축 알고리즘을 지정하는 방식으로 바뀌었습니다.

한 가지 주의할 점은 최근 MySQL이 보안상의 이유로 파일 입출력에 제약을 둔다는 것입니다. SELECT …​ INTO OUTFILE 과 (LOCAL 을 붙이지 않은) LOAD DATA INFILE 은 서버의 secure_file_priv 시스템 변수가 가리키는 디렉터리 안에서만 동작하며, 이 변수는 MySQL 5.7.6부터 기본값이 설정되어 있습니다. 또한 클라이언트 쪽 파일을 읽는 LOAD DATA LOCAL INFILE 은 MySQL 8.0부터 local_infile 변수가 기본적으로 꺼져 있어, 서버와 클라이언트 양쪽에서 이를 명시적으로 켜야 동작합니다.

자세한 내용은 아래 페이지를 참조합니다.

2. Oracle

Oracle에서는 SQL*Loader, Data Pump API 같은 도구나 External Table을 생성해서 데이터 로딩을 할 수 있습니다.

SQL*Loader에서 다루는 파일들은 역할이 상세하게 구분됩니다.

  • 컨트롤 파일 : 수행할 명령문을 담은 파일.

  • 입력 데이터 파일 : DB로 임포트할 데이터를 담은 파일. 컨트롤 파일에서 INFILE * 구문을 사용하면 컨트롤 파일에 데이터를 포함시킬 수도 있습니다.

  • 로그 파일 : 작업 과정과 결과를 기록하는 파일.

  • Bad 파일 : 입력이 거부된 행들이 저장되는 파일. 예를 들면 형식이 틀려서 SQL*Loader가 해석할 수 없거나 DB의 제약조건을 충족시키지 못한 행들이 이 파일로 기록됩니다.

  • Discard 파일 : 컨트롤 파일에서 정한 규칙에 의해 필터링되는 행들이 기록되는 파일.

컨트롤 파일은 아래와 같이 명령행에서 위치를 지정합니다.

sqlldr userid=scott/tiger control='./control.ctl'

컨트롤 파일 안에서는 아래와 같이 입력할 데이터 파일의 위치와 형식, 값이 들어갈 테이블을 지정합니다.

control.ctl
LOAD DATA
INFILE 'players.csv'
INTO players
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
( back_num, name, position )

성능을 높이기 위해 아래 옵션값을 조정해볼 수 있습니다.

  • direct : Direct Path Load 를 사용할지를 true/false로 지정합니다. 이 방식을 쓰면 내부적으로 SQL 명령을 생성하지 않고 데이터베이스 블록에 직접 쓰기 작업을 하기 때문에 성능이 더 좋아집니다. direct 옵션의 디폴트는 false로, 특별히 지정하지 않으면 데이터 로딩을 INSERT 명령으로 수행하는 Conventional Path Load 가 활성화됩니다. Direct Path Load 를 쓸 때는 데이터 로딩 도중에 인덱스 업데이트와 제약조건 등이 바로 적용되지 않으며, 로딩 작업의 시작과 끝에 테이블과 인덱스에 락을 잡습니다. 따라서 로딩 작업 도중에 다른 프로세스에서도 대상 테이블에 접근해야 한다면 적합하지 않습니다. 클러스터링된 테이블에도 사용할 수 없습니다.

  • parallel : 병렬 수행 여부입니다. parallel 모드로 데이터를 올리는 중에는 인덱스가 업데이트되지 않으므로, 데이터 업로드가 끝난 후에 인덱스를 재생성해줘야 합니다. direct=true 와 함께 쓰려면 대상 테이블에 트리거나 제약조건이 비활성화되어 있어야 합니다.

  • rows : 한 번에 운반하는 행의 개수입니다. direct=false 일 때는 commit이 일어나는 단위로, 스프링 배치의 commit-interval 과 유사합니다. direct=trueDirect Path Load 를 쓸 때도 rows가 데이터를 저장하는 단위라는 점은 비슷하지만, 이 모드에서는 중간에 인덱스가 갱신되지 않습니다.

그 외에도 direct 옵션을 사용할 때는 columnarrayrows, streamsize, 아닐 때는 bindsize 등의 옵션으로 데이터의 운반 단위를 조정할 수 있습니다. 위의 옵션은 명령행에서 지정할 수 있습니다. 예를 들어 direct와 parallel 모드를 동시에 사용해서 데이터를 임포트하려면 아래와 같이 실행합니다.

sqlldr userid=scott/tiger control='./control1.ctl' DIRECT=TRUE PARALLEL=true
sqlldr userid=scott/tiger control='./control2.ctl' DIRECT=TRUE PARALLEL=true

Oracle 9i부터는 데이터를 테이블스페이스 바깥의 파일로 유지하는 External Table이 제공됩니다. CSV 형식의 외부 파일을 External Table로 연결하고 SELECT 구문으로 조회해서 테이블에 저장하는 작업도 가능합니다. External Table에 SELECT를 할 때 JOIN, SORT도 사용할 수 있고, 읽기 작업을 병렬로 수행할 수 있다는 장점도 있습니다. 그러나 읽기 전용 작업만 허용되기 때문에 DELETE나 UPDATE 같은 SQL을 External Table을 대상으로 수행할 수는 없습니다.

같은 Oracle DB 간의 테이블 단위 데이터 로딩에는 import, export 유틸리티가 더 성능에 유리합니다. Oracle Database 10.1(10g release 1) 이후부터는 이를 대체하는 Data Pump API가 제공됩니다. 자세한 내용은 아래를 참조합니다.

3. MS SQL Server

SQL Server에서는 OPENROWSET과 BULK INSERT 구문이 제공되고, bcp나 SSIS(Integration Services) 패키지 같은 별도의 도구로도 비슷한 작업을 수행할 수 있습니다.

BULK INSERT 구문은 파일을 지정하는 것 외에는 보통의 INSERT 문처럼 사용할 수 있습니다. players.csv 에서 players 테이블로 데이터를 입력하는 예제는 아래와 같습니다.

BULK INSERT players
FROM 'players.csv'
WITH (
  FIELDTERMINATOR = ',',
  ROWTERMINATOR = '\n'
)

BULK INSERT를 할 때는 아래와 같은 옵션을 조정해 성능을 높일 수 있습니다.

  • tablock : 대량 입력 작업 동안 테이블을 잠급니다.

  • rows_per_batch : 하나의 트랜잭션으로 처리할 행의 개수입니다. 스프링 배치의 commit-interval 속성과 유사합니다. 되도록 큰 값을 지정하는 것이 성능 향상에 유리하나, 인덱스가 있는 테이블에서 지나치게 큰 값을 지정하면 메모리 사용량이 많아지고 병렬 업로드 시 테이블 락을 유발할 수 있습니다.

앞선 예제에서 테이블 락을 걸고 3000개씩 commit을 하는 옵션을 추가하면 다음과 같습니다.

BULK INSERT players
FROM 'players.csv'
WITH (
  FIELDTERMINATOR = ',',
  ROWTERMINATOR = '\n',
  TABLOCK,
  ROWS_PER_BATCH = 3000
)

파일로부터 데이터를 읽어 들이는 또 하나의 방법인 OPENROWSET 구문은 아래와 같이 SELECT 문장과 함께 사용합니다.

SELECT *
FROM OPENROWSET('MSDASQL',
     'Driver={Microsoft Text Driver (*.txt; *.csv)};DefaultDir=C:\data;',
     'SELECT * FROM players.csv')

칼럼명은 텍스트 파일의 첫 줄에 포함시키거나 Schema.ini 파일로 정의할 수 있습니다. 다만 위 예제에서 쓰인 Microsoft Text Driver 는 32비트 전용인 구형 Jet 기반 드라이버로, 현재는 권장되지 않습니다. 최근 환경에서는 64비트를 지원하는 ACE(Access Connectivity Engine) 공급자(Microsoft.ACE.OLEDB.x)를 쓰거나, 아예 SQL Server가 직접 파일을 읽는 OPENROWSET(BULK …​) 구문을 사용하는 편이 낫습니다.

SQL Server는 bcp라는 별도의 데이터 임포트/익스포트 유틸리티도 제공합니다. bcp는 본래 Sybase에서 유래한 도구로, SAP ASE(과거 Sybase ASE)의 bcp와도 사용법이 비슷합니다. 데이터 익스포트와 임포트를 모두 SQL Server를 대상으로 할 때는 네이티브 형식을 이용하면 성능이 더 향상됩니다. BULK INSERT 구문에서는 DATAFILETYPE = 'native' 옵션으로, bcp에서는 -n 옵션을 지정해서 네이티브 파일을 사용할 수 있습니다.

그리고 SQL Server와 함께 제공되는 데이터 통합 도구인 SSIS(Integration Services) 패키지로도 다양한 자원에서 데이터를 입출력할 수 있습니다. GUI 도구에서 작업을 설계하고 dtexec.exe 라는 실행 파일로 커맨드라인에서 작업을 실행하는 방식도 가능합니다. ETL 도구와 유사한 접근법입니다. 앞에서 BULK INSERT 문의 옵션이었던 tablock, rows_per_batch 등의 값도 SSIS에서 설정할 수 있습니다.

각각의 방식에 대한 자세한 설명은 아래 페이지를 참고합니다.

4. PostgreSQL

PostgreSQL에서는 COPY 구문, Foreign Data Wrapper, pg_bulkload를 활용해 텍스트 파일을 바로 DB에 익스포트/임포트할 수 있습니다.

테이블에 있는 데이터를 COPY 구문으로 아래와 같이 익스포트합니다.

COPY (SELECT name, position, back_num FROM players)
TO 'players.csv'
WITH (FORMAT CSV);

임포트 명령은 아래와 같습니다.

COPY players (name, position, back_num)
FROM '/data/players.csv' DELIMITER ',' CSV;

마찬가지로 같은 PostgreSQL끼리 데이터를 주고받을 때는 포맷을 BINARY 로 지정하면 성능 향상을 유도할 수 있습니다.

PostgreSQL에서는 DB 밖에 저장된 데이터를 DB 안의 객체처럼 접근하는 Foreign Data Wrapper라는 개념도 지원합니다. Oracle의 External Table이나 SQL Server의 OPENROWSET 방식과 유사합니다. 아래와 같이 CREATE EXTENSION, CREATE SERVER 구문으로 file_fdw 모듈을 설치합니다.

CREATE EXTENSION file_fdw;
CREATE SERVER baseball FOREIGN DATA WRAPPER file_fdw;

그리고 파일에서 읽어올 데이터의 스키마를 아래와 같이 정의합니다.

CREATE FOREIGN TABLE players (
  name VARCHAR(30),
  position VARCHAR(30),
  back_num INTEGER
) SERVER baseball
OPTIONS ( filename '/home/benelog/players.csv', format 'csv' );

format, header, delimiter 등의 옵션은 COPY 구문과 똑같이 사용할 수 있습니다.

pg_bulkload라는 전용 도구도 지원합니다. Oracle의 SQL*Loader처럼 ctl 파일에 작업의 명세를 정의할 수 있습니다. CSV 파일을 로딩하는 ctl 파일은 아래와 같이 정의합니다.

players_csv.ctl
OUTPUT = players
INPUT = /home/benelog/players.csv
TYPE = CSV
QUOTE = "\""
ESCAPE = \
DELIMITER = ","

명령행에서 다음과 같이 실행하면 CSV 파일의 내용이 테이블로 입력됩니다.

pg_bulkload players_csv.ctl

각각의 방식에 대한 자세한 사용법은 다음을 참조합니다.