MySQL 대용량 데이터 입력

고객이 요청한 사항들을 보면 기능은 별것 아니지만 다른 부분에서 문제가 발생되는 경우가 있습니다. 이번에도 메신저 사용자에게 전체 공지를 보내는 기능을 추가하는 부분에서 문제가 발생했습니다.
서버 사이드에서 쪽지 처리 방법은 사용자가 쪽지를 보내면 해당 쪽지를 받는 사용자 받은 쪽지함에 저장하고 받는 사용자에게 알림을 주는 형식입니다.
문제가 되는 사항은 받는 사용자 받은 쪽지함에 저장하는 부분입니다.
전체 공지를 한다면 사용자가 2,3천 정보 많게는 1만건 정도의 데이터를 입력해야 하는 경우가 발생합니다. 그렇게 되면 사용자 별로 반복문을 돌면서 하나씩 INSERT를 하기에는 너무 많은 소요 시간이 필요합니다.
테스트 환경
OS: Windows 7 64Bit
Memory: 4GByte
Java Version: 1.6.20 64Bit
MySQL : 5.1

 


INSERT INTO 방식 사용
코드가 정확하지 않을 수 있습니다. (예제로만 작성).

// 데이터베이스가 연결되어 있다는 가정.
List users = new ArrayList();
Message message = new Message(sender, '테스트', new Date());
String sql = "INSERT INTO rMessages (sender, receiver, content, time) values (?, ?, ?, ?) "
PreparedStatement psmt = conn.PrepareStatement(sql);
// 받는 사용자 수만큼 반복
for(String userID, users) {
    psmt.setString(message.getSender());
    psmt.setString(userID);
    psmt.setStrign(message.getContent());
    psmt.setTime(new java.sql.Time(new Date().getTime()));
    psmt.executeQuery(); 
}

위와 같이 실행하면 2천명의 사용자에게 전송했을때 소요시간이 1분정도 소요 됩니다.
1분이라면 빠른시간 같지만 프로그래머에겐 최악의 실행시간입니다.
이런 상황을 해결하기 위해 다른 방법을 찾던 중에 회사 과장님의 조언을 받아 ‘LOAD DATA INFILE’ 성능이 좋다는 조언을 들었습니다.

http://dev.mysql.com/doc/refman/5.1/en/load-data.html [참조]
MySQL INSERT 성능비교
http://kevin.vanzonneveld.net/techblog/article/improve_mysql_insert_performance/


LOAD DATA INFILE 사용

<br />// 임시 데이터 파일을 생성
// 파일명에 한글을 입력하게 되면 LOAD DATA에서 경로를 못찾을 수 있습니다.
String fileName= "D:/adminNotice.txt";
File dataFile = new File(fileName);
PrintWriter pw = null;
try {
    dataFile.createNewFile();
    FileOutputStream fos = new FileOutputStream(dataFile);
    // 주의: 인코딩을 데이터베이스와 동일하게 입력
    pw = new PrintWriter(new OutputStreamWriter(fos,"utf8"),true);  

} catch (IOException e) {
    e.printStackTrace();
}

// 파일 쓰기
for(String userID: users) {
    message.setReceiver(userID);

    // adminNotice.txt
    // admin, receiver1, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver2, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver3, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver4, 안녕하세요, 2011-10-10 07:36:00
    // toString() 메소드를 오버라이딩 해서 위의 출력형태로 변경
    // 마지막에 라인 구분자(\r\n)을 입력해야 합니다.
    // 파일에 데이터를 출력합니다.
    pw.write(message.toString); 
}


// MySQL 데이터 입력
String sql = "LOAD DATA INFILE 'D:/adminNotice.txt' INTO TABLE rMessages FIELDS TERMINATED BY ',' LINES TERMINATED BY '\\r\\n'";
PreparedStatement psmt = conn.PrepareStatement(sql);
psmt.executeQuery();

LOAD DATA INFILE 방식을 사용했을때 소요 시간은 1초 미만으로 소요 됩니다.
일반적인 INSERT INTO 방식보다 코드가 길긴 하지만 소요 시간이 대략 60배 이상 감소 하기 때문에 LOAD DATA 방식을 사용해서 처리를 했습니다.
몇건이 안되는 데이터베이스 입력 이라면 INSERT INTO 방식을 사용하는 편이 좋을 것 같습니다.
주의: LOAD DATA INFILE을 사용하려면 파일 사용 권한이 필요 합니다.
mysql> grant file on . to
coozplz@localhost identified by ‘coozplzPW’;

혹시 다른 방법으로 시도 하신분이 계시면 댓글로 부탁드립니다.

 

번외로 프로시저를 생성해서 처리하는 방법

[프로시저 생성 코드]

CREATE PROCEDURE test(IN receiver VARCHAR(255)) 
BEGIN 
DECLARE var INT;
SET var = 10000001;
WHILE(var < 10002000) DO
INSERT INTO rMessage (
    sender
    , receiver
    , content
    , time) 
        VALUES( 
        'admin'
        ,content
        ,'안녕하세요'
        , now())

END WHILE;
END  //
DELIMITER ; 

[Java code]

for(String userID: users) {
    // CALL test('receiver1');
    // CALL test('receiver2');
    // CALL test('receiver3');
    // CALL test('receiver4');    
    String sql="CALL test('"+userID+"')";
    PreparedStatement psmt = conn.PrepareStatement(sql);
    psmt.executeQuery();
}

프로시저를 사용할 경우 속도는 단순 INSERT INTO 방식보다 빠르게 나왔습니다. 2배정도 나옵니다.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s