사전 작업
•
더미 데이터 100만건 초기화
진행 과정
•
쿼리: 인덱스 잘 타도록 분리 (where문 내 or절, where문 순서 등 고려)
•
인덱스: 쿼리에 맞게 재설정
findRecentOfferingsWithKeyword
검색어 유무에 따른 최근 공모 조회
SELECT o
FROM OfferingEntity o
WHERE o.id < :lastId
AND (:keyword IS NULL OR o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
ORDER BY o.id DESC
SQL
복사
개선 사항
1.
:keyword IS NULL 비즈니스 로직으로 추출 (withKeyword, withoutKeyword)
최적화 전-후
PK 인덱스로 이미 충분한 성능을 가짐.
•
검색어 X: 25ms
URI: /offerings?filter=RECENT&last-id=90000&page-size=30
LoggingInfoSuccessResponse[identifier=e5004707-d497-4762-a838-fc955ca7c847, memberIdentifier={"sub":"12","exp":1728714372}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=25ms]
SQL
복사
•
검색어 O: 120ms
URI: /offerings?filter=RECENT&last-id=90000&page-size=30&search=대전
LoggingInfoSuccessResponse[identifier=4df848b5-6c5e-4ad8-9a61-603bdfd18f42, memberIdentifier={"sub":"12","exp":1728714372}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=120ms]
SQL
복사
findImminentOfferingsWithKeyword
검색어 유무에 따른 마감 임박만 공모 조회
SELECT o
FROM OfferingEntity o
WHERE (o.offeringStatus = 'IMMINENT')
AND (o.meetingDate > :lastMeetingDate OR (o.meetingDate = :lastMeetingDate AND o.id < :lastId))
AND (:keyword IS NULL OR o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
ORDER BY o.meetingDate ASC, o.id DESC
SQL
복사
개선 사항
1.
:keyword IS NULL 비즈니스 로직으로 추출 (withKeyword, withoutKeyword)
2.
인덱스를 타기 위해 순서 변경 ⇒ 검색어 → status → or절
3.
검색어 where절 쿼리 메서드 분리
4.
인덱스 추가 (meeting_date, id desc) (meeting_address, offering_status) (title, offering_status)
최적화 전
•
검색어 X: 1136ms
/offerings?filter=IMMINENT&last-id=90000&page-size=30
LoggingInfoSuccessResponse[identifier=8af81b32-07d5-4c33-864c-e90ef0cb3087, memberIdentifier={"sub":"1001","exp":1728716240}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=1136ms]
SQL
복사
•
검색어 O: 1130ms ~ 5204ms
/offerings?filter=IMMINENT&last-id=90000&page-size=30&search=대전
LoggingInfoSuccessResponse[identifier=3a8963bc-1519-4fc3-b13f-01b3be42e995, memberIdentifier={"sub":"1001","exp":1728716240}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=1130ms]
SQL
복사
LoggingInfoSuccessResponse[identifier=5d0b60c0-7fcf-40f3-ab55-2426fb92e9ca, memberIdentifier={"sub":"1001","exp":1728727135}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=5204ms]
SQL
복사
최적화 후
•
검색어 X: 141ms (meeting_date, id desc)
/offerings?filter=IMMINENT&last-id=90000&page-size=30
LoggingInfoSuccessResponse[identifier=32b8594b-0919-449a-a462-e5b886a235dd, memberIdentifier={"sub":"1001","exp":1728716240}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=141ms]
SQL
복사
•
검색어 O: 14ms!!!!!!!!! (title, status) (address, status)
/offerings?filter=IMMINENT&last-id=90000&page-size=30&search=대전
LoggingInfoSuccessResponse[identifier=b6aefd7e-85ff-4560-b23f-9c68843da10e, memberIdentifier={"sub":"1001","exp":1728728970}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=14ms]
SQL
복사
findHighDiscountOfferingsWithKeyword
검색어 유무에 따른 높은 할인율순 공모 조회
SELECT o
FROM OfferingEntity o
WHERE (o.offeringStatus != 'CONFIRMED')
AND (o.discountRate IS NOT NULL)
AND (o.discountRate < :lastDiscountRate OR (o.discountRate = :lastDiscountRate AND o.id < :lastId))
AND (:keyword IS NULL OR o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
ORDER BY o.discountRate DESC, o.id DESC
SQL
복사
개선 사항
1.
:keyword IS NULL 비즈니스 로직으로 추출 (withKeyword, withoutKeyword)
2.
≠ ⇒ in 명령어로 변경
3.
인덱스를 타기 위해 순서 변경 ⇒ 검색어 → status → or절
4.
검색어 where절 쿼리 메서드 분리
5.
인덱스 추가 (discount_rate, id desc) (meeting_address, offering_status) (title, offering_status)
최적화 전
•
검색어 X: 6526ms
LoggingInfoSuccessResponse[identifier=8d1b3878-044d-4522-8646-25f659d81700, memberIdentifier={"sub":"1001","exp":1728728970}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=6526ms]
SQL
복사
최적화 후
•
검색어 X: 57ms!!!!!!!!!!!!!! (discountRate, id)
LoggingInfoSuccessResponse[identifier=eaf05add-84e9-41a1-a5d2-698dc0555826, memberIdentifier={"sub":"1001","exp":1728730815}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=57ms]
SQL
복사
•
검색어 O
◦
결과 없을 때: 13ms
LoggingInfoSuccessResponse[identifier=23444891-df54-4c28-adc0-82953514ce5d, memberIdentifier={"sub":"1001","exp":1728730815}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=13ms]
SQL
복사
◦
결과 있을 때: 36ms
LoggingInfoSuccessResponse[identifier=c0a193c1-1472-4bda-8a20-14750f0085fe, memberIdentifier={"sub":"1001","exp":1728730815}, httpMethod=GET, uri=/offerings, requestBody=, statusCode=200, latency=36ms]
SQL
복사
findJoinableOfferingsWithKeyword
검색어 유무에 따른 참여 가능만 공모 조회
SELECT o
FROM OfferingEntity o
WHERE (o.offeringStatus IN ('AVAILABLE', 'IMMINENT'))
AND (o.id < :lastId)
AND (:keyword IS NULL OR o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
ORDER BY o.id DESC
SQL
복사
개선 사항
1.
:keyword IS NULL 비즈니스 로직으로 추출 (withKeyword, withoutKeyword)
2.
≠ ⇒ in 명령어로 변경
최적화 전-후
PK에 걸리기 때문에 성능 충분함. findRecentOfferingsWithKeyword와 같은 맥락.
findByMeetingDateAndOfferingStatusNot
SELECT o
FROM OfferingEntity o
WHERE o.meetingDate = :meetingDate
AND o.offeringStatus != :offeringStatus
SQL
복사
개선 사항
1.
≠ ⇒ in 명령어로 변경
결론
인덱스
create index idx_title_status on offering(title, offering_status);
create index idx_meetingAddress_status on offering(meeting_address, offering_status);
create index idx_discountRate_id on offering(discount_rate, id);
create index idx_meetingDate_id_desc on offering(meeting_date, id desc);
Java
복사
(dev, prod DB는 코드 머지 후 재설정 예정)
높은 성능 개선 포인트
•
정렬을 위한 컬럼에 복합 인덱스 걸었을 때
•
검색을 위한 컬럼에 인덱스 걸었을 때
•
검색절에 묶여 있는 or를 분리했을 때
낮은 성능 개선 포인트 (의외로)
•
pagination을 위한 or절에 인덱스 걸었을 때
•
PK가 있는 where절은 PK에 걸리는게 가장 좋다.
•
어떤 데이터가 파라미터로 들어오느냐에 따라 걸리는 인덱스가 다르다.
고민 포인트
•
동적 쿼리 기술 도입
