<!-- task=m-performance-hotspot-optimization/14+13_kotlin_ws_transport_hardening plan=0 tag=REFACTOR -->

# Plan - REFACTOR

## 이 파일을 읽는 구현 에이전트에게

13번 subtask의 `complete.log`가 없으면 구현을 시작하지 마세요. 구현 완료 후 `CODE_REVIEW-local-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active 파일은 그대로 두세요. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌이 막으면 리뷰 stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈추세요. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 마세요.

## 배경

Kotlin WS는 기존 full baseline에서 concurrency 16/64/256 p99가 40ms대이고 1KB payload도 p99 42ms대다. TCP는 `socket.tcpNoDelay = true`로 안정적이지만 Java-WebSocket 서버 path에는 같은 설정이 없다. 1MB WS write path는 `ByteString.of(*bytes)`가 Kotlin spread-copy를 만들 수 있어 heap peak와 payload latency에 불리하다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 직접 사용자 프롬프트는 금지이며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유합니다.

## Roadmap Targets

- Milestone: `agent-roadmap/milestones/performance-hotspot-optimization.md`
- Task ids:
  - `kotlin-ws-delay`: Kotlin WS roundtrip/payload 경로의 고정 지연을 분석하고 병목을 줄인다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/milestones/performance-hotspot-optimization.md`
- `agent-test/local/rules.md`
- `agent-test/local/proto-socket-performance-baseline.md`
- `agent-test/local/kotlin-smoke.md`
- `agent-test/runs/20260603-062724-proto-socket-stress-full.md`
- `agent-ops/rules/project/domain/kotlin/rules.md`
- `kotlin/build.gradle.kts`
- `kotlin/settings.gradle.kts`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/Communicator.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/BaseClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/TcpClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/TcpServer.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/WsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TcpTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt`
- `kotlin/crosstest/stress.kt`

### 테스트 환경 규칙

`test_env=local`을 사용한다. `agent-test/local/kotlin-smoke.md`에 따라 Kotlin 구현 변경은 `cd kotlin && ./gradlew test`가 필수다. 성능/latency 변경이므로 `agent-test/local/proto-socket-performance-baseline.md`의 `run_performance.sh --quick`도 적용한다. focused 확인은 `run_stress.sh --quick --lang kotlin --transport tcp,ws --profile roundtrip,payload`를 사용한다.

### 테스트 커버리지 공백

기존 WS tests는 기능 회귀를 잡지만 `tcpNoDelay`, `ByteString.of(*bytes)` spread-copy 제거, p99 개선은 직접 검증하지 않는다. 새 또는 보강된 large payload WS request-response test로 payload correctness를 지키고, 성능 판단은 stress/performance 결과 파일로 한다.

### 심볼 참조

renamed/removed symbol 없음.

### 분할 판단

split 정책을 평가했다. 이 subtask는 `14+13_kotlin_ws_transport_hardening`이며 predecessor `13_kotlin_ws_diagnostics` complete.log가 필요하다. 현재 작성 시점에는 active predecessor path `agent-task/m-performance-hotspot-optimization/13_kotlin_ws_diagnostics/`가 있고 complete.log는 아직 없다. `15+14_kotlin_ws_reference_guard`는 이 subtask PASS 후 실행한다.

### 범위 결정 근거

수정 범위는 Kotlin WS transport와 Kotlin WS tests로 제한한다. `TcpClient`는 reference guard 대상이므로 이 단계에서 수정하지 않는다. `Communicator` 수신 queue를 제거하거나 public API를 바꾸는 변경은 13번 metrics가 명확한 queue 병목을 보여줄 때만 수행하며, 그런 경우에도 단일 serialized receive path와 FIFO를 유지한다.

### 빌드 등급

`local-G08`: Kotlin 단일 도메인이지만 WebSocket transport, socket option, payload copy, concurrency 성능 회귀를 다뤄 높은 local grade가 필요하다.

## 구현 체크리스트

- [ ] 13번 complete.log를 확인하고 diagnostics 결과에서 주요 병목 후보를 기록한다.
- [ ] Java-WebSocket server accepted socket에 `TCP_NODELAY`를 적용한다.
- [ ] OkHttp WS send path에서 Kotlin spread-copy를 피하는 `ByteString.of(bytes, 0, bytes.size)`로 바꾼다.
- [ ] 필요한 경우에만 13번 metrics 근거로 WS receive staging을 조정하고 FIFO/overflow semantics를 유지한다.
- [ ] Kotlin WS large payload correctness test를 추가하거나 보강한다.
- [ ] `cd kotlin && ./gradlew test`, focused stress, `run_performance.sh --quick`을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 plan은 directory name의 `+13`에 따라 `agent-task/m-performance-hotspot-optimization/13_kotlin_ws_diagnostics/complete.log`가 있어야 구현할 수 있다. 추가 의존성은 없다.

### [REFACTOR-1] Enable No-Delay For WS Server Sockets

문제: [TcpClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/TcpClient.kt:36)는 `socket.tcpNoDelay = true`를 적용하지만 [WsServer.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt:25)는 Java-WebSocket 서버 socket에 같은 설정을 하지 않는다. 기존 baseline은 Kotlin WS concurrency 16/64/256 p99가 40ms대다.

해결 방법: `WsServer`에서 `onConnect(key: SelectionKey)`를 override해 `SocketChannel.socket().tcpNoDelay = true`를 설정하고 `super.onConnect(key)`를 반환한다. 필요한 import는 `java.nio.channels.SelectionKey`, `java.nio.channels.SocketChannel`이다.

Before:

```kotlin
// WsServer.kt:25
) : WebSocketServer(InetSocketAddress(host, port)) {
    private val clientByConn = ConcurrentHashMap<WebSocket, WsClient>()
```

After:

```kotlin
) : WebSocketServer(InetSocketAddress(host, port)) {
    override fun onConnect(key: SelectionKey): Boolean {
        (key.channel() as? SocketChannel)?.socket()?.tcpNoDelay = true
        return super.onConnect(key)
    }
```

수정 파일 및 체크리스트:

- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`: imports 추가, `onConnect` override 추가.
- [ ] WSS path에서도 server start/open이 유지되는지 `TlsWsTest`로 확인.

테스트 작성: 별도 socket option unit test는 Java-WebSocket 내부 accepted channel 접근이 제한되어 실익이 낮다. 기존 WS/WSS tests와 performance stress로 검증한다.

중간 검증:

```bash
cd kotlin && ./gradlew test
```

### [REFACTOR-2] Remove WS Write Spread Copy

문제: [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:128)는 `ByteString.of(*bytes)`를 사용한다. Kotlin spread operator는 1MB payload에서 추가 배열 copy를 만들 수 있고, 기존 Kotlin WS 1MB heap peak는 약 668.3MB다.

해결 방법: Okio API의 `ByteString.of(bytes, 0, bytes.size)`를 사용해 spread-copy를 제거한다. 이 변경은 payload bytes 내용을 바꾸지 않는다.

Before:

```kotlin
// WsClient.kt:128
override fun send(bytes: ByteArray): Boolean =
    socketRef.get()?.send(ByteString.of(*bytes)) ?: false
```

After:

```kotlin
override fun send(bytes: ByteArray): Boolean =
    socketRef.get()?.send(ByteString.of(bytes, 0, bytes.size)) ?: false
```

수정 파일 및 체크리스트:

- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`: `ByteString.of` overload 변경.
- [ ] `kotlin/src/test/kotlin/com/tokilabs/proto_socket/WsTest.kt`: 64KB 이상의 WS request-response correctness test 추가 또는 기존 concurrent test 보강.

테스트 작성: 작성한다. `WsTest`에 `testWsLargePayloadRequestResponse`를 추가해 large payload echo가 손상되지 않는지 확인한다.

중간 검증:

```bash
cd kotlin && ./gradlew test
```

### [REFACTOR-3] Preserve Receive Ordering While Applying Diagnostics

문제: [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:58)는 nonblocking `receiveQueue.trySend`, [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:66)는 단일 receive loop, [Communicator.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/Communicator.kt:357)는 bounded inbound queue를 사용한다. 이 구조는 FIFO에 유리하지만 queue hop 비용도 후보이므로 13번 metrics 없이는 구조를 제거하지 않는다.

해결 방법: 13번 diagnostics가 staging backlog/drop/copy가 지배적이지 않음을 보이면 receive path는 유지한다. metrics가 queue hop 병목을 명확히 보이면 단일 serialized actor 안에서 `communicator.onReceivedFrame`을 호출하는 구조로만 조정하고, per-frame `launch`는 사용하지 않는다. overflow disconnect와 gateway submit order tests는 유지 또는 보강한다.

수정 파일 및 체크리스트:

- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`: 13번 metrics 근거에 따라 receive path 유지 또는 최소 조정.
- [ ] `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt`: `testWsIngressSubmitsRawFramesToAttachedGateway`, overflow test 유지.

테스트 작성: receive path를 조정한 경우에만 regression test를 추가한다. 유지한 경우 기존 tests로 충분하며 code review에 13번 metrics 근거를 기록한다.

중간 검증:

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang kotlin --transport tcp,ws --profile roundtrip,payload
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt` | REFACTOR-1 |
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt` | REFACTOR-2, REFACTOR-3 |
| `kotlin/src/test/kotlin/com/tokilabs/proto_socket/WsTest.kt` | REFACTOR-2 |
| `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt` | REFACTOR-3 |

## 최종 검증

```bash
cd kotlin && ./gradlew test
```

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang kotlin --transport tcp,ws --profile roundtrip,payload
```

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick
```

기대 결과: Kotlin tests PASS, focused stress에서 Kotlin TCP/WS roundtrip/payload stability counters 0, performance quick 전체 결과 PASS 또는 환경/기존 WARN이 명확히 기록된다. Kotlin WS p99/throughput이 개선되지 않으면 13번 metrics와 함께 남은 병목 원인을 code review에 문서화해야 한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
