Published on

10월 2주 있었던 일 정리

Authors
  • avatar
    Name
    이건창
    Twitter

Introduction

이번 주는 이슈 대응 할 일이 많았어. 다음 주에 QA라 조금 더 신중하게 작업하고 있지.

성능 개선에 실패했어...

상황 파악

지난 주에 테이블 풀 스캔 횟수를 줄이기 위해 변경한 코드로 인해 작업이 더 느려지는 걸 발견했어. (변경 과정은 10월 1주 회고에서 확인 할 수 있어.) 원인은 다음과 같아.

순차 처리로 인한 리소스 효율 감소 문제

스프링 배치에서 제공하는 페이징 활용해 데이터를 순차적으로 처리하는 과정에서 CPU 효율이 떨어졌어. 즉, 데이터베이스로 인해 스레드 들이 Blocking 되는 상황이었지.

이런 문제를 해결하기 위해 작업을 병렬로 처리할 수 있도록 구성하면 임시방편이 될 수 있겠지만, 다른 원인들도 천천히 파악하기로 했어.

유동적으로 변하는 쿼리 실행 계획 변경으로 인한 실행 시간 증가 문제

접근 방법 변경 하는 과정에서 평균 사용자를 기준으로 실행계획으로 성능을 예측했어. 그래서 데이터가 많은 사용자는 어떻게 동작할지는 상상하지 못했어.

문제가 발생한 지점은 3,000,000 건의 데이터를 1,000 건씩 조회하면서 발생했어. 말 그대로 연산과 정렬이 3,000 번 일어나게 되서 실행시간이 비약적으로 늘어났지,,,

실제 주범은 쿼리 문제

제일 큰 원인은 쿼리 실행 시간이 늘어난 문제였어. 심지어 병목 지점이 되어 Connection Timeout 문제도 발생했지.

결국 풀 테이블 스캔이 일어나니 풀 테이블 스캔 횟수를 줄이자!에 집중한 게 화근이었어. 그로 인해 실행 계획에서는 Using Where가 남발하는 쿼리가 만들어졌지.

해결

가져와야 하는 데이터를 분석했을 때, 활용할 수 있는 인덱스를 참고할 수 있었어.

변경된 방법

기존 로직은 최소 2초가 걸렸지만 변경한 이후 최소 0.2초로 빨리 수행 됐지. 결과적으로 대량 데이터를 가진 사용자로 실행 했을 때, 30 분에서 7 분으로 개선 할 수 있었어.

쿼리가 인덱싱을 활용할 수 있게 데이터를 조회하는 부분을 직접 커스터마이징 했어. 사용자 별로 작업이 평균 4 분 이상 걸려서 진행 상황을 파악 할 수 없는 것도 아쉬웠어. 이번 업데이트를 계기로 아래처럼 진행 상황율을 기록 할 수 있도록 수정할 시간도 가졌어.

... 생략
2023-10-11T16:31:33.957+09:00  INFO 1 --- [nPool-worker-30] [percent : 23%]
2023-10-11T16:31:33.709+09:00  INFO 1 --- [nPool-worker-32] [percent : 21%]
2023-10-11T16:31:33.655+09:00  INFO 1 --- [nPool-worker-34] [percent : 19%]
2023-10-11T16:31:33.469+09:00  INFO 1 --- [nPool-worker-25] [percent : 17%]
2023-10-11T16:31:33.452+09:00  INFO 1 --- [or-http-epoll-9] [percent : 14%]
2023-10-11T16:31:33.326+09:00  INFO 1 --- [nPool-worker-29] [percent : 12%]
2023-10-11T16:31:33.133+09:00  INFO 1 --- [nPool-worker-33] [percent : 10%]
2023-10-11T16:31:33.133+09:00  INFO 1 --- [nPool-worker-35] [percent : 8%]
2023-10-11T16:31:33.037+09:00  INFO 1 --- [nPool-worker-22] [percent : 6%]
2023-10-11T16:31:32.296+09:00  INFO 1 --- [nPool-worker-24] [percent : 4%]
2023-10-11T16:31:31.502+09:00  INFO 1 --- [nPool-worker-27] [percent : 2%]

정리하자면

결과적으로 대량 조회에서 변경된 실행 계획은 DB 작업의 레이턴시를 만들었고, 그로 인해 서비스가 대기하면서 CPU 효율이 떨어지는 대참사가 발생했지. 동작 예측이 어려운 문제도 있었어. 어떤건 빠르고 어떤건 느리고,,, 모니터링 하는 입장에서는 매일 긴장될거야.

이번 이슈를 해결하면서 깨달은건 다음과 같아.

  • 데이터베이스 작업 시간은 서비스 블로킹 시간이라는 점을 고려하자.
  • 최악의 시간 복잡도를 고려하자.

앞으로 신경써서 성능 튜닝을 계획 하자고!

테스트 책을 꽤나 읽었지

이번 주는 소프트웨어 테스팅 책을 2장부터 4장까지 읽었어. 책을 읽으면서 앞으로 어떤 마음가짐으로 프로덕트를 관리 할 지 고민했어.

앞으로의 프로덕트가 좋은 디자인이 유지되기 위해서는 꾸준한 리팩토링이 필요하겠지.

아키텍처

리팩토링을 효율적으로 수행하기 위해서는 자동화된 검증 절차가 필수적이었어. 자동화된 검증 절차를 효율적으로 작성하고 관리하기 위해서는 명세 기반 테스팅과 구조적 기반 테스팅이 제격이였어.

각각의 테스트 기법은 따로 정리했으니 간단하게만 소개할게.

명세 기반 테스트

명세 기반 테스트는 명세한 내용을 기반으로 테스트 대상을 찾게 돼. 명세 기반 테스트를 활용하면 체계적으로 광범위한 검증을 가능하게 해.

명세 기반 테스트는 명세에 필요한 자료구조를 선택하고 자료구조에 대한 입출력 값 분석 후 테스트를 작성하는 절차를 걸쳐.

그림 2

구조적 기반 테스트

구조적 기반 테스트는 코드 구조를 통해 테스트 대상이 되지 못한 구역을 찾게 돼. 구조적 테스트를 하는 이유는 문서에 명시되지 않은 언어 구조의 특성과 알고리즘에 대응하기 위해서야. 구조적 테스트는 코드 커버리지를 활용하면 효율적으로 테스트 미대상 구역을 판단할 수 있어.

구조적 테스트를 적용할 때 주의할 점은 명세 기반 테스트를 진행한 후 적용을 해야하는 점이야. 요구된 정책이 잘 구현됐는지 검증하고 그 이후 코드 구조에 문제가 없는지 검증하는 절차를 꼭 거치자.