Skip to content

[박성조] sprint12#128

Open
castle-bird wants to merge 7 commits into
codeit-bootcamp-spring:박성조from
castle-bird:박성조-sprint12

Hidden character warning

The head ref may contain hidden characters: "\ubc15\uc131\uc870-sprint12"
Open

[박성조] sprint12#128
castle-bird wants to merge 7 commits into
codeit-bootcamp-spring:박성조from
castle-bird:박성조-sprint12

Conversation

@castle-bird
Copy link
Copy Markdown
Collaborator

요구사항

기본

  • 기본 항목

심화

  • 심화 항목

멘토에게

  • 기본 기능밖에 구현하지 못했습니다..

@castle-bird castle-bird requested a review from Highjune May 4, 2026 10:02
@castle-bird castle-bird self-assigned this May 4, 2026
@castle-bird castle-bird added the 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다. label May 4, 2026
Copy link
Copy Markdown
Collaborator

@Highjune Highjune left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

성조님 고생하셨어요~!

이번 구현은 꽤 어려웠어서 기본 기능 구현으로 충분히 잘하셨구요.
그런데 심화 구현은 꼭꼭 하셔야 해요. 마지막 스프린트니까 천천히 하셔도 되구요.
아키텍쳐 그려가면서 해보셔요. 사실 이번 스프린트는 심화 구현이 하이라이트이거든요.

커멘트 남겨둔 것 보시고 기본 + 심화까지 한번 완료해보세요~!

수고하셨습니다!

public void handleMessage(MessageCreatedEvent event) {
MessageDto message = event.getData();
String destination = "/sub/channels.%s.messages".formatted(message.channelId());
messagingTemplate.convertAndSend(destination, message);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

성조님 ~! 아직 심화기능 구현하진 않으셨겠지만,

현재에서 멈추게 되면 어떤 문제인지, 이번 스프린트가 어떤 부분이 중요한 것인지를 위한 리뷰를 살짝만 남길게요~!


위처럼, 그냥 그대로 messagingTemplate.convertAndSend(destination, message); 만 해버리면 문제가 생깁니다.

대부분의 실시간 이벤트가 로컬 전송이라 인스턴스가 여러개일 떄 다른 인스턴스의 클리어은트는 이벤트를 못 받는 구조가 되어버려요.

TransactionalEventListener 는 같은 jvm 내에서만 동작하는 거잖아요.
인스턴스 3개 띄우면
유저 A -> app1 에서만 채널 생성되고 app1 의 sse/소켓 클라이언트만 이벤트 받고, app2, app3 클라이언트는 아무것도 못 받게 되거든요~!

중간에 다른 매개체가 필요한 것 같아요. kafka 같은 것 통해서 모든 인스턴스에 브로드캐스트하는 방식으로 추가 구현이 필요할 것 같습니다~!

jwtRegistry.invalidateJwtInformationByUserId(userId);
});
}
Arrays.stream(request.getCookies())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null check 제거로 npe 위험이 생기게 되었습니다!

kafkaTemplate.send("discodeit.".concat(event.getClass().getSimpleName()), payload);
} catch (JsonProcessingException e) {
log.error("Failed to send event to Kafka", e);
throw new RuntimeException(e);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전에는 로그만 남겼는데, 이제 예외를 던지게 되었는데요.

이 리스너는 @TransactionalEventListener에서 호출될 수 있는데, 트랜잭션 커밋 후 예외가 발생하게 되면,
이미 DB에는 커밋이 되었는데, 호출자에게 예외가 전파가 되어요. 즉 씽크가 안 맞는거죠. 양쪽이 다른겁니다.

유저 입장에서는 "메시지 생성 실패"처럼 보이지만, 실제로는 DB에 저장돼 있어요.

그래서 여기에서 사실, JsonProcessingException은 코드 버그이므로, 로그 + 모니터링 알림이 더 적합할 것 같아요!

Comment thread docker-compose.yml

backend:
image: discodeit-backend:local
container_name: discodeit-backend
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 나중에 분산 구조로 변환할 떄 수정을 해야할 것 같아요!
containier_name 이 들어가게 되면 replicas 가 불가능하거든요~!

data.compute(receiverId, (key, emitters) -> {
List<SseEmitter> next = emitters == null ? new ArrayList<>() : new ArrayList<>(emitters);
next.add(emitter);
return next;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매번 새로운 list 를 생성하는데요.

아래 remove 에서도요.

compute 내에서 복사본을 만드는 건 thread-safety를 위한 의도적인 선택인 것 맞죠!? (읽기 시 락 없이 안전하니까요). 하지만 접속자가 많을 때 add/remove가 빈번하면 GC 압박이 생길 수 있어요. 힙 메모리 관련 공부해보세요~! 그래서 여기에서는 CopyOnWriteArrayList를 쓰면 동일한 로직인데 내부 구현이 그것을 그대로 구현하고 있어요. 공부해보세요~!

보통 읽기가 빈번하고, 쓰기그 드물 때 사용하면 좋습니다. 바로 이번처럼요~!

읽기 빈번 - 이벤트 발행할 떄마다 emitter 순회하면서 전송
쓰기 드묾 - 유저가 연결/해제할 때만(분당 수회)

- SSE 메시지 모델 추가: `SseMessage` 클래스 도입 및 브로드캐스트/수신자 필터링 기능 구현
- WebSocket 및 SSE 채널 인증: `JwtAuthenticationChannelInterceptor` 도입
- Kafka 이벤트 리스너 구현: `SseRequiredTopicListener`에서 주요 이벤트 처리 및 SSE 전송
- Redis 캐싱 통합: `RedisConfig` 및 `RedisLockProvider` 추가
- 이벤트 기반 설계 확장: BinaryContent, Channel, User 관련 이벤트 처리 개선 및 SSE 연동
- 기본 RedisLockProvider로 분산 락 구현
- 불필요한 코드를 정리하고 이벤트 생성 및 처리 로직을 효율화
- `application-dev.yaml`에서 환경변수 참조 제거 및 정적 값으로 대체
- SSE 타임아웃 설정 추가 (`application.yaml`)
- `SseEmitterRepository`, `SseMessageRepository`, `SseService` 삭제
- 사용되지 않는 SSE 관련 코드 정리 및 의존성 제거
- `application-dev.yaml`에서 민감 정보 값 표시 방식 수정
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants