-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
강의 OPEN시 수업 정보와 수강생 정보의 Redis 캐싱 및 활용 구현 #62 #66
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# for free
to join this conversation on GitHub.
Already have an account?
# to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What is this Pull Request about? 💬
학생이 출석 시도시, 수강 신청 데이터가 많으면 데이터를 가져오는데에 엄청난 시간이 소요되는 것을 확인했다,
(1000건 동시요청시 10만건에선 아예 600건 이상 타임아웃)
그래서 인덱싱을 통해 해결을 시도하니 약 6.5초만에 전부 성공했다 -> #57
그러나, 6.5초도 여전히 유저 입장에선 여전히 느리다고 생각되어, 다양한 커버링 인덱싱을 고려해보았다. 하지만, 인덱싱을 위해 저장해야 하는 데이터가 아주 많았다. Lecture 객체 전체와 Enrollment 객체의 대부분을 저장해야 했다.
그런데, 잘 생각해보니, 캐싱이 필요한 상황은 강사가 수업을 연 이후였다. 이에, 강의를 열 때 수강생 정보를 캐싱을 해보기로 했다
1. 계기
우리 서비스에서 짧은 시간에 많은 트래픽이 몰리는 기능은 "출석" 기능이다.
출석은 아래와 같은 과정을 통해 이루어진다.
이떄 강사의 작업을 제외하자면, 학생들은 한번의 출석에서 여러번의 DB I/O를 발생시킨다.
3번 과정은 이미 구현할 당시 부터 Redis에 캐싱해 둠으로써 Read과정을 줄였다.
문제는 2번 과정이였다. 수강신청 건수가 10만건이고, 1000명이 동시에 요청할 떄 (동시성 프레임워크) 30초 이상이 소요되었는데, 기본 Time Out 시간에 걸린 것이다. 그리고 총 650건 실패했다.
이 문제를 해결하기 위해 인덱싱을 걸으니, 6.5초대로 개선되었다. 수강신청 건수가 10만건이 아닌 수천건 밖에 안 될 때도 3초 이상이 걸렸다.
이런 응답시간은 유저의 입장에서 답답할 수 밖에 없다.
그래서 캐싱을 도입했다.
2. 캐싱 대상
수업을 OPEN할 시, 원래는 출석번호만 캐싱했다. 이젠, 아래와 같은 데이터를 캐싱한다
추가적으로, 강사가 학생의 수강신청을 승인해 줄 때도, 이미 강의가 열려있다면 캐싱해준다.
3. 캐싱 이후의 출석 과정
4. 수업 정보는 같은 트랜잭션, 학생 정보는 비동기 자식 트랜잭션으로 구현한 이유
수업 정보는 수업을 여는 트랜잭션과 같은 트랜잭션 하에 진행된다.
왜냐하면 수업 열기가 실패하는 경우 캐싱은 필요 없고, 수업 정보엔 출석 번호가 포함되는데, 이 출석 번호가 없다면 출석할 수 없기 때문에, 캐싱이 실패했을 때도 수업 열기가 함께 실패해야 하기 때문이다.
그런데, 학생 정보는 Event를 이용해 캐싱했다.
그 이유는 이 방법이 내 의도와 가장 잘 맞으면서도 간단했기 때문이다.
보통 의존성을 끊는 방법으로 Event가 쓰이곤 하는데, 내가 쓴 이유는 수강생 캐싱이 아래와 같이 동작하길 바랬기 때문이다.
강사는 강의를 Open하고 출석 번호를 발급 받는 것이 API를 호출하면서 기대하는 바이기 때문이다
그래서 Event를 이용하면 위 구현이 쉬웠다.
Event를 발행해 처리하고, 비동기적으로 처리되게 하였으며, 새로운 트랜잭션을 만들되, 강의 Open이 실패하는 경우 이루어지지 않도록 쉽게 처리할 수 있었다.
5. 구현과 설정에 대한 고민
5.1 현재 동시성 문제가 발생하는 부분이 있다
나는 학생별 강의 데이터를 저장할 때, 하나인 경우 String 이후엔 Set으로 구현하고 있었다. 이는 실수가 없는 한 학생이 동시에 출석 가능한 강의는 1개이고 (대부분이 값을 1개만 가짐) 메모리를 아껴야 하기 때문에 이렇게 구현했는데, Type별로 가져오는 방식이 달라, 타입을 확인하고 가져오는 사이에 타입이 변하여 에러가 발생했다. 1만건당 1 ~ 2건 꼴이지만, 누락되면 사실상 학생은 그 강의에 아예 출석이 불가능하므로, 누락되선 안 된다. 이에 대한 고민은 여기 새로운 이슈에서 -> #65
5.2 eviction 정책은 volitle ttl 로 한다.
앱에서 다양한 종류의 캐싱을 하지만, 가장 큰 용량을 차지하는 것은 이 수강생 정보이다. 문제는 수강생 정보는 사용되지 않은 데이터 일수록 오히려 사용 확률이 높다는 점이다 이는 기존의 캐싱 정책들과 반대이다
왜냐하면 한번 출석한 학생은 오히려 또 출석할 일이 없고, 출석하지 않은 학생이 새로 출석할 가능성이 높기 떄문이다. 그래서 레디스에 이 상황에 100% 맞는 적절한 정책이 없다. (보통은 자주 쓰이는 데이터를 더 살려 놓는다)
그나마 적절해 보이는 것이 volitle-ttl이다. (정책이 아예 없는 경우엔 새로운 저장이 안 된다.) 그래서 volitle-ttl을 선택했다.
문제는 앱에서 캐싱하는 다양한 데이터 중에 캐싱된 데이터가 수강생 데이터가 제일 사라져도 문제가 없는 데이터란 것이고, 다른 데이터들은 사라지는 경우 문제가 된다.
이제까지 사라져선 안 되는 데이터들은 제한 시간이 실제 비즈니스적으로도 존재하는 데이터들이여서 비즈니스적인 제한 시간과 redis exprire time을 똑같이 맞췄다. 이젠, 이 데이터들의 exprire time을 2배로 늘리고, 따로 유효 시간을 저장하는 방식을 고려해봤다.
그러니까 위험을 대비해 저장 용량을 늘리게 되는 것이다.
이 방법은 기존의 일반적인 캐싱과 같은 상황에선 비싼 메모리 자원을 더 소모하는 것임으로 문제가 되겠지만, 우리 서비스에선 이런 방식으로 추가 메모리를 사용해 저장하는 자료가 적음으로 고려할만해 보인다. 만약 용량이 그리 넉넉하지 않았더라면, 유효시간을 대폭 줄이는 방법으로 바꿨을 것이다. -> 또다른 이슈가 필요하다
3. 강의를 OPEN 중에 새로운 학생이 승인될 수 있다
(출석 가능한 학생이 동적으로 생겨날 수 있음)
-> 강사가 학생을 승인했을 때, 강의가 이미 열려있다면 레디스에 캐싱한다.
4. redis transaction support
redis template에서 관련 설정을 true로 설정해야 /@transactional 을 붙인 메서드에서 RedisTemplate를 사용할 때, 우리가 평소에 DB를 사용할 때와 같이 동작한다. (하나의 트랜잭션으로 묶이고, 롤백시 롤백 등)
6. 적용 결과
7. 추가된 새로운 객체들