1. 문제 상황

회사에서 신규 프로젝트 운영을 위해서 python으로 백오피스를 구현하고 있습니다.

리스트로 이루어진 데이터가 입력으로 오면, 이 데이터를 mongoDB에서 조회해서 가공한 후에 지도에 출력하는 프로젝트 입니다.

 

문제는 성능이 나오지 않았습니다.

42개의 데이터를 조회 및 처리하는 데에 5초나 걸렸습니다.

 

2. 원인

mongoDB가 해외에 위치해 있습니다. 그러다보니 query를 날리고 응답을 받는데에 시간이 오래 걸릴 것이라고 생각했습니다.

특히 이런 IO 작업을 싱글 쓰레드로 처리하다 보니 속도가 느려졌다고 생각했었습니다.

 

3.  해결

그래서 싱글 쓰레드로 동작하던 것을 멀티 쓰레드로 변경했습니다.

python의 GIL이 있지만, mongoDB에서 데이터를 가져오는 IO 작업 부분을 멀티 쓰레드로 작업하기 때문에 큰 상관은 없습니다.

 

약 max_worker를 20개로 설정해서 실험을 해봤습니다.

 

기존에 5초가 걸리던 작업이 약 2.3초까지 줄어든 것을 확인했습니다.

42개의 데이터를 가져오는 데 2.3초나 걸리는 것은 성능이 많이 느리다고 생각했습니다.

그래서 다른 해결 방안을 찾고 있었습니다.

 

 

이번에는 데이터의 개수가 약 4500개를 조회하도록 하고 max_worker를 100까지 증가시켰습니다.

무려 24초나 걸렸습니다.

 

이번엔 mongoDB에 index를 생성해서 조회를 해봤습니다.

24초 -> 16초까지 성능은 개선이 되었습니다. 무려 8초나 줄었기 때문에 여기서 만족하고 다른 일을 처리하러 갔습니다.

 

그러던 중, kafka의 partitioner가 생각이 났습니다.

kafka의 경우 produce를 할 때, partitioner의 전략(sticky, round robin)에 따라 데이터를 어느 partition에 데이터를 넣을 지 결정합니다. 그리고 이를 바로 kafka에 전송하는 것이 아니라, 설정한 batch 개수 만큼 쌓거나 일정 시간 지난 후에 한 번에 보내게 됩니다.

이렇게 한 이유가 네트워크 통신 비용을 줄이려고 그랬다는 내용이 갑자기 떠올랐습니다.

 

그래서 데이터 개수만큼의 쿼리를 전송하는 것이 아니라, 하나의 쿼리로 여러 데이터를 조회하도록 수정했습니다.

무려 16초에서 3.7초까지 성능이 개선 되었습니다.

특히 해외에 mongoDB가 있다보니 여러 쿼리를 날리고 응답을 받는 것이 성능에 큰 이슈가 있었던 것으로 파악이 됩니다.

 

결론

개선 방법 성능
멀티 쓰레드로 조회 24 s
mongoDB index 생성 16 s
싱글 쓰레드로 쿼리 하나로 여러 데이터 조회하도록 수정(Bulk) 3.7s

 

 

실제 다른 기능도 멀티 쓰레드로 구현이 되어 있었습니다. 데이터를 많이 조회하지도 않는데, 7초나  소모됩니다. 

이 부분을 맡은 팀원 분에게도 해당 내용 공유를 드렸습니다.

 

이 기능까지 성능이 개선이 된다면, 전체 프로세스가 기존 31초에서 약 4초까지 성능이 개선될 것으로 예상이 됩니다.

 

 

4. 느낀 점

당연히 batch로 데이터를 전송하는 것이 성능이 좋다는 것은 알고 있었습니다. 하지만 이 당시에 제 코드를 작성하고 있을 때는 여러 쿼리를 날리는 것이 문제가 될 것이라고 생각하지 못했습니다. 머리로는 알고 있었지만, 응용은 하지 못했었습니다.

 

머리로만 알던 지식을 완전히 제 것으로 만들기 가장 쉬운 방법은 프로젝트 경험이라는 것을 느낄 수 있었습니다.

(그래서 개발 동아리가 끝난 후에도, 팀원을 구해서 사이드 프로젝트를 계속 유지보수할 생각입니다.)

 

 

성능 개선하는 것이 보람차고 즐겁네요.

성능이 중요한 일을 해보고 싶네요.

 

반응형

'BackEnd > 이슈 정리' 카테고리의 다른 글

[Webflux] 대용량 데이터 처리 heap 사용량 이슈  (0) 2024.08.03

회사에서 일을 하다가 마주친 이슈입니다.

 

 

1. 문제 상황

데이터를 외부에서 수집해서 서비스에 맞게 파싱 후, 저희 db와 redis에 넣는 작업을 했었습니다.

데이터 자체는 크지 않았지만, 서비스에 맞게 사용하기 위해 가공한 후의 데이터가 많았습니다.

 

여기서 문제는 가공하고 mongoDB와 redis에 넣을 때 힙을 약 5기가 정도로 많이 사용하고 있었습니다.

 

그래서 왜 이렇게 많이 힙을 사용하는 지 알아봤습니다.

 

 

2. 원인 파악

flatMap을 이용해서 데이터를 처리하고 있었습니다.

 

void test() {
        
    List<String> data = new ArrayList<>();

    Flux.just(data)
            .flatMap(fetchData -> {
                //db에 저장
                return Mono.just(fetchData);
            })
            .flatMap(fetchData -> {
                //redis 에 저장
                return Mono.just(fetchData);
            });
        
}

(실제로 회사에서 이러한 코드를 사용하는 건 아닙니다.

 

실제 데이터를 가져오고, 이를 가공한 후에 넣는데 문제는 flatMap을 사용하다보니 이 과정이 비동기로 동작했습니다.

예를 들면, 저희 api1, api2, api3에 사용하기 위한 데이터를 가공을 하게 되는데 이 데이터를 전부 비동기로 db에 넣고 있었습니다.

그러다보니 힙을 예상보다 많이 사용하게 되었습니다.

 

또한 가공을 할 때, 모든 데이터를 한 번에 가공했었습니다.

데이터가 200만 개라고 가정하면, 200만 개를 전부 한 번에 가공하고 데이터를 db에 넣는 등 작업을 했었습니다.

가공을 하게 되면서 데이터의 사이즈가 늘어나게 되고 이 과정도 문제가 되었습니다.

 

 

3. 해결 방안

일단 모든 데이터를 한 번에 처리하던 것을 일정 개수로 나눠서 가공하고 db 등에 넣도록 수정했습니다.

하지만 이것만 했을 때는 큰 효과가 없었습니다.

왜냐하면 비동기로 동작하기 때문에 나누더라도 모든 데이터가 한번에 가공이 되기 때문입니다.

 

그래서 비동기로 동작하던 것을 동기로 변경을 시켜주고 싶었습니다.

그러기 위해서 flatMap의 concurrency를 제어해주었습니다.

 

flatMap(.... , {concurrency}) 를 통해 설정할 수 있습니다.

이를 1로하니 동기처럼 동작하는 것을 확인했습니다.

(로그 통해서)

 

 

실제로 힙이 5기가 사용하던 것이, 3기가로 극적으로 줄었습니다.

 

하지만 성능은 30초 걸리던 것이 40초로 늘어났습니다.

이러한 트레이드 오프를 찾는 것이 중요한 것 같습니다.

 

 

4. 느낀 점

기존에 스케줄러로 데이터를 가져와서 처리하고 있어서 그대로 썼었습니다.

spring batch가 대용량 데이터 처리에 좋다고 하는데, 이를 이용한다면 이러한 작업을 수월하게 할 수 있지 않을까 라는 생각이 들었습니다.

 

spring batch는 아직 한 번도 안 써봐서 이것도 공부를 해봐야 할 것 같습니다.

(그래서 현재 조그만한 회사 프로젝트를 코틀린 + batch로 작업하고 있습니다.)

반응형

'BackEnd > 이슈 정리' 카테고리의 다른 글

[DB] Bulk 조회를 통한 성능 개선(24초 -> 3초)  (0) 2024.09.05

+ Recent posts