메모리를 아끼던 코드가 대용량 서버에서 병목이 된 이유
최근 회사에서 대용량 서버를 배정받았습니다. 기존에 운영하던 예측 서버를 해당 서버로 이전하면서 동시에 늘어난 자원을 효율적으로 사용할 수 있도록 성능 최적화 프로젝트도 진행했습니다. 이 과정에서 한 태스크가 기존 평균 171분에서 최적화 후 평균 30초 정도로 약 350배의 엄청난 성능 향상을 보였는데요, 오늘은 이 태스크에 대해 이야기해보고자 합니다.
제가 근무하고 있는 회사는 스타트업이기 때문에 최대한 적은 자원으로 최고의 효율을 내는 것이 중요합니다. 기존에 사용하던 서버는 매우 작은 서버였기 때문에 저는 가지고 있는 메모리를 최대한으로 사용하되 메모리가 터지지 않도록 관리하는 것에 중점을 두고 있었습니다.
이번에 최적화한 태스크는 분석 실험 결과를 집계해서 저장하는 태스크였습니다. 처음 개발할 때는 서버의 메모리 한계를 확인한 뒤, 집계 계산할 때 독립적으로 계산할 수 있는 배치 단위로 데이터를 fetch해 온 후, 연산하고 insert하는 식으로 진행했습니다.
그래서 최적화 단계에서는 이 배치 단위를 서서히 키워나가는 식으로 진행했습니다. 그렇게 실험을 진행하다가 어느 순간 깨달았습니다. 새로 이전한 서버는 모든 데이터를 한번에 연산할 수 있을 만큼 충분히 큰 사이즈였고, 어쩌면 데이터를 나눠서 가져오고 나눠서 올리는 배치 과정 자체가 병목이었을 수도 있다는 생각이 들었습니다. 배치마다 반복되는 DB 커넥션 비용과 네트워크 I/O가 쌓이면서 오히려 전체 처리 시간을 늘리고 있었던 거죠.
그래서 과감하게 배치 과정을 제거했습니다. 모든 데이터를 한번에 가져와서 한번에 계산한 후 한번에 insert 했습니다. 그러자 결과는 앞서 말한 것 처럼 놀라웠습니다. 무려 약 350배의 성능 향상을 가져왔습니다.
하지만 저는 이 프로젝트의 가장 큰 성과는 이 ‘약 350배’라는 수치가 아니라고 생각합니다. 저는 이 프로젝트를 통해서 ‘상황에 따라 정답은 변한다’라는 사실을 배웠습니다. 개발 환경이 달라지면 그에 따라 아키텍처 설계와 접근법이 달라져야 합니다. 예를 들어 자원이 부족할 때는 메모리를 효율적으로 사용하는 것이 최우선이지만, 자원이 풍부할 때는 I/O를 최소화해서 처리 속도를 높이는 게 중요할 수도 있습니다.
이번 프로젝트는 시스템의 병목뿐만 아니라 제 안의 ‘고정관념’이라는 병목까지 함께 제거할 수 있었던 소중한 기회였습니다. 앞으로 여러 프로젝트를 진행하면서 이런 순간이 계속 찾아올 것이고, 그 때마다 조금씩 더 성장할 수 있을 것 같아서 기대됩니다.