-
Notifications
You must be signed in to change notification settings - Fork 8
쿼리 성능 개선 ‐ 템플릿 생성
데이터 스펙
멤버: 10 건
카테고리: 100 건 (멤버 당 10 건)
태그: 2000 건 (멤버 당 200 건)
템플릿: 10만 건 (멤버 당 1만 건)
소스 코드: 10만 ~ 50만 건 (템플릿 당 1~5 개 랜덤 생성)
테스트 조건
10개의 스레드로 100번씩 실행
총 1000번의 요청 실행
생성하는 템플릿 조건
사용하는 태그 : 20개 (10개는 존재하지 않는 태그 사용) 소스 코드 : 2개
Total request count: 1000
Total elapsed time: 81569ms
Average elapsed time: 81ms
-
Repository:
CategoryRepository
-
Method:
fetchById
select
c1_0.id,
c1_0.created_at,
c1_0.is_default,
c1_0.member_id,
m1_0.id,
m1_0.created_at,
m1_0.modified_at,
m1_0.name,
m1_0.password,
m1_0.salt,
c1_0.modified_at,
c1_0.name
from
category c1_0
join
member m1_0
on m1_0.id=c1_0.member_id
where
c1_0.id=?
- 호출 횟수: 1회
-
Repository:
TemplateRepository
-
Method:
save
insert
into
template
(category_id, created_at, description, member_id, modified_at, title)
values
(?, ?, ?, ?, ?, ?)
- 호출 횟수: 1회
-
Repository:
TagRepository
-
Method:
findNameByNamesIn
(직접 쿼리 작성)
select
t1_0.name
from
tag t1_0
where
t1_0.name in (?, ?, ?, ?, ?)
- 호출 횟수: 1회
-
Repository:
TagRepository
-
Method:
fetchByName
select
t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name
from
tag t1_0
where
t1_0.name=?
- 호출 횟수: (전체 태그 중 존재하는 태그 개수)
-
Repository:
templateTagRepository
-
Method:
saveAll
select
tt1_0.tag_id,
tt1_0.template_id,
tt1_0.created_at,
tt1_0.modified_at,
t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name,
t2_0.id,
t2_0.category_id,
t2_0.created_at,
t2_0.description,
(select
count(*)
from
likes
where
likes.template_id = t2_0.id),
t2_0.member_id,
t2_0.modified_at,
t2_0.title
from
template_tag tt1_0
join
tag t1_0
on t1_0.id=tt1_0.tag_id
join
template t2_0
on t2_0.id=tt1_0.template_id
where
(tt1_0.tag_id, tt1_0.template_id) in ((?, ?))
insert
into
template_tag
(created_at, modified_at, tag_id, template_id)
values
(?, ?, ?, ?)
- 호출 횟수: (전체 태그 중 존재하는 태그 개수)
-
Repository:
TagRepository
-
Method:
saveAll
insert
into
tag
(created_at, modified_at, name)
values
(?, ?, ?)
- 호출 횟수: (전체 태그 중 존재하는 새로운 태그 개수)
-
Repository:
TemplateTagRepository
-
Method:
saveAll
select
tt1_0.tag_id,
tt1_0.template_id,
tt1_0.created_at,
tt1_0.modified_at,
t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name,
t2_0.id,
t2_0.category_id,
t2_0.created_at,
t2_0.description,
(select
count(*)
from
likes
where
likes.template_id = t2_0.id),
t2_0.member_id,
t2_0.modified_at,
t2_0.title
from
template_tag tt1_0
join
tag t1_0
on t1_0.id=tt1_0.tag_id
join
template t2_0
on t2_0.id=tt1_0.template_id
where
(tt1_0.tag_id, tt1_0.template_id) in ((?, ?))
insert
into
template_tag
(created_at, modified_at, tag_id, template_id)
values
(?, ?, ?, ?)
- 호출 횟수: (전체 태그 중 존재하는 새로운 태그 개수)
-
Repository:
SourceCodeRepository
-
Method:
saveAll
insert
into
source_code
(content, created_at, filename, modified_at, ordinal, template_id)
values
(?, ?, ?, ?, ?, ?)
- 호출 횟수: 소스코드 개수
-
Repository:
SourceCodeJpaRepository
-
Method:
fetchByTemplateAndOrdinal
select
sc1_0.id,
sc1_0.content,
sc1_0.created_at,
sc1_0.filename,
sc1_0.modified_at,
sc1_0.ordinal,
sc1_0.template_id
from
source_code sc1_0
where
sc1_0.template_id=?
and sc1_0.ordinal=?
- 호출 횟수: 1번
-
Repository:
ThumbnailRepository
-
Method:
save
insert
into
thumbnail
(created_at, modified_at, source_code_id, template_id)
values
(?, ?, ?, ?)
- 호출 횟수: 1번
- 인덱스 제안:
-
name
- 이유: 템플릿에서 사용할 태그들을 이름으로 조회한다.
- 추가 여부: O,
CREATE INDEX idx_tag_name ON tag(name);
-
explain
SELECT t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name
FROM tag t1_0
WHERE t1_0.name IN ('newTag0', 'newTag1', 'newTag2');
id | select_type | type | possible_keys | key | Extra |
---|---|---|---|---|---|
1 | SIMPLE | range | idx_tag_name | idx_tag_name | Using index condition |
참고 : Tag
를 생성할 때, 이미 존재하는 Tag
는 생성하지 않는다.
as-is
존재하는 태그들을 List<String> existingTags
로 이름만 조회한 후, 각각의 이름에 대해 TagRepository.fetchByName()
으로 Tag
조회한다.
존재하는 태그의 개수 만큼 TagRepository.fetchByName()
가 호출된다.
@Transactional
public void createTags(Template template, List<String> tagNames) {
List<String> existingTags = tagRepository.findNameByNamesIn(tagNames);
templateTagRepository.saveAll(
existingTags.stream()
.map(tagRepository::fetchByName)
.map(tag -> new TemplateTag(template, tag))
.toList()
);
List<Tag> newTags = tagRepository.saveAll(
tagNames.stream()
.filter(tagName -> !existingTags.contains(tagName))
.map(Tag::new)
.toList()
);
templateTagRepository.saveAll(
newTags.stream()
.map(tag -> new TemplateTag(template, tag))
.toList()
);
}
to-be
tagRepository.findByNameIn(List<String> names);
으로 태그 자체를 조회하여 Tag
객체를 재사용한다.
tagRepository.fetchByName()
을 추가로 호출할 필요가 없다.
@Transactional
public void createTags(Template template, List<String> tagNames) {
List<Tag> existingTags = tagRepository.findByNameIn(tagNames);
List<String> existNames = existingTags.stream()
.map(Tag::getName)
.toList();
List<Tag> newTags = tagRepository.saveAll(
tagNames.stream()
.filter(name -> !existNames.contains(name))
.map(Tag::new)
.toList()
);
existingTags.addAll(newTags);
for (Tag existingTag : existingTags) {
templateTagRepository.save(new TemplateTag(template, existingTag));
}
}
TemplateTag
를 저장하기 전, TemplateTag
를 조회하고 이 때 사용하지 않는 Template
, Tag
를 조회하기 위해 불필요하게 긴 쿼리문을 사용한다.
select
tt1_0.tag_id,
tt1_0.template_id,
tt1_0.created_at,
tt1_0.modified_at,
t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name,
t2_0.id,
t2_0.category_id,
t2_0.created_at,
t2_0.description,
(select
count(*)
from
likes
where
likes.template_id = t2_0.id),
t2_0.member_id,
t2_0.modified_at,
t2_0.title
from
template_tag tt1_0
join
tag t1_0
on t1_0.id=tt1_0.tag_id
join
template t2_0
on t2_0.id=tt1_0.template_id
where
(tt1_0.tag_id, tt1_0.template_id) in ((?, ?))
이를 개선하기 위해 TemplateTag.template
, TemplateTag.tag
필드를 lazy loading 한다.
각 필드에 @ManyToOne(fetch = FetchType.LAZY)
어노테이션 추가
Total request count: 1000
Total elapsed time: 50798ms
Average elapsed time: 50ms
-
Repository:
CategoryRepository
-
Method:
fetchById
select
c1_0.id,
c1_0.created_at,
c1_0.is_default,
c1_0.member_id,
m1_0.id,
m1_0.created_at,
m1_0.modified_at,
m1_0.name,
m1_0.password,
m1_0.salt,
c1_0.modified_at,
c1_0.name
from
category c1_0
join
member m1_0
on m1_0.id=c1_0.member_id
where
c1_0.id=?
- 호출 횟수: 1회
-
Repository:
TemplateRepository
-
Method:
save
insert
into
template
(category_id, created_at, description, member_id, modified_at, title)
values
(?, ?, ?, ?, ?, ?)
- 호출 횟수: 1회
-
Repository:
TagRepository
-
Method:
findByNameIn
(직접 쿼리 작성)
select
t1_0.id,
t1_0.created_at,
t1_0.modified_at,
t1_0.name
from
tag t1_0
where
t1_0.name in (?, ?, ?, ?, ?)
- 호출 횟수: 1회
-
Repository:
TagRepository
-
Method:
saveAll
insert
into
tag
(created_at, modified_at, name)
values
(?, ?, ?)
- 호출 횟수: (전체 태그 중 존재하는 새로운 태그 개수)
-
Repository:
TemplateTagRepository
-
Method:
saveAll
select
tt1_0.tag_id,
tt1_0.template_id,
tt1_0.created_at,
tt1_0.modified_at
from
template_tag tt1_0
where
(
tt1_0.tag_id, tt1_0.template_id
) in ((?, ?))
insert
into
template_tag
(created_at, modified_at, tag_id, template_id)
values
(?, ?, ?, ?)
- 호출 횟수: (태그 개수)
-
Repository:
SourceCodeRepository
-
Method:
saveAll
insert
into
source_code
(content, created_at, filename, modified_at, ordinal, template_id)
values
(?, ?, ?, ?, ?, ?)
- 호출 횟수: 소스코드 개수
-
Repository:
SourceCodeJpaRepository
-
Method:
fetchByTemplateAndOrdinal
select
sc1_0.id,
sc1_0.content,
sc1_0.created_at,
sc1_0.filename,
sc1_0.modified_at,
sc1_0.ordinal,
sc1_0.template_id
from
source_code sc1_0
where
sc1_0.template_id=?
and sc1_0.ordinal=?
- 호출 횟수: 1번
-
Repository:
ThumbnailRepository
-
Method:
save
insert
into
thumbnail
(created_at, modified_at, source_code_id, template_id)
values
(?, ?, ?, ?)
- 호출 횟수: 1번
- 백엔드 코드 컨벤션
- 백엔드 기술 스택 및 선정 이유
- 각종 인스턴스 설정 파일 및 구성 위치 가이드
- 1.1.2 버전 ERD (24.09.27)
- 백엔드 CI CD 동작 프로세스
- 로컬 DB 환경 설정
- 백엔드 로깅 전략
- 백엔드 로그 모니터링 구성도
- 스프링 메트릭 모니터링 구성도
- Flyway 로 스키마 관리
- 코드잽 서버 구성도
- Git Submodule 사용 메뉴얼
- 프론트엔드 코드 컨벤션
- 프론트엔드 기술 스택 및 선정 이유
- 프론트엔드 서비스 타겟 환경 및 브라우저 지원 범위 선정
- 프론트엔드 모니터링 및 디버깅 환경 구축
- 프론트엔드 테스트 목록
- 프론트엔드 라이브러리 기술 검토
- 프론트엔드 개발서버, 운영서버 빌드 및 배포 환경 구분
- 목표했던 타겟 환경과 디바이스에서 서비스 핵심 기능 동작 확인
- 프론트엔드 접근성 개선 보고서
- EC2 로그 확인 방법
- VSCode를 통한 EC2 인스턴스 SSH 연결 방법
- 터미널을 통한 EC2 인스턴스 SSH 연결 방법
- NGINX 설정 파일 접근 및 적용 방법
- DB 접속 및 백업 방법
- [QA] 배포 전 체크리스트
- CI 파이프라인 구축
- CD 파이프라인 구축
- 백엔드 CI CD 트러블슈팅
- Lombok Annotation Processor 의존성을 추가한 이유
- 2차 스프린트 기준 ERD
- DTO 검증하기
- ProblemDetail
- Fork된 레포지토리 PR에서 CI Secrets 접근 문제 해결
- AWS CloudWatch 모니터링
- 스프링 메트릭 모니터링 구축 방법
- 로깅과 Logback에 대해 알아보아요.
- 백엔드 CD 파이프라인 Ver.2
- 요청, 응답 로그에 correlationId 를 추가하자!
- 3차 스프린트 기준 ERD
- 더미데이터 생성하고 실행하기
- 쿼리 성능 개선 결과
- 테이블별 인덱스 설정 목록
- 사용자 증가 시 발생할 수 있는 문제 상황과 개선 방안
- k6를 사용한 서버 부하 테스트
- 6차 스프린트 기준 ERD
- Query Performance Improvement Results
- 테스트 전략 및 CI 설정
- CI CD 구조
- 배포 전, 로컬에서 로그인 기능 포함 테스트해보는 법
- stylelint 적용기
- 내 작업 브랜치 중간에 Merge된 동료의 작업물을 넣고 싶다면 pull vs rebase
- [TS] Webpack config
- [TS] Webpack 환경에서 MSW v2 이슈
- [TS] webpack에서 react‐router‐dom 적용 안됨
- 2024.07.28 새 기획 회의
- 2024.07.26 2차 데모데이 후 회의
- 2024.07.11 백엔드 논의 좀 할게요
- 2024.07.11 백엔드 ERD 회의
- 2024.07.09 깃 브랜치 전략, PR 템플릿 회의
- 2024.07.03 주제 선정 회의
- 2023.07.03 팀빌딩데이 킥오프 회의
- 2023.08.07 3차 스프린트 중간회고