DiffMate

블로그로 돌아가기

대용량 파일(100만 행+) 비교 시 주의사항

2025년 3월 25일

데이터 분석, 마이그레이션 검증, 로그 분석 등의 업무에서 100만 행이 넘는 대용량 파일을 비교해야 하는 상황은 점점 늘어나고 있습니다. ERP 시스템 전환, 데이터베이스 마이그레이션, 월말 재무 데이터 정합성 검증 등 실무에서 대용량 비교는 피할 수 없는 작업이 되었습니다. 하지만 일반적인 비교 방법으로는 이런 규모의 데이터를 처리하기 어렵습니다.

이 글에서는 대용량 파일 비교 시 발생하는 주요 문제와 효과적인 해결 방법을 구체적인 수치와 기술적 배경을 포함하여 깊이 있게 안내합니다.

메모리 부족 문제와 구체적인 계산

가장 흔한 문제는 메모리 부족입니다. 구체적으로 어느 정도의 메모리가 필요한지 계산해 보겠습니다.

100만 행 × 20열의 CSV 파일에서 각 셀의 평균 크기가 50바이트라고 가정하면, 원시 데이터만으로 약 1GB(1,000,000 × 20 × 50 = 1,000,000,000 바이트)가 됩니다. 하지만 실제로는 이보다 훨씬 많은 메모리가 필요합니다. JavaScript에서 문자열은 UTF-16으로 인코딩되므로 ASCII 텍스트도 2배의 공간을 차지합니다. 여기에 객체 헤더, 배열 인덱스, 참조 포인터 등의 오버헤드를 더하면 실제 메모리 사용량은 원시 데이터의 3~5배에 달할 수 있습니다.

결과적으로 100만 행 × 20열 CSV 파일 하나를 JavaScript 객체로 파싱하면 약 3~5GB의 메모리를 사용하게 됩니다. 두 파일을 동시에 비교하려면 6~10GB가 필요한 셈입니다.

### 브라우저별 메모리 한계

운영체제와 브라우저에 따라 탭 하나가 사용할 수 있는 메모리 한계가 다릅니다.

  • **Chrome (64비트)**: 탭당 약 4GB까지 사용 가능. V8 엔진의 힙 크기 제한은 기본적으로 약 4GB이며, `--max-old-space-size` 플래그로 조절할 수 있지만 일반 사용자가 설정하기는 어렵습니다.
  • **Firefox (64비트)**: 탭당 약 4~8GB까지 사용 가능하지만, GC(가비지 컬렉션) 압박으로 실제 성능은 2~3GB 수준에서 저하가 시작됩니다.
  • **Safari (macOS)**: 시스템 메모리에 따라 유동적이지만, WebKit 엔진 특성상 대용량 ArrayBuffer 할당 시 제한이 발생할 수 있습니다.
  • **모바일 브라우저**: 탭당 300MB~1GB 수준으로 매우 제한적이며, 대용량 파일 비교에는 적합하지 않습니다.

이러한 한계를 감안하면, 브라우저에서 100만 행 이상의 파일을 직접 비교하려면 메모리를 효율적으로 관리하는 전략이 필수적입니다.

Web Worker 아키텍처와 UI 프리징 방지

브라우저의 메인 스레드는 UI 렌더링, 사용자 입력 처리, JavaScript 실행을 모두 단일 스레드에서 처리합니다. 대용량 파일의 파싱이나 diff 연산을 메인 스레드에서 실행하면, 그 작업이 완료될 때까지 화면이 완전히 멈추는 "UI 프리징" 현상이 발생합니다. 100만 행 비교에 30초가 걸린다면, 사용자는 30초 동안 아무런 조작도 할 수 없게 됩니다.

Web Worker는 이 문제를 해결하는 브라우저 API입니다. Web Worker는 메인 스레드와 별도의 백그라운드 스레드에서 JavaScript를 실행합니다. 핵심 특징은 다음과 같습니다.

  • **독립된 실행 환경**: Worker에서 무거운 연산을 수행해도 메인 스레드의 UI는 정상적으로 반응합니다.
  • **메시지 기반 통신**: 메인 스레드와 Worker는 `postMessage()`와 `onmessage` 이벤트를 통해 데이터를 주고받습니다.
  • **Transferable Objects**: 대용량 ArrayBuffer를 Worker로 전달할 때 복사 없이 소유권만 이전하여 메모리를 절약할 수 있습니다. 이 방식을 사용하면 1GB 버퍼도 거의 즉시 전달됩니다.
  • **진행률 보고**: Worker에서 주기적으로 진행률 메시지를 보내면, 메인 스레드에서 프로그레스 바를 실시간으로 업데이트할 수 있습니다.

DiffMate는 파일 파싱과 diff 연산 모두를 Web Worker에서 수행하여, 대용량 파일 처리 중에도 UI가 항상 반응하도록 설계되어 있습니다.

청크 단위 파일 읽기 전략

수 GB에 달하는 파일 전체를 한 번에 메모리에 올리는 것은 비효율적입니다. 대신 파일을 작은 청크(chunk)로 나누어 순차적으로 읽는 전략이 효과적입니다.

JavaScript의 `File` 객체는 `Blob`을 상속하므로 `slice(start, end)` 메서드를 사용할 수 있습니다. 예를 들어 1GB 파일을 64MB씩 16개의 청크로 나누어 읽을 수 있습니다.

``` const CHUNK_SIZE = 64 * 1024 * 1024; // 64MB for (let offset = 0; offset < file.size; offset += CHUNK_SIZE) { const chunk = file.slice(offset, offset + CHUNK_SIZE); const text = await chunk.text(); // 청크 처리... } ```

이 방식을 사용할 때 주의할 점은 청크 경계에서 행이 잘릴 수 있다는 것입니다. 마지막으로 완전한 줄바꿈(\n) 위치까지만 처리하고, 잘린 나머지 부분은 다음 청크에 붙여서 처리해야 합니다. 이를 "경계 보정(boundary adjustment)"이라고 합니다.

청크 방식의 장점은 피크 메모리 사용량을 청크 크기 수준으로 제한할 수 있다는 것입니다. 1GB 파일을 64MB 청크로 읽으면, 파일 읽기 단계에서 메모리 사용량을 64MB 수준으로 유지할 수 있습니다.

해시 기반 비교 vs 전체 텍스트 비교

파일 비교의 핵심은 diff 알고리즘입니다. 두 가지 주요 접근 방식의 트레이드오프를 이해하는 것이 중요합니다.

### 전체 텍스트 비교

모든 행의 전체 내용을 문자 단위로 비교합니다. 가장 정확한 방식이지만, 문자열 비교는 문자열 길이에 비례하는 시간이 걸립니다. 행 하나의 평균 길이가 1,000자인 100만 행 파일 두 개를 비교하면, 최악의 경우 10^12(1조)번의 문자 비교가 필요합니다. 이는 현실적으로 불가능한 수준입니다.

### 해시 기반 비교

각 행의 해시값(예: FNV-1a, MurmurHash)을 먼저 계산하고, 해시값으로 동일 여부를 빠르게 판단합니다. 해시 비교는 O(1) 상수 시간에 수행되므로, 100만 행 × 100만 행의 비교에서도 해시 계산(O(n))과 매칭(O(n log n) 또는 O(n))으로 처리할 수 있습니다.

단점은 해시 충돌 가능성입니다. 서로 다른 내용이 같은 해시값을 가질 수 있습니다. 실무에서는 해시가 일치하는 행에 대해서만 전체 텍스트로 재검증하는 2단계 방식이 일반적입니다. 이렇게 하면 대부분의 행은 빠른 해시 비교로 처리하고, 소수의 충돌 후보만 정밀 비교하여 속도와 정확성을 모두 확보할 수 있습니다.

DiffMate의 diff 엔진은 Python의 `difflib.SequenceMatcher`를 JavaScript로 포팅한 것으로, 내부적으로 해시 기반의 빠른 매칭과 정밀한 시퀀스 비교를 조합하여 사용합니다.

가상 스크롤링: 대용량 비교 결과 렌더링

100만 행의 비교 결과를 DOM에 그대로 렌더링하면, 브라우저는 수백만 개의 DOM 노드를 생성해야 합니다. 이는 메모리 부족과 렌더링 성능 저하로 직결됩니다. 실험적으로, 10만 개 이상의 DOM 노드를 가진 페이지는 스크롤이 버벅이기 시작하며, 50만 개를 넘으면 대부분의 브라우저가 사실상 동작을 멈춥니다.

가상 스크롤링(Virtual Scrolling)은 이 문제를 해결하는 기법입니다. 화면에 보이는 영역(viewport)에 해당하는 행만 실제 DOM에 렌더링하고, 나머지는 빈 공간으로 대체합니다. 사용자가 스크롤하면 보이는 영역의 행을 동적으로 교체합니다.

예를 들어 화면에 50행이 보인다면, 위아래 여유분(overscan)을 포함하여 약 70~100개의 DOM 노드만 유지합니다. 100만 행이든 1000만 행이든 실제 DOM 노드 수는 항상 100개 수준으로 일정합니다. 이를 통해 메모리 사용량을 극적으로 줄이고, 스크롤 성능을 일정하게 유지할 수 있습니다.

DiffMate의 FullDocumentView 컴포넌트는 이 가상 스크롤링 기법을 사용하여 대용량 비교 결과도 부드럽게 스크롤할 수 있도록 구현되어 있습니다.

실제 벤치마크: 파일 크기별 처리 시간

브라우저 환경에서 CSV 파일 비교의 일반적인 처리 시간을 정리하면 다음과 같습니다. (기준: M1 MacBook Pro 16GB RAM, Chrome 최신 버전, 20열 CSV)

  • **10만 행 (약 100MB)**: 파싱 2~3초, diff 연산 5~8초, 총 약 10초
  • **50만 행 (약 500MB)**: 파싱 10~15초, diff 연산 30~60초, 총 약 1분
  • **100만 행 (약 1GB)**: 파싱 20~30초, diff 연산 90~180초, 총 약 2~3분
  • **200만 행 (약 2GB)**: 메모리 한계에 근접. 청크 처리 필수. 총 약 5~8분

이 수치는 변경된 행의 비율에 따라 크게 달라집니다. 변경이 10% 미만인 파일은 해시 매칭으로 빠르게 동일 행을 건너뛸 수 있어 더 빠르고, 변경이 50% 이상인 파일은 정밀 비교 대상이 많아져 상당히 느려집니다.

데스크톱 도구와의 비교

대용량 파일 비교에 자주 사용되는 데스크톱 도구들의 한계도 알아두면 유용합니다.

  • **Beyond Compare**: 텍스트 비교 기능이 우수하지만, 100만 행 이상의 파일에서는 로딩과 비교에 수 분이 걸립니다. 바이너리 비교 모드를 사용하면 빠르지만, 줄 단위 차이를 보기 어렵습니다. 유료 소프트웨어($60)이며 설치가 필요합니다.
  • **WinMerge (Windows)**: 오픈소스이지만 50만 행을 넘는 파일에서 메모리 부족으로 크래시가 빈번합니다. 대용량 파일 전용으로는 비추천합니다.
  • **Meld (Linux/macOS)**: GTK 기반 도구로 UI가 직관적이지만, 30만 행 이상에서 렌더링 성능이 급격히 저하됩니다.
  • **diff 명령어 (CLI)**: Unix/Linux의 `diff` 명령어는 메모리 효율성이 뛰어나 100만 행도 빠르게 처리하지만, 결과가 텍스트 기반이어서 시각적으로 파악하기 어렵습니다.

DiffMate는 브라우저 기반이므로 설치가 필요 없고, Web Worker를 활용하여 대용량 처리 성능을 확보하면서도 시각적으로 풍부한 비교 결과를 제공한다는 점이 차별점입니다.

파일 포맷별 주의사항

CSV 파일의 경우, 구분자(쉼표, 탭, 세미콜론 등)가 일관되는지 확인하세요. 대용량 CSV에서 중간에 구분자가 바뀌면 이후 모든 데이터가 잘못 파싱됩니다. 또한 셀 내용에 구분자나 줄바꿈이 포함된 경우 큰따옴표로 감싸져 있는지(RFC 4180 표준) 확인이 필요합니다.

엑셀 파일의 경우, .xlsx 형식은 내부적으로 XML 기반이므로 파싱에 시간이 걸립니다. 100만 행의 .xlsx 파일은 동일 데이터의 CSV보다 파싱에 3~5배 더 오래 걸립니다. 가능하다면 비교 전에 CSV로 변환하는 것이 속도 면에서 유리합니다. 참고로 Excel의 행 제한은 .xlsx 기준 1,048,576행(약 104만 행)이므로, 이를 초과하는 데이터는 CSV를 사용해야 합니다.

텍스트 파일의 경우, 줄바꿈 문자(LF vs CRLF)의 차이가 불필요한 변경으로 인식될 수 있습니다. 비교 전에 줄바꿈 형식을 통일하는 것을 권장합니다.

인코딩 문제

대용량 파일에서 인코딩 문제가 발생하면 영향 범위가 매우 큽니다. 파일 시작 부분의 BOM(Byte Order Mark)을 확인하고, 전체 파일의 인코딩이 일관되는지 미리 검증하세요.

특히 여러 소스에서 합친 파일의 경우, 중간에 인코딩이 바뀌는 경우가 있어 주의가 필요합니다. 예를 들어, 데이터베이스 A에서 UTF-8로 내보낸 데이터와 데이터베이스 B에서 EUC-KR로 내보낸 데이터를 단순 결합하면 파일 중간에서 인코딩이 깨집니다.

DiffMate는 BOM 감지 → UTF-8 → EUC-KR → ISO-8859-1 → UTF-16 순서로 인코딩을 자동 감지하지만, 혼합 인코딩 파일은 사전에 하나의 인코딩으로 통일해야 합니다. Linux/macOS에서는 `iconv` 명령어로, Windows에서는 메모장의 "다른 이름으로 저장"에서 인코딩을 지정하여 변환할 수 있습니다.

초대용량 파일 분할 전략

메모리 한계를 초과하는 파일(예: 500만 행 이상)은 분할하여 비교하는 것이 현실적입니다.

### 행 기준 분할

가장 단순한 방법은 행 수를 기준으로 분할하는 것입니다. Linux/macOS의 `split` 명령어를 사용하면 간단합니다.

``` # 50만 행씩 분할 (헤더 포함) head -1 data.csv > header.csv tail -n +2 data.csv | split -l 500000 - part_ for f in part_*; do cat header.csv "$f" > "${f}.csv"; done ```

분할 시 반드시 헤더 행을 각 부분 파일에 포함시켜야 합니다. 그렇지 않으면 비교 도구가 첫 번째 데이터 행을 헤더로 오인할 수 있습니다.

### 키 기준 분할

데이터에 고유 키(ID, 날짜 등)가 있다면, 키 범위별로 분할하는 것이 더 의미 있습니다. 예를 들어 고객 ID 1~100만, 100만~200만으로 나누면 각 부분 파일의 비교 결과를 독립적으로 검증할 수 있습니다.

### 변경 가능성 기반 분할

마이그레이션 검증처럼 대부분의 데이터가 동일할 것으로 예상되는 경우, 먼저 행별 해시값을 계산하여 해시가 다른 행만 추출한 후 비교하는 방법도 효과적입니다. 100만 행 중 변경된 행이 1%라면 1만 행만 정밀 비교하면 되므로 시간과 메모리를 대폭 절약할 수 있습니다.

데이터베이스 내보내기 모범 사례

대용량 파일 비교의 상당수는 데이터베이스에서 내보낸 데이터를 비교하는 것입니다. 내보내기 단계에서 몇 가지만 주의하면 비교 작업이 훨씬 수월해집니다.

  • **정렬 순서 통일**: 반드시 동일한 ORDER BY를 사용하여 내보내세요. 정렬이 다르면 내용이 같아도 모든 행이 "변경"으로 나타납니다.
  • **NULL 표현 통일**: 한쪽은 빈 문자열, 다른 쪽은 "NULL"로 내보내면 무의미한 차이가 대량 발생합니다. COALESCE 함수 등으로 통일하세요.
  • **날짜/시간 형식 통일**: `2025-03-25`와 `25/03/2025`는 내용은 같지만 문자열로는 다릅니다. ISO 8601 형식(YYYY-MM-DD)을 권장합니다.
  • **소수점 정밀도 통일**: ROUND 함수로 소수점 자릿수를 맞추세요. 한쪽은 3.14, 다른 쪽은 3.1400000001이면 차이로 인식됩니다.
  • **CSV 내보내기 옵션**: 큰따옴표 이스케이프, 구분자 문자를 양쪽에서 동일하게 설정하세요.

비교 전 데이터 전처리

대용량 파일 비교의 정확도를 높이려면 전처리가 중요합니다.

  • 앞뒤 공백 제거 (TRIM)
  • 대소문자 통일 (필요한 경우)
  • 날짜 형식 통일 (YYYY-MM-DD)
  • 숫자 형식 통일 (소수점 자릿수, 천 단위 구분자)
  • 빈 행/열 제거
  • 연속 공백을 단일 공백으로 치환
  • 셀 내 줄바꿈 제거 또는 통일

대용량 파일의 전처리는 Excel보다 스크립트(Python pandas, awk 등)를 사용하는 것이 빠르고 안정적입니다.

진행 상태 표시와 취소 전략

대용량 파일 비교는 수 분이 걸릴 수 있으므로, 사용자에게 현재 진행 상태를 명확히 보여주는 것이 중요합니다.

효과적인 진행 상태 표시의 핵심 요소는 다음과 같습니다.

  • **단계별 진행률**: 파일 읽기(30%) → 파싱(20%) → diff 연산(40%) → 결과 렌더링(10%) 등 각 단계의 비중을 반영한 통합 진행률 표시
  • **예상 남은 시간**: 현재까지의 처리 속도를 기반으로 예상 완료 시간을 계산하여 표시
  • **처리된 행 수**: "450,000 / 1,000,000 행 처리됨"과 같은 구체적인 수치 표시
  • **취소 기능**: Web Worker는 `worker.terminate()`로 즉시 중단할 수 있습니다. 사용자가 설정을 잘못했거나 너무 큰 파일을 선택한 경우 취소하여 브라우저 메모리를 해제할 수 있습니다.

취소 후 재시도 시에는 가비지 컬렉션이 완료될 때까지 약 1~2초의 딜레이를 두는 것이 안정적입니다.

결과 확인 시 팁

100만 행의 비교 결과를 처음부터 끝까지 눈으로 확인하는 것은 불가능합니다. 효과적인 확인 방법은 다음과 같습니다.

  • 변경된 행만 필터링하여 확인
  • 통계 요약 먼저 확인 (추가/삭제/변경 각각 몇 행인지)
  • 미니맵을 활용하여 변경이 집중된 영역 파악
  • 샘플링 검증 (무작위로 일부를 선택하여 정확성 확인)
  • 변경 유형별 분류: 데이터 변경 vs 포맷 변경을 구분하여 실질적인 차이만 집중 확인
  • 비교 결과를 CSV로 내보내어 스프레드시트에서 피벗 테이블이나 필터로 분석

결론

대용량 파일 비교는 적절한 도구와 전처리만 있으면 충분히 실현 가능합니다. 핵심은 메모리 관리(청크 읽기, 해시 기반 비교), UI 성능(Web Worker, 가상 스크롤링), 데이터 품질(인코딩 통일, 포맷 통일)의 세 가지입니다.

DiffMate는 Web Worker 엔진을 사용하여 100만 행 이상의 파일도 브라우저에서 안정적으로 비교할 수 있습니다. 서버 업로드 없이 로컬에서 처리되므로 보안도 안전합니다. 데이터베이스 내보내기 결과를 정렬과 포맷을 맞춰 준비하고, 필요하다면 파일을 분할하여 비교하면 수백만 행의 데이터도 체계적으로 검증할 수 있습니다.

DiffMate로 대용량 파일 비교하기