Skip to content

feat: WebSocket 성능 최적화#257

Merged
so-myoung merged 1 commit into
mainfrom
김소명
Mar 20, 2026

Hidden character warning

The head ref may contain hidden characters: "\uae40\uc18c\uba85"
Merged

feat: WebSocket 성능 최적화#257
so-myoung merged 1 commit into
mainfrom
김소명

Conversation

@so-myoung

Copy link
Copy Markdown
Collaborator

이슈 번호

작업 내용

성능 최적화

  • Inbound/Outbound 채널에 전용 스레드 풀 구성: 코어 풀 크기 4, 최대 풀 크기 16, 큐 용량 1000으로 설정하여
    동시 메시지 처리 능력 향상
  • 메시지 크기 및 버퍼 제한 설정: 메시지 크기 64KB, 전송 버퍼 512KB로 제한하여 메모리 사용 최적화
  • 전송 타임아웃 설정: 메시지 전송 제한 시간 15초, 첫 메시지 대기 60초로 설정하여 비정상 연결 조기 차단
  • Simple Broker 하트비트 구성: 10초 간격 하트비트로 연결 상태 모니터링 및 불필요한 연결 정리
  • 하트비트 전용 ThreadPoolTaskScheduler 추가: 풀 크기 2로 하트비트 처리 성능 개선

보안 강화

  • CORS 설정 개선: 와일드카드(*)에서 구체적인 allowedOrigins로 변경하여 보안 강화
  • 인증 실패 시 메시지 거부: 토큰 없는 STOMP 프레임을 null 반환으로 차단 (기존에는 통과시킴)
  • 세션 기반 인증 캐싱: Authentication 객체를 WebSocket 세션에 저장하여 매 메시지마다 JWT 검증하는 오버헤드
    제거

코드 구조 개선

  • WebSocket 설정 상수 중앙화: WebSocketConfig의 모든 상수를 WebSocketConstants로 이동하여 중앙 관리
  • 상수 클래스 패턴 적용: WebSocketConstants에 private 생성자 추가하여 인스턴스화 방지
  • 인증 로직 단순화: StompAuthChannelInterceptor의 조건문 분리로 가독성 향상

예외 처리 및 모니터링

  • WebSocket 예외 핸들러 추가: ChatWebSocketExceptionHandler로 모든 WebSocket 예외를 일관되게 처리하고
    사용자에게 적절한 에러 응답 반환
  • 세션 이벤트 리스너 추가: WebSocketSessionEventListener로 CONNECT/CONNECTED/SUBSCRIBE/DISCONNECT 이벤트
    로깅하여 디버깅 및 모니터링 강화
  • 세션 정보 로깅 개선: sessionId, user, destination 정보를 포함한 상세 로그 기록

@so-myoung so-myoung merged commit ccdbf1f into main Mar 20, 2026
1 check failed
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 WebSocket 통신의 전반적인 성능, 보안 및 안정성을 크게 향상시키는 것을 목표로 합니다. 전용 스레드 풀 구성, 메시지 제한 설정, 하트비트 활성화 등을 통해 성능을 최적화하고, CORS 설정 강화 및 세션 기반 인증 캐싱을 통해 보안을 강화했습니다. 또한, WebSocket 관련 설정을 중앙화하고 인증 로직을 개선하여 코드 구조를 명확히 했으며, 포괄적인 예외 처리기와 세션 이벤트 로깅을 추가하여 시스템 모니터링 및 디버깅 기능을 강화했습니다.

Highlights

  • 성능 최적화: 인바운드/아웃바운드 채널에 전용 스레드 풀을 구성하여 동시 메시지 처리 능력을 향상시켰습니다.
  • 성능 최적화: 메시지 크기(64KB) 및 전송 버퍼(512KB) 제한을 설정하여 메모리 사용을 최적화했습니다.
  • 성능 최적화: 메시지 전송 타임아웃(15초) 및 첫 메시지 대기 시간(60초)을 설정하여 비정상 연결을 조기 차단합니다.
  • 성능 최적화: Simple Broker 하트비트(10초 간격)를 구성하여 연결 상태를 모니터링하고 불필요한 연결을 정리합니다.
  • 성능 최적화: 하트비트 처리를 위한 전용 ThreadPoolTaskScheduler를 추가하여 성능을 개선했습니다.
  • 보안 강화: CORS 설정을 와일드카드에서 구체적인 허용 오리진으로 변경하여 보안을 강화했습니다.
  • 보안 강화: 인증 토큰이 없는 STOMP 프레임을 거부하도록 변경하여 보안 취약점을 제거했습니다.
  • 보안 강화: WebSocket 세션에 Authentication 객체를 캐싱하여 매 메시지마다 JWT 검증하는 오버헤드를 제거했습니다.
  • 코드 구조 개선: WebSocket 관련 모든 설정을 WebSocketConstants 클래스로 이동하여 중앙 집중식으로 관리합니다.
  • 코드 구조 개선: WebSocketConstants에 private 생성자를 추가하여 인스턴스화를 방지하는 상수 클래스 패턴을 적용했습니다.
  • 코드 구조 개선: StompAuthChannelInterceptor의 인증 로직 조건문을 분리하여 가독성을 향상시켰습니다.
  • 예외 처리 및 모니터링: ChatWebSocketExceptionHandler를 추가하여 모든 WebSocket 예외를 일관되게 처리하고 사용자에게 적절한 에러 응답을 반환합니다.
  • 예외 처리 및 모니터링: WebSocketSessionEventListener를 추가하여 CONNECT, CONNECTED, SUBSCRIBE, DISCONNECT 이벤트를 로깅하여 디버깅 및 모니터링을 강화했습니다.
  • 예외 처리 및 모니터링: 세션 ID, 사용자, 목적지 정보를 포함한 상세 로그를 기록하도록 개선했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

이번 PR은 WebSocket 성능 최적화, 보안 강화, 코드 구조 개선 등 여러 중요한 개선 사항을 포함하고 있습니다. 전반적으로 변경 사항은 매우 긍정적이며, 특히 인증 캐싱, 전용 스레드 풀 구성, 설정 상수화 등은 시스템의 안정성과 유지보수성을 크게 향상시킬 것입니다. 몇 가지 추가적인 개선을 위해 코드 중복 제거, 예외 처리, 상수화와 관련된 세 가지 리뷰 의견을 남겼습니다. 제안된 내용을 반영하면 코드가 더욱 견고해질 것입니다.

Comment on lines +75 to +77
} catch (Exception ignored) {
return null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

catch (Exception ignored) 블록에서 예외를 무시하고 있습니다. 레포지토리 스타일 가이드 53번 라인("실패를 숨기지 않는다: 의미 없는 catch 후 무시 금지")에 따라, 예외를 무시하는 것은 잠재적인 버그를 찾기 어렵게 만들 수 있습니다. 페이로드 역직렬화 실패는 정상적인 흐름에서 발생할 수 있으므로, 디버깅을 위해 최소한 debug 레벨로 로그를 남기는 것이 좋습니다.

Suggested change
} catch (Exception ignored) {
return null;
}
} catch (Exception e) {
log.debug("Failed to deserialize WebSocket message payload.", e);
return null;
}
References
  1. 의미 없는 catch 후 예외를 무시하는 것을 금지하고 있습니다. 현재 코드에서 예외를 무시하고 있어 잠재적인 문제를 파악하기 어렵습니다. (link)

Comment on lines +77 to +78
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("ws-broker-heartbeat-");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

webSocketBrokerTaskScheduler 빈 설정에서 2"ws-broker-heartbeat-"와 같은 매직 넘버와 매직 스트링이 사용되었습니다. 스타일 가이드 26, 80, 139번 라인에 따라, 이러한 값들은 WebSocketConstants 클래스에 상수로 정의하여 관리하는 것이 가독성과 유지보수성 측면에서 더 좋습니다.

참고: WebSocketConstantsBROKER_HEARTBEAT_POOL_SIZEBROKER_HEARTBEAT_THREAD_PREFIX 상수를 추가해야 합니다.

Suggested change
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("ws-broker-heartbeat-");
scheduler.setPoolSize(WebSocketConstants.BROKER_HEARTBEAT_POOL_SIZE);
scheduler.setThreadNamePrefix(WebSocketConstants.BROKER_HEARTBEAT_THREAD_PREFIX);
References
  1. 매직넘버/문자열은 상수화하고 의미 있는 이름을 부여해야 합니다. 현재 코드에 하드코딩된 값이 있어 이를 상수로 추출하는 것이 좋습니다. (link)

Comment on lines +14 to +50
public class WebSocketSessionEventListener {

@EventListener
public void onSessionConnect(SessionConnectEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
log.info("[WebSocket] CONNECT sessionId={}, user={}, destination={}",
accessor.getSessionId(),
accessor.getUser() != null ? accessor.getUser().getName() : null,
accessor.getDestination());
}

@EventListener
public void onSessionConnected(SessionConnectedEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
log.info("[WebSocket] CONNECTED sessionId={}, user={}",
accessor.getSessionId(),
accessor.getUser() != null ? accessor.getUser().getName() : null);
}

@EventListener
public void onSessionSubscribe(SessionSubscribeEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
log.info("[WebSocket] SUBSCRIBE sessionId={}, user={}, destination={}",
accessor.getSessionId(),
accessor.getUser() != null ? accessor.getUser().getName() : null,
accessor.getDestination());
}

@EventListener
public void onSessionDisconnect(SessionDisconnectEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
log.info("[WebSocket] DISCONNECT sessionId={}, user={}, closeStatus={}",
accessor.getSessionId(),
accessor.getUser() != null ? accessor.getUser().getName() : null,
event.getCloseStatus());
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

onSessionConnect, onSessionConnected, onSessionSubscribe, onSessionDisconnect 각 메소드에서 사용자 이름을 가져오는 로직 (accessor.getUser() != null ? accessor.getUser().getName() : null)이 중복되고 있습니다. 스타일 가이드 13번 라인(DRY 원칙)에 따라, 이 로직을 별도의 private 헬퍼 메소드로 추출하여 중복을 제거하고 코드의 가독성을 높일 수 있습니다.

아래와 같이 헬퍼 메소드를 추가하고 각 이벤트 리스너에서 호출하는 것을 제안합니다.

private String getUserName(StompHeaderAccessor accessor) {
    return java.util.Optional.ofNullable(accessor.getUser())
                             .map(java.security.Principal::getName)
                             .orElse(null);
}

적용 예시:

@EventListener
public void onSessionConnect(SessionConnectEvent event) {
    StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
    log.info("[WebSocket] CONNECT sessionId={}, user={}, destination={}",
            accessor.getSessionId(),
            getUserName(accessor),
            accessor.getDestination());
}
References
  1. 반복되는 로직은 중복을 제거해야 합니다. 현재 여러 메소드에 걸쳐 사용자 이름을 가져오는 로직이 반복되고 있어 이를 공통화할 필요가 있습니다. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 웹소켓 최적화

1 participant