Skip to content

fix(db): OSIV 를 끈다 — SSE 스트림이 커넥션을 5분씩 쥐고 있었다 - #313

Merged
dldnsgkr merged 1 commit into
developfrom
unhak/disable-osiv
Sep 7, 2026
Merged

fix(db): OSIV 를 끈다 — SSE 스트림이 커넥션을 5분씩 쥐고 있었다#313
dldnsgkr merged 1 commit into
developfrom
unhak/disable-osiv

Conversation

@dldnsgkr

@dldnsgkr dldnsgkr commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

#312 로 넣은 누수 탐지가 실제로 범인을 잡았다. 그 후속 수정.

누수 탐지가 찍은 것 (dev, 07:09)

Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@...
  at TaskStore.getOwned(...)
  at AgentEventStreamService.open(AgentEventStreamService.java:31)
  at AgentController.streamTaskEvents(AgentController.java:205)

원인

open-in-view 는 EntityManager(따라서 JDBC 커넥션)를 요청이 끝날 때까지 붙들어 둔다. 일반 REST 요청은 수십 ms 라 티가 안 나지만, SSE 요청의 수명은 스트림 수명이다 — STREAM_TIMEOUT_MS 가 5분이므로 커넥션 하나가 5분씩 묶인다.

스트림이 여러 개 열리면(브라우저 탭 · 재연결 · 다중 사용자) 그만큼 풀이 마른다.

앞선 조사가 한 줄기로 설명된다

이것이 auth/refresh 500 조사에서 만난 풀 고갈의 근본 원인이다:

  • 01:52 부터 22분간 Connection is not available (total=10, active=10, idle=0) 이 이어지고 재기동으로만 풀림 → 스트림이 계속 다시 열리니 재기동 전에는 안 풀린다
  • 첫 희생자가 agent-event 스레드 → 스트림 자신이 가장 먼저 굶는다
  • 그 사이 auth/refresh · approvals 가 500, 스케줄 워커 전부 실패 → DB 커넥션이 필요한 모든 것이 함께 죽는다

스프링이 기동 때마다 경고를 남기고 있었다. 아무도 안 봤다:

WARN JpaBaseConfiguration$JpaWebConfiguration :
     spring.jpa.open-in-view is enabled by default.

끄는 것이 안전한 이유

이 프로젝트는 JPA 연관관계를 하나도 쓰지 않는다.

$ grep -rn "@ManyToOne\|@OneToMany\|@OneToOne\|@ManyToMany\|FetchType" --include="*.java" src/main/java | wc -l
0

엔티티는 전부 toDomain() 으로 도메인 객체에 옮겨 담아 나간다(매퍼 25개). 트랜잭션 밖에서 지연 로딩될 대상 자체가 없다.

#312 는 그대로 둔다

풀 20 · 누수 탐지는 완화이자 진단 장치다. 이번처럼 다음 누수도 이름으로 잡아 준다 — 그게 이 수정을 가능하게 한 것이기도 하다.

검증

전체 1326개 통과. dev 배포 후 SSE 를 실제로 열어 leak detection WARN 이 사라지는지 확인 필요.

🤖 Generated with Claude Code

https://claude.ai/code/session_013y8USoCXTsRTATAhy88M93

#312 의 누수 탐지가 범인을 이름으로 찍었다(2026-09-08 07:09 dev):

  Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@...
    at TaskStore.getOwned(...)
    at AgentEventStreamService.open(AgentEventStreamService.java:31)
    at AgentController.streamTaskEvents(AgentController.java:205)

open-in-view 는 EntityManager(따라서 JDBC 커넥션)를 요청이 끝날 때까지 붙들어 둔다. 일반
REST 요청은 수십 ms 라 티가 안 나지만, SSE 요청의 수명은 스트림 수명이다 —
STREAM_TIMEOUT_MS 가 5분이므로 커넥션 하나가 5분씩 묶인다. 스트림이 여러 개 열리면
(브라우저 탭·재연결·다중 사용자) 그만큼 풀이 마른다.

이것이 앞서 조사한 풀 고갈의 근본 원인이다. 01:52 부터 22분간
"Connection is not available (total=10, active=10, idle=0)" 이 이어지고 재기동으로만
풀렸던 것, 첫 희생자가 agent-event 스레드였던 것, 그 사이 auth/refresh 와 approvals 가
500 을 내고 스케줄 워커가 전부 실패했던 것이 한 줄기로 설명된다. 스프링이 기동 때마다
경고를 남기고 있었는데(JpaWebConfiguration: "open-in-view is enabled by default") 아무도
안 봤다.

끄는 것이 안전한 이유: 이 프로젝트는 JPA 연관관계를 하나도 쓰지 않는다(@manytoone ·
@onetomany · @OnetoOne · @manytomany 전부 0건). 엔티티는 전부 toDomain() 으로 도메인
객체에 옮겨 담아 나가므로(25개 매퍼) 트랜잭션 밖에서 지연 로딩될 대상 자체가 없다.

#312(풀 20 · 누수 탐지)는 그대로 둔다. 그건 완화이자 진단 장치이고, 이번처럼 다음 누수도
이름으로 잡아 준다.

검증: 전체 1326개 통과. dev 배포 후 SSE 를 실제로 열어 leak detection WARN 이 사라지는지
확인 필요.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013y8USoCXTsRTATAhy88M93
@dldnsgkr
dldnsgkr merged commit accee89 into develop Sep 7, 2026
1 check passed
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.

1 participant