|
1 | | -## 트랜잭션 전파 속성 |
| 1 | +<details> |
| 2 | +<summary><h1> 트랜잭션 전파 속성</h1></summary> |
| 3 | + |
2 | 4 | ### REQUIRED |
3 | 5 | : 스프링 트랜잭션의 기본 설정 이미 진행 중인 트랜잭션이 있으면 기존 트랜잭션에 합류하고, 없으면 새로운 물리 트랜잭션을 생성함 여러 논리 트랜잭션이 하나의 물리 트랜잭션을 공유하게 됨 |
4 | 6 |
|
@@ -328,7 +330,10 @@ processOrder 시작 (신규 물리 트랜잭션 1 시작) |
328 | 330 |
|
329 | 331 | REQUIRES_NEW보다 DB Connection 낭비를 줄이면서, 부분 롤백(특정 작업 실패 시 메인 작업은 유지)이 필요할 때 사용 (단, JPA에서는 지원하지 않으며 JDBC Savepoint 기능에 의존) |
330 | 332 |
|
331 | | -## 트랜잭션 개선 사항 |
| 333 | +</details> |
| 334 | + |
| 335 | +<details> |
| 336 | +<summary><h1>트랜잭션 개선 사항</h1></summary> |
332 | 337 |
|
333 | 338 | 이번 리팩토링에서는 결제/취소/만료 스케줄러 흐름을 중심으로 트랜잭션 경계를 다시 정리함 |
334 | 339 | 목표는 다음 3가지 : |
@@ -473,11 +478,59 @@ REQUIRES_NEW보다 DB Connection 낭비를 줄이면서, 부분 롤백(특정 |
473 | 478 | - 보상 처리의 동시성 안정성이 높아짐 |
474 | 479 | - 결제 상태 전이 규칙이 강화되어 잘못된 상태 변경 가능성이 줄어듦 |
475 | 480 |
|
476 | | ---- |
477 | | -## 쿼리 성능 최적화 |
| 481 | +</details> |
| 482 | + |
| 483 | +<details> |
| 484 | +<summary><h1>인덱스 종류 조사</h1></summary> |
| 485 | + |
| 486 | +### 클러스터링 인덱스 ( |
| 487 | + |
| 488 | +* **개념:** 테이블의 실제 데이터가 인덱스의 정렬 순서대로 물리적으로 저장되는 방식 (국어사전과 같은 구조) |
| 489 | +* **특징:** |
| 490 | +* 테이블당 딱 1개만 생성 가능함. |
| 491 | +* 보통 기본키(Primary Key)에 자동 적용됨. |
| 492 | + |
| 493 | +* **적용 지점:** `BETWEEN`, `>`, `<` 등 범위 검색(Range Query)이 잦은 컬럼. `ORDER BY`나 `GROUP BY`가 자주 발생하는 기준 컬럼에 매우 유리함. |
| 494 | + |
| 495 | +### 논클러스터링 인덱스 / 세컨더리 인덱스 |
| 496 | + |
| 497 | +* **개념:** 물리적 데이터 정렬과 무관하게 논리적인 인덱스(색인) 페이지만 따로 만들어두고, 실제 데이터가 있는 주소값을 저장하는 방식 (전공 서적 맨 뒤의 찾아보기와 같은 구조) |
| 498 | +* **특징:** |
| 499 | +* 테이블당 여러 개 생성 가능함. |
| 500 | +* 인덱스에서 위치를 찾은 뒤, 실제 테이블 데이터로 한 번 더 접근(Look-up)하는 과정이 필요함. |
| 501 | + |
| 502 | +* **적용 지점:** `WHERE` 절의 조건이나 `JOIN`에 자주 사용되지만, 데이터 변경(INSERT, UPDATE, DELETE)이 너무 빈번하지 않은 컬럼에 적합함. |
| 503 | + |
| 504 | +### 커버링 인덱스 |
| 505 | + |
| 506 | +* **개념:** 실행하려는 쿼리를 충족시키는 데 필요한 '모든 컬럼'을 이미 다 가지고 있는 인덱스 |
| 507 | +* **특징:** |
| 508 | +* 특정 인덱스의 종류라기보다는, **쿼리의 모든 항목이 인덱스에 포함된 상태**를 뜻함. |
| 509 | +* 실제 데이터 블록(테이블)에 접근하는 Look-up 과정 없이 인덱스 페이지만 읽고 바로 결과를 반환하므로 성능이 압도적으로 빠름. |
| 510 | + |
| 511 | +* **적용 지점:** 무한 스크롤이나 게시판 페이징 등 데이터베이스 부하가 심각한 쿼리를 튜닝할 때. `SELECT` 절에 가져와야 할 컬럼 수가 적고, 검색 조건이 명확한 경우 매우 효과적임. |
| 512 | + |
| 513 | +### 복합 인덱스 |
478 | 514 |
|
479 | | -이번 과제에서는 실제 서비스에서 자주 사용되거나 성능 병목이 발생할 수 있는 쿼리를 선정한 뒤, |
480 | | -`EXPLAIN ANALYZE`를 통해 실행 계획을 직접 확인하고 인덱스를 개선하여 성능을 최적화했다. |
| 515 | +* **개념:** 2개 이상의 컬럼을 결합하여 하나로 만든 인덱스 |
| 516 | +* **특징:** |
| 517 | +* 인덱스를 구성하는 **컬럼의 배치 순서**가 성능을 좌우함. (첫 번째 컬럼 기준으로 먼저 정렬된 후, 그 안에서 두 번째 컬럼이 정렬되는 방식) |
| 518 | + |
| 519 | +* **적용 지점:** 항상 2개 이상의 컬럼이 `WHERE` 조건에서 `AND`로 같이 묶여서 검색될 때. (예: `WHERE status = 'ACTIVE' AND created_at > ...` 형태의 쿼리가 핵심 비즈니스 로직일 때 적용함) |
| 520 | + |
| 521 | +### 유니크 인덱스 |
| 522 | + |
| 523 | +* **개념:** 중복된 값을 절대 허용하지 않는 인덱스 |
| 524 | +* **특징:** |
| 525 | +* 내부적인 트리 구조는 세컨더리 인덱스와 동일함. |
| 526 | +* 데이터 삽입/수정 시 중복 체크 과정이 추가되므로 쓰기 성능은 약간 떨어질 수 있음. |
| 527 | + |
| 528 | +* **적용 지점:** 유저의 이메일, 닉네임 등 비즈니스 로직상 유일성이 반드시 보장되어야 하면서, 동시에 검색 조건으로 자주 사용되는 컬럼에 적용함. |
| 529 | + |
| 530 | +</details> |
| 531 | + |
| 532 | +<details> |
| 533 | +<summary><h1>쿼리 성능 최적화</h1></summary> |
481 | 534 |
|
482 | 535 | 최적화 대상은 다음 3가지 쿼리이다. |
483 | 536 |
|
@@ -652,28 +705,19 @@ Covering index range scan on ms using idx_movie_screen_screen_start_end |
652 | 705 |
|
653 | 706 | --- |
654 | 707 |
|
655 | | -## 실행 계획 및 성능 비교 요약 |
| 708 | +## 실행 계획 및 성능 비교 |
656 | 709 |
|
657 | 710 | | 쿼리 | 기존 방식 | 개선 방식 | 최적화 전 | 최적화 후 | |
658 | 711 | |---|---|---|---:|---:| |
659 | 712 | | 예약 만료 대상 조회 | `status`, `created_at` 인덱스 + `payment_status` 후처리 | `status`, `payment_status`, `created_at`, `id` 복합 인덱스 | 실행시간 차이 미미 | `Covering index range scan` 확인 | |
660 | 713 | | 사용자별 예매 목록 조회 | `user_id` 인덱스 탐색 후 정렬 | `user_id`, `created_at`, `id` 복합 인덱스 | `2.57 ms` | `0.665 ms` | |
661 | 714 | | 상영 시간 겹침 검사 | `screen_id` 인덱스 탐색 후 시간 필터 | `screen_id`, `start_at`, `end_at` 복합 인덱스 | `0.124 ms` | `0.052 ms` | |
662 | 715 |
|
663 | | ---- |
664 | | - |
665 | | -## 결론 |
666 | | - |
667 | | -이번 최적화에서는 단순히 인덱스를 추가하는 데 그치지 않고, |
668 | | -실제 쿼리의 조건 순서와 접근 패턴에 맞게 복합 인덱스를 설계했다. |
669 | | - |
670 | | -특히 다음과 같은 점을 확인할 수 있었다. |
671 | | - |
672 | | -- 조건 컬럼이 인덱스에 충분히 반영되지 않으면 후처리 `Filter`가 남는다. |
673 | | -- `WHERE + ORDER BY + LIMIT` 패턴은 정렬 컬럼까지 포함한 복합 인덱스가 매우 중요하다. |
674 | | -- `Covering index scan`이 가능해지면 테이블 본문 접근 없이 더 효율적인 조회가 가능하다. |
| 716 | +</details> |
675 | 717 |
|
676 | 718 |
|
| 719 | +<details> |
| 720 | +<summary><h1>로깅과 캐싱</h1></summary> |
677 | 721 |
|
678 | 722 | ## 운영 로깅 개선 |
679 | 723 |
|
@@ -716,6 +760,8 @@ Covering index range scan on ms using idx_movie_screen_screen_start_end |
716 | 760 | - `AdminMovieService#createTheater()`: 극장 생성 후 해당 지역 목록이 달라지므로 `theatersByRegion` 캐시 무효화 |
717 | 761 | - `AdminMovieService#createSchedule()`: 시간표 등록 후 같은 극장/날짜 조회 결과가 달라지므로 해당 `schedules` 캐시 무효화 |
718 | 762 | - `ReviewService#createReview()`: 리뷰 작성 후 리뷰 목록과 영화 평점 정보가 달라질 수 있어서 `movieReviews`, `movieDetail` 캐시 무효화 |
| 763 | +</details> |
| 764 | + |
719 | 765 |
|
720 | 766 | <details> |
721 | 767 | <summary><h1>❓</h1></summary> |
@@ -885,6 +931,7 @@ JOIN m.team t -- Member 엔티티 내부의 team 필드(연관관계) 기준 |
885 | 931 | - **해결 :** 1. 컬렉션 타입을 `List` 대신 `Set`으로 변경함 (단, 데이터 순서가 보장되지 않고 여전히 카테시안 곱 문제는 존재) |
886 | 932 |
|
887 | 933 | 2. 가장 권장되는 방법은 하나만 페치 조인하고 나머지는 **`Batch Size`** 설정을 통해 여러 번의 쿼리로 나누어 가져오는 것 |
| 934 | + |
888 | 935 | </details> |
889 | 936 |
|
890 | 937 |
|
|
0 commit comments