-
Notifications
You must be signed in to change notification settings - Fork 79
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
[1단계 - DB 복제와 캐시] 산초(나영서) 미션 제출합니다. #73
base: nayonsoso
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
산초 안녕하세요!
마지막 미션에서 뵙게 되어 반가워요!!
복제 지연 해결 방법을 다양하게 분석해주셨네요!
생각한 방법들과 이유, 장단점 잘 파악해주신 것 같아요 👍
결론은 발생 빈도의 관점에서 DataSource 를 WRITER 로 선택하는 방안을 채택해 주신 것 같아요 ㅎㅎ
이렇게 생각한 과정에서 궁금한 점이 있어요!
결정에 앞서 쿠폰 조회 서비스의 사용 대상에 대해 언급해 주셨는데요,
산초는 쿠폰 생성 및 조회 서비스는 내부 직원 혹은 사용자 중에 누가 사용할 것이라고 생각하셨나요?
지금의 getCoupon()
메서드는 둘 중에 어느 대상을 고려해서 만든 건지, 그리고 그렇게 생각하신 이유가 궁금해요!
이번 미션 잘 부탁드리고, 미션과 프로젝트 모두 화이티입니다!
|
||
public class DataSourceRouter extends AbstractRoutingDataSource { | ||
|
||
private static final ThreadLocal<DataSourceType> currentDataSource = new ThreadLocal<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThreadLocal 로 구현 잘 해주신 것 같습니다 👍
혹시 ThreadLocal 을 사용해야 하는 이유를 설명해줄 수 있나요?
import lombok.Getter; | ||
|
||
@Getter | ||
public class Coupon { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
도메인도 객체지향적으로 잘 분리해주셨네요!!
@AllArgsConstructor | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
public class CouponEntity { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엔티티와 도메인을 분리해주셨네요! 저는 도메인과 엔티티를 혼용하다보니 필드 구성과 스키마 설계도 좀 쉽지 않았는데, 배워갑니다!!
if (couponEntity.isEmpty()) { | ||
DataSourceRouter.setDataSourceType(DataSourceType.WRITER); | ||
couponEntity = couponRepository.findById(id); | ||
} | ||
if(couponEntity.isEmpty()) { | ||
throw new IllegalArgumentException("존재하지 않는 쿠폰입니다."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isEmpty() 로 CouponEntity 객체를 두 번 검사하게 되는데,
처음 검사는 복제지연을 피하기 위한 검사이고, 다음 검사가 정말 쿠폰이 존재하는지에 대한 검사네요!
이렇게 두 번 같은 확인을 하도록 설계하신 이유가 있는지 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가적으로, Service 가 DataSourceType 과 DataSourceRouter 를 직접적으로 알게 되는데, 서비스가 많은 책임을 가지고 있는 것 처럼 느껴지는 것 같아요!
이에 대해 어떻게 생각하시나요?
private Optional<CouponEntity> getCouponById(Long id) { | ||
try { | ||
return couponRepository.findById(id); | ||
} catch (SQLGrammarException ex) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQLGrammerException
을 캐치해서 Optional.empty()
를 반환하도록 구현한 의도가 궁금합니다!
안녕하세요 무빈😊
우테코의 마지막 미션에서 만나게 되어 정말 반갑습니다.
제가 고민한 내용들을 간단히 적어봤어요~
(고민한 내용들을 그대로 드러내고자 '~다' 체를 사용했습니다 ㅎㅎ)
문제 상황 분석
쿠폰을 생성하고 나서 바로 그 쿠폰을 조회하려 할 때 발생하는 문제이다.
즉, writer 에 저장이 완료되었더라도, 읽어올 때 reader에서 읽어오기 때문에 아직 복제가 안되면 문제가 발생한다.
만약 이 서비스가 내부 직원들을 대상으로 하는 것이라면, 성능이 크게 중요하지 않을 수도 있다.
하지만 대상이 사용자들이라면? 성능이 중요하다.
그리고 쿠폰이라는 도메인은 돈과 관련되어있으므로 정합성도 못지 않게 중요하다.
굳이 우위를 가리자면, '정합성 > 성능'일 것이다.
따라서 가장 반드시 지켜야 하는 조건은
❗️저장된 쿠폰이 조회가 되는 것 & 저장되지 않은 쿠폰은 조회되지 않는 것❗️
이다.
복제 지연 해결 방법
1/ 클라이언트에서 or 서버에서 일정 시간 대기 후 읽어오는 방법
2/ writer 에 저장 후 값을 캐싱해고, read 시 캐시를 읽는 방법
3/ reader 에 없다면 writer 에서 읽어오는 방법
결론
완벽한 방법은 없다🙀
그나마 고르자면 2번 또는 3번일 것인데, 그 빈도를 생각해보자면..
2번은 모든 조회 요청에 대해 remote 캐시를 조회한다.
3번은 'reader 에 없는 경우'에만 write 에 접근힌다.
따라서 발생 빈도가 적은 3번의 방법이 적절해보인다👍
하지만 복제 지연 시간이 유의미하게 길어진다면, 2번이 더 적절한 방법일 수도 있다.