Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: redis를 활용한 조회 성능 최적화 #480

Merged
merged 63 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
94c9e0b
chore: redis 설정 추가
mikekks Nov 10, 2024
5db062a
chore: redis 관련 의존성 추가
mikekks Nov 10, 2024
51f4ed8
feat(CoLeaderReader): 캐싱을 위한 CoLeaderReader 레이어 구현
mikekks Nov 10, 2024
79d13f6
refactor(CoLeaderRepository): CoLeader user 관련 데이터 페치조인 구현
mikekks Nov 10, 2024
c37b0b2
refactor(ControllerExceptionAdvice): 서버 에러 트레이스를 위해 트레이스 스택 출력하도록 개선
mikekks Nov 10, 2024
f99f29c
chore(ExecutionLoggingAop): redis 관련 세팅 로깅 aop 에서 제외
mikekks Nov 10, 2024
3c01584
chore(UserActivityVO): redis에 저장되어 있는 객체 역직렬화 하기 위한 코드 수정
mikekks Nov 10, 2024
9475efe
chore(ImageUrlVO): redis에 저장되어 있는 객체 역직렬화 하기 위한 코드 수정
mikekks Nov 10, 2024
fab543a
chore(Meeting):
mikekks Nov 10, 2024
63a9de1
chore(BaseTimeEntity): LocalDateTime 직렬화 및 역직렬화 문제로 인해 코드 추가
mikekks Nov 10, 2024
91af087
chore(Meeting): LocalDateTime 직렬화 및 역직렬화 문제로 인해 코드 추가
mikekks Nov 10, 2024
ef4a5f3
feat(UserReader): 캐싱을 위한 UserReader 레이어 구현
mikekks Nov 10, 2024
359b766
feat(MeetingReader): 캐싱을 위한 MeetingReader 레이어 구현
mikekks Nov 10, 2024
0aaad7e
chore: LazyLoading 객체 저장 오류 해결
mikekks Nov 10, 2024
242674d
add(RedisConfig): redis 관련 설정 추가
mikekks Nov 10, 2024
d8239d5
refactor(MeetingV2ServiceImpl): 캐시를 활용환 성능 최적회
mikekks Nov 10, 2024
32191cb
test(yml): redis 설정 추가
mikekks Nov 13, 2024
6013885
chore(User, Meeting): lazyLoading 객체 redis에 저장하지 않도록 구현
mikekks Nov 13, 2024
09ef831
chore(RedisConfig): host와 port 명시적으로 설정
mikekks Nov 13, 2024
a1051b0
feat(AbstractContainerBaseTest): redis 테스트 컨테이너 추가
mikekks Nov 13, 2024
f7156ef
fix(AbstractContainerBaseTest): Property 수정
mikekks Nov 13, 2024
3cbb36d
chore(yml): dev redis 경로 변경
mikekks Nov 13, 2024
11e2cd2
chore(cd-dev): 배포 테스트
mikekks Nov 13, 2024
adf5fe3
merge: 충돌 해결
mikekks Nov 13, 2024
78a7320
chore(cd-dev): 배포 테스트
mikekks Nov 13, 2024
96c5f2a
chore(cd-dev): 배포 테스트
mikekks Nov 13, 2024
94a574b
chore(cd-dev): 배포 테스트
mikekks Nov 13, 2024
e67b833
chore(UserReader): 레디스에 캐싱하는 데이터를 User 에서 MeetingCreatorDto 로 변경
mikekks Nov 13, 2024
df1d110
rename(redisContainerBaseTest): redisContainerBaseTest 으로 파일 이름 변경
mikekks Nov 13, 2024
1f75c21
chore(redisContainerBaseTest): 싱글톤으로 수정
mikekks Nov 13, 2024
45c5db4
merge: 충돌 해결
mikekks Dec 8, 2024
ab88260
chore: redis 추가
mikekks Dec 8, 2024
b6635e5
chore: redis localhost로 변경
mikekks Dec 8, 2024
17cc5c2
chore: readonly 옵션 추가
mikekks Dec 8, 2024
4a88ec7
del(User): 주석삭제
mikekks Dec 8, 2024
8938b9e
chore(UserReader): readonly 옵션 추가
mikekks Dec 8, 2024
2f08cda
chore(RedisConfig): 디폴트 ttl 설정, 이후에 다른 ttl 주기의 캐시가 필요하면 추가할 수 있도록 구현
mikekks Dec 12, 2024
96118f2
chore(ImageUrlVO): 필드 final 로 변경 및 직렬화 설정
mikekks Dec 12, 2024
aa995f8
chore(Meeting): 기존 상태로 원상복구
mikekks Dec 12, 2024
e9395ff
feat(MeetingReader): Meeting 반환이 아닌 MeetingRedisDto 를 사용으로 수정
mikekks Dec 12, 2024
34c4da7
🚨feat(CoLeaderReader): CoLeaders 가 아닌 CoLeadersRedisDto 반환으로 수정
mikekks Dec 12, 2024
e8129bd
chore(RedisConfig): objectMapper 커스텀화하여 공통적으로 사용할 수 있게 구현
mikekks Dec 12, 2024
4989a49
chore(MeetingV2ServiceImpl): 로직 변경으로 인한 수정
mikekks Dec 12, 2024
20b005d
chore(MeetingV2GetMeetingByIdResponseDto): Meeting 생성자 변경없이 하기 위해 모임 …
mikekks Dec 12, 2024
f8b966c
chore: redis 커넥션 풀 설정
mikekks Dec 12, 2024
a6219e2
chore: redis 관련 기능들만 커스텀 objectMapper 사용하도록 수정
mikekks Dec 12, 2024
76538c4
chore(UserActivityVO): UserActivityVO final 추가
mikekks Dec 12, 2024
9d9d09f
chore(RedisProperties): RedisProperties 를 따로 정의하여 사용
mikekks Dec 12, 2024
fbbab5e
fix: 잘못된 코드 원상복구
mikekks Dec 12, 2024
adbd640
chore(CoLeaderReader): readonly 추가
mikekks Dec 12, 2024
f83d172
chore(ImageUrlVO): static -> 인스턴스 메서드로 수정
mikekks Dec 12, 2024
da2333c
chore(cd): cd 트리거 수정
mikekks Dec 12, 2024
7228013
chore(UserActivityVO): 검증 로직 추가
mikekks Dec 12, 2024
0d28402
chore(CoLeadersRedisDto): 기본 생성자 추가
mikekks Dec 12, 2024
05cff92
fix: 기존 코드 복구
mikekks Dec 12, 2024
cae141e
feat(AuthV2ServiceImpl): 회원 정보 수정된 경우에 캐시 초기화
mikekks Dec 15, 2024
86ef6dc
feat(User): updateIfChanged 메서드 구현, withUserIdForRedis 메서드 구현
mikekks Dec 15, 2024
e35e447
chore(yml): 커넥션 풀 설정 제거
mikekks Dec 15, 2024
60d54ef
chore(CoLeaderRedisDto): User 생성 로직 수정
mikekks Dec 15, 2024
010b882
chore(CoLeadersRedisDto): 기본 해시맵 생성으로 변경
mikekks Dec 15, 2024
7876015
test: 파트 및 기수가 잘못됐을 경우 예외 발생
mikekks Dec 15, 2024
c82df53
fix: NPE 문제 해결
mikekks Dec 15, 2024
d93ffb0
chore: 검증로직 private 메서드화
mikekks Dec 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 67 additions & 11 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ services:
caddy.log.output: stdout
caddy.log.format: json
caddy.log.include: http.log.access.localhost
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"

swagger:
image: swaggerapi/swagger-ui
Expand All @@ -42,9 +47,23 @@ services:
labels:
caddy.route: /docs*
caddy.route.reverse_proxy: "{{ upstreams 8080 }}"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"

redis:
image: redis:alpine
container_name: redis
hostname: redis
ports:
- 6379:6379
networks:
- caddy

nestjs-green:
image: makerscrew/server:latest
image: makerscrew/server:develop
container_name: nestjs-green
ports:
- 3001:3000
Expand Down Expand Up @@ -107,17 +126,28 @@ services:
caddy.route_14.reverse_proxy: "{{ upstreams 3000 }}"

spring-green:
image: makerscrew/main:latest
image: makerscrew/main:develop
environment:
- TZ=Asia/Seoul
TZ: Asia/Seoul
container_name: spring-green
ports:
- 4001:4000
- 5556:5555
restart: unless-stopped
depends_on:
- nestjs-green
- pinpoint-agent
- redis
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "100"
networks:
- caddy
volumes:
- data-volume:/pinpoint-agent

labels:
caddy.log: "localhost"
# for Swagger spec
Expand Down Expand Up @@ -152,9 +182,17 @@ services:
caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}"
caddy.route_14: /auth/v2/*
caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}"
caddy.route_15: /8da2d7e6-72aa-4120-9e84-8f459a2584a1/*
caddy.route_15.reverse_proxy: "{{ upstreams 5555 }}"
caddy.route_16: /internal/*
caddy.route_16.reverse_proxy: "{{ upstreams 4000 }}"

nestjs-blue:
image: makerscrew/server:latest
image: makerscrew/server:develop
container_name: nestjs-blue
ports:
- 3002:3000
restart: unless-stopped
env_file:
- ./.env
environment:
Expand All @@ -170,10 +208,11 @@ services:
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_REGION=${AWS_REGION}
- JWT_SECRET=${JWT_SECRET}
container_name: nestjs-blue
ports:
- 3002:3000
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"
networks:
- caddy
labels:
Expand Down Expand Up @@ -212,17 +251,27 @@ services:
caddy.route_14.reverse_proxy: "{{ upstreams 3000 }}"

spring-blue:
image: makerscrew/main:latest
image: makerscrew/main:develop
environment:
- TZ=Asia/Seoul
TZ: Asia/Seoul
container_name: spring-blue
ports:
- 4002:4000
- 5557:5555
restart: unless-stopped
depends_on:
- nestjs-blue
- pinpoint-agent
- redis
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "100"
networks:
- caddy
volumes:
- data-volume:/pinpoint-agent
labels:
caddy.log: "localhost"
# for Swagger spec
Expand Down Expand Up @@ -257,7 +306,14 @@ services:
caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}"
caddy.route_14: /auth/v2/*
caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}"
caddy.route_15: /8da2d7e6-72aa-4120-9e84-8f459a2584a1/*
caddy.route_15.reverse_proxy: "{{ upstreams 5555 }}"
caddy.route_16: /internal/*
caddy.route_16.reverse_proxy: "{{ upstreams 4000 }}"

volumes:
data-volume:

networks:
caddy:
external: true
external: true
7 changes: 7 additions & 0 deletions main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ dependencies {

// Slack Webhook
implementation 'com.github.maricn:logback-slack-appender:1.4.0'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate6'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package org.sopt.makers.crew.main.auth.v2.service;

import java.util.Optional;

import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto;
import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto;
import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository;
import org.sopt.makers.crew.main.global.jwt.JwtTokenProvider;
import org.sopt.makers.crew.main.entity.user.User;
import org.sopt.makers.crew.main.entity.user.UserRepository;
import org.sopt.makers.crew.main.external.playground.PlaygroundService;
import org.sopt.makers.crew.main.external.playground.dto.request.PlaygroundUserRequestDto;
import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -23,42 +24,69 @@
public class AuthV2ServiceImpl implements AuthV2Service {

private final UserRepository userRepository;
private final CoLeaderRepository coLeaderRepository;

private final PlaygroundService playgroundService;
private final JwtTokenProvider jwtTokenProvider;

@Override
@Transactional
public AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto) {
PlaygroundUserResponseDto responseDto = fetchPlaygroundUser(requestDto);
User curUser = userRepository.findByOrgId(responseDto.getId())
.orElseGet(() -> signUpNewUser(responseDto));

// 플그 서버로의 요청
PlaygroundUserResponseDto responseDto = playgroundService.getUser(PlaygroundUserRequestDto.of(requestDto.authToken()));
mikekks marked this conversation as resolved.
Show resolved Hide resolved
Optional<User> user = userRepository.findByOrgId(responseDto.getId());

/**
* @note: 회원가입 경우
*
* */
if (user.isEmpty()) {
User newUser = responseDto.toEntity();
userRepository.save(newUser);

log.info("new user signup : {} {}", newUser.getId(), newUser.getName());
String accessToken = jwtTokenProvider.generateAccessToken(newUser.getId(), newUser.getName());
return AuthV2ResponseDto.of(accessToken);
if (updateUserIfChanged(curUser, responseDto)) {
clearCacheForUser(curUser.getId());
}

/**
* @note: 로그인 경우 : 기존 정보에서 변화있는 부분은 업데이트 한다.
*
* */
User curUser = user.get();
curUser.updateUser(responseDto.getName(), responseDto.getId(), responseDto.getUserActivities(),
responseDto.getProfileImage(), responseDto.getPhone());

String accessToken = jwtTokenProvider.generateAccessToken(curUser.getId(), curUser.getName());
log.info("accessToken : {}", accessToken);

log.info("Access token generated for user {}: {}", curUser.getId(), accessToken);
return AuthV2ResponseDto.of(accessToken);
}

private PlaygroundUserResponseDto fetchPlaygroundUser(AuthV2RequestDto requestDto) {
return playgroundService.getUser(PlaygroundUserRequestDto.of(requestDto.authToken()));
}

private User signUpNewUser(PlaygroundUserResponseDto responseDto) {
User newUser = responseDto.toEntity();
User savedUser = userRepository.save(newUser);
log.info("New user signup: {} {}", savedUser.getId(), savedUser.getName());
return savedUser;
}

private boolean updateUserIfChanged(User curUser, PlaygroundUserResponseDto responseDto) {
User playgroundUser = responseDto.toEntity();
boolean isUpdated = curUser.updateIfChanged(playgroundUser);

if (isUpdated) {
log.info("User updated: {}", curUser.getId());
}

return isUpdated;
}

private void clearCacheForUser(Integer userId) {
clearCacheForLeader(userId);

coLeaderRepository.findAllByUserIdWithMeeting(userId).forEach(
coLeader -> clearCacheForCoLeader(coLeader.getMeeting().getId())
);
log.info("Cache cleared for user: {}", userId);
}

@Caching(evict = {
@CacheEvict(value = "meetingLeaderCache", key = "#userId")
})
public void clearCacheForLeader(Integer userId) {

}

@Caching(evict = {
@CacheEvict(value = "coLeadersCache", key = "#meetingId")
})
public void clearCacheForCoLeader(Integer meetingId) {

}
}
mikekks marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
package org.sopt.makers.crew.main.entity.apply;

import static jakarta.persistence.GenerationType.IDENTITY;
import static jakarta.persistence.GenerationType.*;
import static org.sopt.makers.crew.main.global.exception.ErrorStatus.*;

import java.time.LocalDateTime;

import org.sopt.makers.crew.main.entity.apply.enums.ApplyStatusConverter;
import org.sopt.makers.crew.main.entity.apply.enums.ApplyTypeConverter;
import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus;
import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType;
import org.sopt.makers.crew.main.entity.common.BaseTimeEntity;
import org.sopt.makers.crew.main.entity.meeting.Meeting;
import org.sopt.makers.crew.main.entity.user.User;
import org.sopt.makers.crew.main.global.exception.BadRequestException;
import org.springframework.data.annotation.CreatedDate;

import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

import java.time.LocalDateTime;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import org.sopt.makers.crew.main.entity.common.BaseTimeEntity;
import org.sopt.makers.crew.main.global.exception.BadRequestException;
import org.sopt.makers.crew.main.entity.apply.enums.ApplyStatusConverter;
import org.sopt.makers.crew.main.entity.apply.enums.ApplyTypeConverter;
import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus;
import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType;
import org.sopt.makers.crew.main.entity.meeting.Meeting;
import org.sopt.makers.crew.main.entity.user.User;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "apply")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Apply extends BaseTimeEntity {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
Expand All @@ -16,10 +21,14 @@ public abstract class BaseTimeEntity {

@CreatedDate
@Column(name = "createdTimestamp")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime createdTimestamp;
mikekks marked this conversation as resolved.
Show resolved Hide resolved

@LastModifiedDate
@Column(name = "modifiedTimestamp")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime modifiedTimestamp;

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class CoLeader extends BaseTimeEntity {

@Builder
private CoLeader(Meeting meeting, User user) {
if (Objects.equals(meeting.getUserId(), user.getId())) {
if (meeting != null && Objects.equals(meeting.getUserId(), user.getId())) {
throw new BadRequestException(LEADER_CANNOT_BE_CO_LEADER_APPLY.getErrorCode());
}
this.meeting = meeting;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.sopt.makers.crew.main.entity.meeting;

import java.util.List;

import org.sopt.makers.crew.main.meeting.v2.dto.redis.CoLeadersRedisDto;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CoLeaderReader {
private final CoLeaderRepository coLeaderRepository;

@Cacheable(value = "coLeadersCache", key = "#meetingId")
public CoLeadersRedisDto getCoLeaders(Integer meetingId) {
List<CoLeader> coLeaders = coLeaderRepository.findAllByMeetingId(meetingId);

return new CoLeadersRedisDto(coLeaders);
}
}
Loading
Loading