Skip to content

Commit 9d5be9c

Browse files
add post '[MySQL] LOAD DATA로 대용량 데이터 빠르게 삽입하기'
1 parent 34fe9ed commit 9d5be9c

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
layout: "post"
3+
title: "[MySQL] LOAD DATA로 대용량 데이터 빠르게 삽입하기"
4+
description:
5+
"MySQL의 `LOAD DATA` 명령어를 활용해 대용량 데이터를 빠르게 삽입하는 방법을 소개합니다.
6+
\ 이 명령어는 텍스트 파일에서 데이터를 읽어와 테이블에 신속하게 삽입하며, 일반적인 `INSERT`보다 약 20배 빠른 성능을 제공합니다.
7+
\ Container를 사용해 MySQL 환경을 설정하고, CSV 파일을 통해 데이터를 삽입하는 과정과 함께,
8+
\ `innodb_buffer_pool_size` 값을 적절히 조정하여 성능을 최적화하는 방법도 설명합니다.
9+
\ 이를 통해 대량의 데이터를 효율적으로 처리할 수 있음을 확인했습니다."
10+
categories:
11+
- "스터디-데이터베이스"
12+
- "개발"
13+
tags:
14+
- "MySQL"
15+
- "LOAD DATA"
16+
- "대용량"
17+
- "INSERT"
18+
- "BULK INSERT"
19+
- "BULK"
20+
- "Podman"
21+
- "Docker"
22+
- "Container"
23+
- "innodb_buffer_pool_size"
24+
date: "2025-07-16 14:00:00 +0000"
25+
toc: true
26+
image:
27+
path: "/assets/thumbnails/2025-07-16-mysql-load-data.jpg"
28+
---
29+
30+
# `LOAD DATA` 로 대용량 데이터 빠르게 삽입하기
31+
32+
작년 말에 K-DEVCON 스터디에서 MySQL을 공부하면서 `LOAD DATA` 명령어에 대해서 알게 되었다.
33+
34+
- [INSERT, UPDATE, DELETE 쿼리 작성 및 최적화 - Real MySQL 스터디 7회차](https://jonghoonpark.com/2024/12/21/mysql-insert-update-delete-optimize)
35+
36+
최근 대용량 테스트 데이터를 적재해야 하는 상황이 생기면서, `LOAD DATA` 를 실제로 사용해볼 기회가 생겼다.
37+
38+
## LOAD DATA 란?
39+
40+
[LOAD DATA](https://dev.mysql.com/doc/refman/8.4/en/load-data.html) 명령어는 텍스트 파일로부터 데이터를 읽어와 테이블에 매우 빠르게 삽입할 수 있다. Real MySQL에서는 그냥 insert 하는 것과 비교하면 약 20배의 성능차를 보여준다고 설명이 나와있다.
41+
42+
`LOAD DATA` 는 빠르지만, **단일 스레드** 로 동작한다는 점에 유의하여 사용한다. Real MySQL에서는 여러개의 파일로 분할하여 병렬로 진행하라는 팁을 제공해주었다.
43+
44+
## LOAD DATA 사용해보기
45+
46+
### MySQL 세팅
47+
48+
**Docker Desktop** 을 사용하지 못하는 환경이라, **Podman Desktop** 을 사용하였다. [**Podman**](https://podman.io/) 은 이번에 처음 사용해 보았는데 Docker 와 호환되는(Compatible) 한 인터페이스를 제공하여, Docker 경험이 있다면 큰 어려움 없이 사용할 수 있었다.
49+
50+
실제 운영 환경과 동일하게 맞추기 위해 `MySQL 8.0.32` 버전으로 테스트를 진행하였다.
51+
52+
```sh
53+
podman run -dit -e MYSQL_ROOT_PASSWORD=testtesttesttest -e MYSQL_DATABASE=test -p 3306:3306 --name local-mysql mysql:8.0.32
54+
```
55+
56+
### 데이터 세팅
57+
58+
간단한 자바코드를 작성하여 파일로 csv 파일을 생성하도록 하였다. 컬럼 헤더는 csv에 담지 않았다. [faker](https://github.com/DiUS/java-faker) 를 이용하여 어느 정도 랜덤한 있는 데이터가 나올 수 있도록 하였다. 테스트 데이터도 최대한 실제와 유사하기 위해 암호화도 적용하여 데이터를 생성하게 하였다.
59+
60+
```
61+
USER0000001,7426C09FB3...,Rob,Gerlach,47a7e9bd9...,251FE112...,10,\N,10,\N,\N,N,0,40,...
62+
...
63+
```
64+
65+
csv 특성 상 `null` 처리가 까다로운데, `LOAD DATA``\N``null`로 인식한다.
66+
67+
그냥 빈 공백으로 처리할 경우 삽입 처리중에 아래와 같은 에러가 발생될 수 있으니 주의하자.
68+
69+
```
70+
[22001][1292] Data truncation: Incorrect ... value: '' for column 'column_name' at row xxx
71+
```
72+
73+
### Data 파일을 container 내부로 복사하기
74+
75+
다음과 같이 cp 명령어를 사용하여 데이터 파일을 container 내부로 복사할 수 있다.
76+
77+
```sh
78+
podman cp /Users/jonghoonpark/project/slow-query-select-member-list/output.csv local-mysql:/var/lib/mysql-files/file.csv
79+
```
80+
81+
### LOAD DATA 를 이용하여 데이터 삽입
82+
83+
파일을 컨테이너 내부로 옮겼다면, 아래 명령어를 통해 데이터를 삽입할 수 있다. `USER_TABLE` 이라는 이름의 테이블에 데이터를 삽입한다.
84+
85+
```SQL
86+
LOAD DATA INFILE '/var/lib/mysql-files/file.csv'
87+
INTO TABLE USER_TABLE
88+
FIELDS TERMINATED BY ',' -- csv 파일의 구분자 (쉼표인 경우)
89+
ENCLOSED BY '"' -- 필드가 따옴표로 묶여 있는 경우
90+
LINES TERMINATED BY '\n' -- 줄 바꿈 문자 (Unix/Linux 기준)
91+
-- IGNORE 1 LINES; -- 헤더 있는 경우
92+
```
93+
94+
### innodb_buffer_pool_size
95+
96+
LOAD DATA를 사용하면 기본적으로 빠르게 데이터를 삽입할 수 있지만, MySQL 의 [innodb_buffer_pool_size](https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size) 값을 적절히 설정해준다면 더 빠른 처리를 이끌어 낼 수 있다.
97+
98+
`innodb_buffer_pool_size` 는 InnoDB에서 테이블과 인덱스 데이터를 캐시하는 메모리 영역의 크기에 대한 변수이다. 일반적으로 메모리의 50-75% 정도로 설정해 주면 좋다고 한다. 기본 값은 `128MB` 이다.
99+
100+
아래 명령어를 통해 설정 값을 조회/설정 할 수 있다.
101+
102+
```SQL
103+
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
104+
```
105+
106+
```SQL
107+
SET GLOBAL innodb_buffer_pool_size = 134217728; -- 128mb
108+
```
109+
110+
`SET GLOBAL`을 통한 설정은 서버가 재시작 되기 전까지만 유지된다.
111+
112+
만약 영구적으로 설정을 하고 싶다면 설정 파일을 통해 설정할 수 있다.
113+
114+
```INI
115+
[mysqld]
116+
innodb_buffer_pool_size=134217728
117+
```
118+
119+
`innodb_buffer_pool_size` 를 설정할 때 유의할 점 한가지는, `innodb_buffer_pool_chunk_size` 의 배수로 설정해야 한다는 점이다.
120+
만약 배수가 아닌 값으로 설정할 경우, MySQL 에서 자동으로 배수로 조정한다.
121+
122+
`innodb_buffer_pool_chunk_size` 도 기본 값은 `128MB` 이다.
123+
124+
### 실행 결과
125+
126+
실제로 실행한 결과는 다음과 같았다.
127+
128+
실행 환경은 다음과 같다.
129+
130+
- 데이터는 **400만개** 로 고정
131+
- CPU는 **vCPU 2** 로 고정
132+
- 실행을 마친 후에는 table 을 truncate 한 후, container를 재실행
133+
134+
| 메모리 할당 | innodb_buffer_pool_size | 소요시간 | 비고 |
135+
| ----------- | ----------------------- | -------- | ------ |
136+
| 16GB | 128MB | 8m 20s | 기본값 |
137+
| 16GB | 8GB (50%) | 4m 31s | |
138+
| 16GB | 12GB (75%) | 4m 10s | |
139+
140+
| 메모리 할당 | innodb_buffer_pool_size | 소요시간 | 비고 |
141+
| ----------- | ----------------------- | -------- | ------ |
142+
| 8GB | 128MB | 7m 58s | 기본값 |
143+
| 8GB | 4GB (50%) | 3m 42s | |
144+
| 8GB | 6GB (75%) | 4m 1s | |
145+
146+
| 메모리 할당 | innodb_buffer_pool_size | 소요시간 | 비고 |
147+
| ----------- | ----------------------- | -------- | ---- |
148+
| 4GB | 2GB (50%) | 3m 16s | |
149+
| 4GB | 3GB (75%) | - | 실패 |
150+
151+
| 메모리 할당 | innodb_buffer_pool_size | 소요시간 | 비고 |
152+
| ----------- | ----------------------- | -------- | ------ |
153+
| 2GB | 128MB | 7m 23s | 기본값 |
154+
| 2GB | 256MB (1/8) | 4m 23s | |
155+
| 2GB | 512MB (1/4) | 3m 57s | |
156+
| 2GB | 1GB (50%) | 4m 9s | |
157+
| 2GB | 1.5GB (75%) | - | 실패 |
158+
159+
일부 설정에서는 할당된 메모리에 비해 버퍼풀 사이즈가 너무 과도하게 잡혔는지 컨테이너가 계속 종료되어 결과를 측정할 수 없었다.
160+
161+
기본적으로 `LOAD DATA``INSERT` 보다 훨씬 빠르게 동작하지만, 적절한 `innodb_buffer_pool_size` 값을 설정해주었을 때 더 빠르게 수행된 것을 확인할 수 있다.
162+
163+
## 마무리
164+
165+
스터디를 하며 배웠던 `LOAD DATA` 를 실제로 사용해보고, 설정에 따른 소요시간을 확인해보았다. MySQL에서 대용량 데이터를 빠르게 삽입할 때 매우 효과적인 방법임을 확인할 수 있었다.
166+
167+
`LOAD DATA` 는 그 자체로도 빠르지만, 서버 환경에 맞는 `innodb_buffer_pool_size` 설정을 통해 성능을 더욱 끌어올릴 수 있었다.

_sass/_main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ section.post .toc a {
366366

367367
section.post .table-wrapper {
368368
overflow-x: auto;
369+
padding-bottom: 1rem;
369370
}
370371

371372
figcaption {
1.05 MB
Loading

0 commit comments

Comments
 (0)