본문 바로가기

개발일지/TIL

[230810] 2개 이상의 기능을 1개의 SSE로 처리할 때 문제

SSE 구조 문제

✔ 문제 1

💢 알림을 보내야 할 주체를 특정하기가 어렵다.
     ➡ 어떤 유저에게 알림을 보내줄 것인가?
     ➡ 포스트를 보고 있는 사람을 어떻게 특정할 것인가?

✅ 유저와 포스트를 특정할 수 있도록 ID를 부여하도록 했다. Ssemitter와 묶기 위해 객체를 하나 만들었다.

 

✔ ID와 Ssemitter가 묶인 클래스

 

public record CustomSseEmitter (
        Long id,
        SseEmitter sseEmitter
) {}

 

✔ 문제 2

💢 알림과 실시간 입찰 가격 조회를 하나의 SSE를 사용할 경우 문제가 발생한다는 것을 알았다.
     ➡ 필요하지 않은 이벤트 전송이 일어난다. [ 경매를 참여하지 않은 클라이언트에게도 이벤트가 날아가는 경우가 생김 ]

💬 유저와 데이터가 많아질수록 SSE 하나 만드는 비용보다 필요하지 않은 이벤트를 전송하는 비용이 더 많이 든다고 판단 

✅ 알림과 실시간 입찰 가격을 분리하여 2개의 SSE를 생성하고 관리하기로 했다.
     ➡ 분리하여 관리하기 위해 connect, event type이 필요하고 생각
     ➡ 2개의 SSE를 관리하기 위한 컨테이너와 connect, event type을 적용하기 위해 service 코드 수정

 

✔ Connect type 정의

 

public enum SseConnect {
    NOTIFICATION, BID
}

 

✔ Event type 정의

 

public enum SseEvent {
    BID_PRICE_UPDATE, NOTICE_BID_END, NOTICE_BID_PRICE_UPDATE, NOTICE_BID_START_BEFORE
}

 

✔ Service 수정

 

public class SseService {
    private static final Long DEFAULT_TIMEOUT = 1000 * 60 * 30L;
    private static final List<CustomSseEmitter> noticeEmitters = new ArrayList<>();
    private static final List<CustomSseEmitter> bidEmitters = new ArrayList<>();

    public SseEmitter connect(SseConnect type, Long id, String lastEventId) {
        SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT);
        List<CustomSseEmitter> currentEmitters = type == SseConnect.NOTIFICATION 
                                                 ? noticeEmitters 
                                                 : bidEmitters;
        String initMessage = type == SseConnect.NOTIFICATION 
                             ? "connect user : " + id 
                             : "connect idea : " + id;

        CustomSseEmitter customSseEmitter = new CustomSseEmitter(id, emitter);
        currentEmitters.add(customSseEmitter);

        emitter.onCompletion(() -> currentEmitters.remove(customSseEmitter));
        emitter.onTimeout(() -> currentEmitters.remove(customSseEmitter));

        try {
            emitter.send(initMessage);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return emitter;
    }

    public void send(SseConnect type, SseEvent event, Long id, Object data) {
        List<CustomSseEmitter> currentEmitters = type == SseConnect.NOTIFICATION 
                                                 ? noticeEmitters 
                                                 : bidEmitters;
        List<CustomSseEmitter> sendEmitters = currentEmitters.stream()
                .filter((customSseEmitter) -> customSseEmitter.id() == id).toList();

        for(CustomSseEmitter sendEmitter : sendEmitters) {
            try {
                sendEmitter.sseEmitter()
                        .send(SseEmitter.event()
                                .name(event.toString())
                                .data(data));
            } catch (IOException exception) {
                currentEmitters.remove(sendEmitter);
                throw new RuntimeException("연결 오류!");
            }
        }
    }
}