Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

KeepHunDev

텍스트 파일 조인의 성능 병목 분석 및 개선기 본문

회고록

텍스트 파일 조인의 성능 병목 분석 및 개선기

keephun 2025. 9. 12. 15:27

데이터베이스 과제를 수행하며 간단한 파일 조인 로직을 구현할 기회가 있었습니다

제공된 JOIN 코드는 데이터가 증가함에 따라 심각한 성능 저하를 유발합니다. 이 과정에서 문제의 원인을 분석하고 조인 최적화, JOIN 방법을 정리하려고 합니다

 

문제의 코드

...
BufferedReader br1 = new BufferedReader(new FileReader(rentedFileName));
String line1 = null;

// rented data
while ( (line1 = br1.readLine()) != null) {
    String [] toks1 = line1.split(",");
    String personName1 = toks1[0];

    // customer data
    BufferedReader br2 = new BufferedReader(new FileReader(customerFileName));
    String line2 = null;
    while ( (line2 = br2.readLine()) != null) {
        String [] toks2 = line2.split(",");
        String personName2 = toks2[0];
        String addr2 = toks2[1];
        if(personName1.equals(personName2)) {
            if(addr2.contains(addrHint)) {
                result.add(line1+":"+line2);
            }
        }
    }
    br2.close();
}
br1.close();
...

과제 요구사항은 대여 기록(rented.txt)고객 정보(customer.txt) 파일을 읽어, 특정 조건을 만족하는 데이터를 조인해야 합니다. 제공된 코드는 위와 같습니다. 해당 파일을 실행했습니다

 

실행 결과

 

데이터 건수 기존 조인 (예상 시간)

2,000건 651 ms
20,000건 55,274 ms
200,000건 ~약 91분
2,000,000건 ~약 6.3일
  • 200,000건 데이터부터는 오랜 시간이 걸려 시간 복잡도로 계산하였습니다.

 

성능 병목 분석

위 코드는 데이터베이스의 Nested Loop Join 과 동일한 원리로 동작하며, 두 가지 주요한 성능 병목을 가지고 있었습니다

  1. 알고리즘 시간 복잡도: 대여 기록(rented.txt)의 데이터 수를 N, 고객 정보(customer.txt)의 데이터 수를 M이라 할 때, 외부 루프(대여 기록)가 한 번 실행될 때마다 내부 루프(고객 정보)는 M번 전체를 순회합니다. 따라서 전체 시간 복잡도는 O(NM) 이 되어 데이터가 증가할수록 수행 시간은 기하급수적으로 증가합니다
  2. 과도한 디스크 I/O: 외부 루프가 반복될 때마다 FileReader를 호출하여 디스크에서 파일을 반복적으로 읽어온다는 점입니다. 디스크 접근이 N번 만큼 발생하여 전체 성능에 영향을 줍니다

해결책: Hash Join 원리의 적용

문제 해결을 위해 데이터베이스의 Hash Join 원리를 적용했습니다

먼저 고객 정보를 한 번만 읽어 HashMap에 저장하고, 대여 기록을 읽으면서, 매번 디스크를 찾는 대신 메모리에 있는 HashMap에서 O(1)의 시간 복잡도로 원하는 데이터를 조회하도록 로직을 변경했습니다

개선된 코드 (Hash Join)

// LineDTO는 테이블 DTO

...
BufferedReader br1 = new BufferedReader(new FileReader(rentedFileName));

String line1 = null;
HashMap<String, LineDTO> nameAndVal = new HashMap<String, LineDTO>();

// br2 cache
BufferedReader br2 = new BufferedReader(new FileReader(customerFileName));
String line2 = null;
while ( (line2 = br2.readLine()) != null) {
    String [] toks2 = line2.split(",");
    nameAndVal.put(toks2[0], new LineDTO(toks2[1], line2));
}
br2.close();

// rented data
while ( (line1 = br1.readLine()) != null) {
    String [] toks1 = line1.split(",");
    String personName1 = toks1[0];

    String addr2 = nameAndVal.get(personName1).addr;
    if(addr2.contains(addrHint)) {
        result.add(line1+":"+nameAndVal.get(personName1).line);
    }

}
br1.close();
...

 

실행 결과

  • 시간 복잡도 개선: O(N+M) → 해시 생성 O(M) + 루프 및 탐색: O(N)

결론

데이터 건수 기존 조인 개선된 조인 성능 개선 효과

2,000건 651 ms 13 ms 약 50배
20,000건 55,274 ms 28 ms 약 1,974배
200,000건 약 91분 179 ms 약 30,502배
2,000,000건 약 6.3일 1621 ms 약 335,792배

결론적으로, 개선된 조인 방식은 반복적인 디스크 접근을 제거하고 HashMap을 사용하여 알고리즘의 시간 복잡도를 O(N*M)에서 O(N+M)으로 줄였습니다

 

마치며

데이터베이스 교과목 과제인 <텍스트 파일 데이터 조인하기>를 수행하며 실제 성능에 관한 수치들을 관찰하였습니다. 이전에 WAS와 DB 사이에서 N:M 테이블 JOIN 전략을 고민해 보며 서비스 최적화를 해본 경험을 바탕으로 해당 과제를 수행하는 동안 최적화 방법의 중요성을 깨달았습니다.

 

전체적인 아키텍처는 구성되어 있는 상황에서 어떤 방식으로 알고리즘을 만들고, 캐시를 하냐에 따라 효율성이 달라진다는 게 흥미롭다고 생각합니다.

 

앞으로 무에서 유를 만들 수 있는 서버 개발자를 넘어 아키텍처 엔지니어가 되는 날까지 열심히 하겠습니다

감사합니다