서론
개발 중인 스프링 프로젝트에서 캐싱과 파티셔닝을 적용해보았다.
이전 글에서 캐싱으로 응답속도가 빨라진 것을 확인했는데, 과연 파티셔닝도 적용했을 때 성능이 좋아졌을까?
파티셔닝은 주로 대용량 테이블의 유지 관리를 돕고 테이블에서 대량의 데이터를 빠르게 로드하고 제거하는 방법을 제공하기 위해 설계된 기능이다. 파티셔닝은 쿼리 성능을 향상시킬 수 있지만, 그 성능 향상이 보장되는 것은 아니다.
캐싱과 달리 파티셔닝은 IDE만으로는 테스트하기 까다로워서 nGrinder 라는 성능 테스트 도구를 사용했다.
1. nGrinder
https://github.com/naver/ngrinder
nGrinder는 네이버에서 제공하는 서버 성능/부하 테스트 오픈 소스 프로젝트이다.
애플리케이션을 개발하고 nGrinder에서 여러가지 가상 시나리오를 만들어 트래픽에 몰렸을 때 성능을 측정할 수 있도록 도와준다.
2. nGrinder Setup
nGrinder 공식 저장소 릴리즈 페이지에서 war파일을 다운받고 아래 명령어로 war 파일을 실행시킨다.
그 다음에는 인터넷 브라우저에서 localhost:8300 을 접속한다.
초기 ID,PW는 admin, admin 이다.
우측 상단 메뉴바에서 에이전트 다운로드를 클릭해서 Agent를 다운로드 받는다.
3. Test Script 작성
4. Performance Test
처음에는 행이 2000여개 들어있는 테이블을 조회하는 것으로 테스트를 진행했다.
AWS EC2 프리티어 서버에서 테스트 진행하면 서버가 다운될 것 같아서 로컬에서 테스트했다.
- TPS ( Transactions per Second ) : 시간 당 처리량
- MTT (Mean Test Time) : 평균 테스트 시간
1) 2,000 여개 행 가진 테이블 조회 테스트
1-1) 파티션 적용 전
1-2) 파티션 적용 후
생각보다 차이가 미미해 보인다. 🤔
알아보니 파티셔닝은 데이터가 많아야 효과가 생긴다는 말을 들어서
가상의 테스트 데이터 10,000개를 만들어서 테스트해보기로 했다.
지역별 조회로 각각 5000개의 데이터를 가진 2개의 지역 정보를 생성해보았다.
2) 테스트 데이터 생성 (MySQL)
파티션 1 (p1) : 컬럼 addr_lvl1 의 값이 "서울특별시"인 row 5,000개
파티션 2 (p2) : 컬럼 addr_lvl1 의 값이 "전라남도"인 row 5,000개
파티셔닝 결과 확인하기 :
3) 데이터 10,000 개 대상 테스트
3-1) 캐싱
3-2) 캐싱 + 파티셔닝
3-3) 파티셔닝
3-4) 캐싱X + 파티셔닝X
3-5) 결과
파티셔닝 해둔 경우가 오히려 안 한 것보다 성능이 낮게 나왔다.
이유가 뭘까 찾아보았다.
5. 캐싱이 파티셔닝보다 빠른 이유
- 데이터 접근 속도
캐싱은 자주 사용되는 데이터를 메모리에 저장하므로, 파티셔닝에서처럼 SQL 서버에 쿼리를 날리고 디스크에서 데이터를 읽는 것보다 훨씬 빠른 속도로 데이터에 접근할 수 있다.
하지만 캐싱은 메모리 용량의 제한으로 인해 데이터 크기에 따른 한계가 있을 경우에는 파티셔닝이 더 효과적일 수 있다.
6. 왜 파티셔닝 적용했을 때 성능이 더 느려졌을까
6-1) EXPLAIN 으로 쿼리 실행 계획 분석하기
기존에 사용하던 자바 코드에선 다음 코드를 사용했다.
(테이블의 행은 10,000개인데 왜 2개 추가가 된걸까..)
전체 테이블이 모두 조회가 되었다. 😰
파티셔닝된 테이블에서 파티션 키가 아닌 열로만 조회할 경우, 파티셔닝의 이점이 크게 줄어들거나 효과가 없을 수 있다.
이는 주로 파티션 프루닝(partition pruning)이 제대로 이루어지지 않기 때문이다.
파티션 프루닝이란 쿼리 조건에 따라 필요한 파티션만 읽고 나머지는 건너뛰는 최적화 기술이다.
파티션 키가 아닌 열로만 조회하면 모든 파티션을 탐색해야 하므로 성능 저하가 발생할 수 있다.
만약 파티셔닝의 키인 division_key로 검색한다면 어떻게 될까.
한 파티션의 모든 행의 수인 5000개가 탐색된다.
테이블을 division_key 키로 겁색하는 수정된 코드로 nGrinder로 테스트해보았다.
- 코드 변경했을 때
- 코드 변경 + 캐시
결과 정리 :
여전히 파티셔닝을 적용하지 않았을 때보다 적용했을 때 성능이 낮아졌다.....
6-2) SELECT * FROM 테이블 PARTITION p1
MySQL은 쿼리에 대한 명시적인 파티션 선택을 지원한다.
예를 들어 파티션에서 조건 과 일치하는 SELECT * FROM t PARTITION (p0,p1) WHERE c < 5행만 선택한다 .
이 경우 MySQL은 table의 다른 파티션을 확인하지 않는다 .
검사하려는 파티션을 이미 알고 있는 경우 쿼리 속도가 크게 향상될 수 있다.
하지만 JPA 또는 기타 ORM 프레임워크는 기본적으로 파티션 테이블을 지원하지 않는다.
6-3) 일반적인 이유
1. 적절하지 않은 파티셔닝 키 선택
파티셔닝 키를 잘못 선택하면 데이터가 불균형하게 분포될 수 있다.
이는 특정 파티션에 너무 많은 데이터가 집중되거나 빈 파티션이 생길 수 있어 성능 저하를 유발한다.
2. 조인이 빈번한 경우
파티션된 테이블 간의 조인이 빈번하게 발생하면, 파티션 간 데이터를 병합해야 하는 오버헤드가 발생할 수 있다.
이는 성능 저하로 이어진다.
3.파티션 개수가 너무 많거나 적은 경우
파티션이 너무 많으면 관리 오버헤드가 증가하고, 파티션이 너무 적으면 파티셔닝의 이점을 누리기 어렵다.
파티션 개수를 너무 세분화하면 인덱스 관리 및 검색 성능이 저하될 수 있다.
4.파티셔닝된 컬럼을 자주 업데이트하는 경우
파티셔닝 키를 자주 업데이트하면 해당 행을 다른 파티션으로 이동해야 하는 오버헤드가 발생한다. 이는 성능을 저하시킬 수 있다.
예시: 파티셔닝 키가 변경될 때마다 전체 데이터 구조를 수정해야 하는 경우.
5. 복잡한 쿼리
복잡한 쿼리를 실행할 때 파티셔닝 전략이 잘못 설정되어 있으면 쿼리 최적화가 어려워져 성능이 저하될 수 있다.
예시: 파티셔닝 전략이 쿼리 패턴과 일치하지 않는 경우 쿼리 계획이 비효율적으로 생성될 수 있다.
6-4) 인덱스
MySQL에서 테이블을 파티셔닝하면 자동으로 파티션 키에 대해 인덱스가 생성되지 않는다.
따라서 성능을 최적화하기 위해서는 필요한 경우 별도로 인덱스를 생성해야 한다.
MySQL에서는 기본적으로 로컬 인덱스만 지원한다. MySQL 8.0.13부터는 글로벌 인덱스도 지원되기 시작했다.
- 로컬 인덱스
각 파티션마다 별도로 존재하는 인덱스이다. 파티션 내에서만 데이터를 인덱싱하므로 파티션이 독립적으로 관리된다.
파티션을 추가하거나 삭제할 때 인덱스도 함께 관리되므로 관리가 용이하다.
- 글로벌 인덱스
테이블 전체에 걸쳐 존재하는 인덱스이다. 파티션에 관계없이 모든 데이터를 인덱싱하므로, 특정 데이터에 대한 쿼리가 더 빠를 수 있다.
파티션을 추가하거나 삭제할 때 인덱스를 다시 빌드해야 할 수도 있어 관리가 더 복잡하다.
쿼리가 특정 파티션 내의 데이터를 자주 참조한다면 로컬 인덱스가 적합할 수 있다.
대부분의 경우 로컬 인덱스를 사용한다.
MySQL에는 분할된 테이블에서 한 번에 한 파티션씩 인덱스 생성을 지원하는 구문이 없다. 인덱스는 하나의 ALTER TABLE 또는 CREATE INDEX 문의 모든 파티션에 추가된다.
6-5) 기타 이유
- 최소 1 M (백만) 개의 행이 존재하는 테이블에 파티셔닝을 적용해야 한다.
분할로 인해 소규모 데이터 세트에는 도움이 되지 않을 수 있는 오버헤드와 복잡성이 발생하기 때문이다.
그래서 테스트 데이터를 procedure로 100만 개 만들어보려고 시도했으나 mysql에서 다음의 에러로 거부당했다..
혹시나해서 테이블을 조회했더니 50만개 행 생성을 시도했던 하나의 프로시저에서 5만4천여개의 행만 만들어지고 연결이 끊어진 것으로 확인되었다..100만 개 이상 데이터는 회사 가서 경험하자 ^^
7. 후기
파티셔닝 진행했던 테이블의 행 수가 너무 적어서 오버헤드로 인해 오히려 성능 저하가 일어난 것으로 보인다.
그래서 해당 테이블에 파티셔닝을 적용하지 않기로 결정했다.
원인을 알아내기까지 생각보다 너무 많은 시간이 걸렸다.
Reference
https://dev.mysql.com/doc/refman/8.0/en/partitioning-overview.html
https://www.oracle.com/docs/tech/partitioning-guide-2703320.pdf
https://coding-factory.tistory.com/841
https://dev.mysql.com/doc/refman/8.4/en/create-index.html
https://stackoverflow.com/questions/44964442/how-does-indexes-work-with-mysql-partitioned-table