위성 영상 삭제 성능 개선
영상을 비동기로 삭제하고, 테이블에 인덱스를 구성하여 약 60배(1m->1s) 성능 개선
개요
2024년 7월 10일에 회사에서 삭제 API의 성능을 개선한 작업에 대해 기록하고 회고한다.
배경 및 목적
테스트 엔지니어가 위성 영상을 삭제하는 테스트를 진행하는 중 에러가 발생해 버그를 리포트했다. 버그를 확인하기 위해 직접 버그를 재현해보니 504(Gateway Timeout) 에러가 발생하는 것을 확인했다. 어디에서 병목이 발생하여 504 에러가 발생하는지 확인하고 1분 미만으로 성능을 개선하는 것을 목표로 작업을 진행했다.
개선 전 성능 측정
개선하기 전에 성능을 먼저 측정한 결과, 테스트 서버에서 영상 50개를 삭제할 때 타임아웃이 발생하므로 성능 목표를 약 1분으로 설정했다.
1차 원인 분석
타임아웃이 발생하는 API의 코드를 확인해 로직을 분석했다. 해당 로직은 다음과 같다.
- 영상 식별자 목록을 수신
- 영상 목록 조회
- DB에서 삭제할 영상과 관련된 데이터들과 영상 데이터 제거
- 영상 목록을 순회하며 영상 파일 제거
- 영상 목록을 순회하며 삭제 이벤트 발행
예상되는 원인은 영상 목록을 순회하며 영상 파일을 제거하는 코드를 동기적으로 수행하는 것이었다. 영상의 크기가 10MB에서 30GB까지 다양하기 때문에, 삭제하려는 파일들이 모두 몇 GB일 경우 타임아웃이 발생할 수 있기 때문이다.
1차 성능 개선
기존 코드에서 코루틴을 활용해 비동기로 영상 목록을 삭제하도록 수정했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
private fun deleteSceneFiles(scenes: List<Scene>) {
CoroutineScope(Dispatchers.IO).launch {
runCatching {
scenes.forEach {
File(it.meta.path).delete()
File("/scenes/link/${it.name}").delete()
}
}.onFailure {
val sceneNames = scenes.map { it.name }
logger.error(it) { "영상 삭제를 실패했습니다 [ scenes : $sceneNames ]" }
}
}
}
이후 빌드하여 테스트 서버에 배포하고 정상 동작 여부와 소요 시간을 확인했다.
1차 성능 측정
이전에 사용한 영상 50개를 다시 넣은 후 삭제 API를 호출해본 결과, 정상적으로 영상이 삭제되었으며 17초가 소요되어 약 3.5배 성능이 향상된 것을 확인했다. 이제 타임아웃 에러는 발생하지 않지만, 17초도 사용자에게는 느리게 느껴질 수 있어 추가적인 개선 작업을 진행했다.
2차 원인 분석
병목이 발생하는 부분을 확인하기 위해 각 로직마다 소요 시간을 측정하는 로깅 코드를 추가하고 배포 후 테스트를 수행했다. 로그를 확인해보니 영상 데이터를 삭제하는 DELETE 쿼리에서 15초가 소요되는 것을 확인했다.
이후 임의로 영상 데이터를 하나 넣고 삭제해본 결과 3초가 소요되었다. DELETE 쿼리의 병목 구간을 상세히 분석하기 위해 다음 쿼리로 실행 계획을 확인했다.
1
2
EXPLAIN (ANALYZE, BUFFERS, TIMING)
DELETE FROM scene where name = 'wv3_20241110000000_1';
결과를 확인해보니 3개의 제약 조건에서 각각 약 1초씩 걸렸다. 이는 영상 식별자에 대한 FK 제약 조건 때문이었으며, 삭제하려는 영상 식별자가 테이블에 존재하는지 확인하는 데 시간이 걸리고 있었다.
2차 성능 개선
병목이 발생하는 제약 조건이 걸린 테이블에 영상 식별자로 인덱스를 추가하고, 삭제 쿼리의 소요 시간을 확인하니 3ms로 줄어든 것을 확인했다. 즉, 삭제 쿼리를 3초에서 3ms로 750배 성능을 향상시켰다. 이후 코드를 빌드하여 테스트 서버에 배포하고 성능을 다시 측정했다.
2차 성능 측정
1차 성능 측정과 동일하게 데이터를 구성한 후 API를 호출해보았다. 결과를 확인해보니 데이터가 정상적으로 삭제되었고 880ms가 소요되었다. 즉, 삭제 API 성능이 1분에서 880ms로 약 60배 개선되었다.
정리 및 회고
영상 목록을 삭제할 때 1분 이상 소요되었으나, 비동기 처리와 인덱스 추가를 통해 소요 시간을 1초 이하로 줄여 60배 이상의 성능 개선을 할 수 있었다. 이번 버그 수정 작업을 통해 다음 두 가지를 깨달았다. API를 구현할 때 동기 처리와 비동기 처리를 명확히 구분해 구현하는 것이 중요하다는 것과, 데이터를 삭제할 때 제약 조건에 따라 쿼리가 느려질 수 있다는 점이다. 앞으로 이러한 내용을 주의해 API를 구현하도록 하자.