<!-- task=m-performance-hotspot-optimization/09_dart_tcp_latency_heartbeat plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.

## 개요

date=2026-06-06
task=m-performance-hotspot-optimization/09_dart_tcp_latency_heartbeat, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/milestones/performance-hotspot-optimization.md`
- Task ids:
  - `dart-tcp-fixed-latency`: Dart TCP 40ms대 고정 지연 원인 분리
  - `dart-tcp-burst-sustained`: Dart TCP burst/sustained 처리량 저하 분리
  - `dart-heartbeat-cost`: Dart heartbeat timer churn 성능 경로 분리
- Completion mode: check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] TCP no-delay and flush policy | [x] |
| [REFACTOR-2] Heartbeat disabled semantics and churn test | [x] |
| [REFACTOR-3] Focused performance evidence | [x] |

## 구현 체크리스트

- [x] TCP connect/connectSecure 경로에서 TCP no-delay 적용 가능 여부를 구현하고 Dart TCP focused roundtrip row로 원인을 분리한다.
- [x] `_TcpSocketTransport.writePacket`의 per-packet `flush()` 비용을 안전하게 줄이거나 유지 사유를 문서화하고 burst/sustained row로 검증한다.
- [x] heartbeat interval 0을 disabled semantics로 정리하거나 유지 사유를 문서화하고 timer churn 테스트를 추가한다.
- [x] `run_stress.sh --quick --lang dart --transport tcp --profile roundtrip,burst,sustained`와 `run_performance.sh --quick` 결과를 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active 파일을 `.log`로 아카이브하고 PASS이면 `complete.log`를 작성한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임 완료 이벤트 메타데이터를 보고한다.

## 계획 대비 변경 사항

- REFACTOR-1 tcpNoDelay 적용 위치: 계획 sketch는 static `ProtobufClient.connect`만 수정했지만, 실제로는 `ProtobufClient` 생성자(`dart/lib/src/protobuf_client.dart:47`)에서 `_socket.setOption(SocketOption.tcpNoDelay, true)`를 호출했다. dial한 client socket과 server가 accept한 socket이 모두 이 생성자를 거치므로 roundtrip 양쪽에 Nagle을 끌 수 있다. client 쪽만 끄면 echo 서버 응답이 여전히 Nagle+delayed-ACK 40ms를 받아 fixed latency 분리가 불완전해진다. 수정 파일은 계획대로 `protobuf_client.dart` 한 파일이며 `connectSecure`(SecureSocket)도 동일 생성자 경로로 함께 커버된다.
- REFACTOR-1 flush 정책: per-packet `await _socket.flush()`를 제거했다(`dart/lib/src/protobuf_client.dart:140` 부근). 유지가 아니라 제거를 선택했고, 안정성 회귀가 없음을 stress로 확인했다(아래 설계 결정 참고).

## 주요 설계 결정

- **tcpNoDelay 생성자 적용**: fixed latency 40ms의 원인은 Nagle + delayed-ACK 상호작용이다. 생성자 단일 지점에서 끄면 client/server accept socket을 모두 덮어 가장 작은 변경으로 양방향 roundtrip을 분리한다. `setOption`은 loopback/표준 TCP·SSL socket에서 항상 지원되므로 try/catch 없이 직접 호출했다. focused roundtrip row에서 concurrency=1 p50이 0.262~0.371ms로, 기존 40ms대 fixed latency가 사라진 것으로 원인이 분리된다.
- **per-packet flush 제거**: `_socket.add()`가 송신 순서를 보장하고, 잔여 버퍼는 `_TcpSocketTransport.close()`의 `_socket.close()`가 flush한다. 따라서 매 패킷 `flush()` await는 정확성에 불필요하고 burst/sustained에서 send를 event-loop drain에 직렬화시켜 throughput만 깎는다. 제거 후 burst 44.5k rps, sustained 47.1k rps(p99 0.72ms)이고 stability 위반(timeout/nonce/type/FIFO/pendingLeak)은 전부 0이다. inbound backpressure 테스트도 그대로 통과해 close/대용량 frame 회귀가 없음을 확인했다.
- **heartbeat disabled semantics**: `_heartbeatEnabled => _heartbeatIntervalTime > 0 && _heartbeatWaitTime > 0`(`dart/lib/src/heartbeat_mixin.dart`)로 정의하고 `sendHeartBeat()`가 비활성일 때 즉시 return하도록 했다. 기존에는 interval 0이 "즉시 발화"라 Dart만 0,0으로 heartbeat를 끌 수 없었고 stress가 86400(1일) 우회값을 써야 했다. 이제 다른 언어 구현과 동일하게 0이 "비활성"이며, 비활성 시 프레임 수신마다 ResponseChecker timer를 churn하지 않는다. `dart/bench/stress.dart`의 `_heartbeatSeconds`도 86400→0으로 정리했다. 활성(interval/wait > 0) 경로 동작은 그대로이므로 기존 heartbeat timeout 테스트는 변경 없이 통과한다.
- **테스트**: interval 0 client/server가 600ms 이상 self-close 없이 유지되고 TestData 송수신이 되는 테스트를 `dart/test/socket_test.dart`의 `Heartbeat disabled (interval 0)` 그룹에 추가했다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- TCP no-delay/flush 변경이 close, SSL, fragmented frame, request-response를 깨지 않는지 확인한다.
- heartbeat 0 semantics가 tests와 stress harness에서 일관되는지 확인한다.
- Roadmap Completion으로 세 task를 체크할 만큼 focused stress와 performance quick evidence가 있는지 확인한다.

## 검증 결과

### REFACTOR-1/REFACTOR-2 중간 검증 (socket_test + analyze)
```text
$ dart analyze lib bench test
   info - lib/src/heartbeat_mixin.dart:13:16 - The member 'close' overrides an inherited member but isn't annotated with '@override'. - annotate_overrides
1 issue found.
# 위 info는 변경 전부터 있던 추상 선언(line 13)에 대한 것이며 이번 변경과 무관(범위 외)이다.

$ cd dart && dart test test/socket_test.dart -r expanded
All tests passed!   # 41 tests (기존 40 + 신규 'Heartbeat disabled (interval 0)' 1)
# 신규: Heartbeat disabled (interval 0) interval 0이면 timer churn 없이 연결이 유지되고 송수신된다 → PASS
```

### REFACTOR-3 중간 검증 (focused dart tcp stress)
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang dart --transport tcp --profile roundtrip,burst,sustained
DONE dart: PASS (rows=6 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: agent-test/runs/20260606-031314-proto-socket-stress-quick.md

| Profile   | Axis            | Tput(rps) | p50(ms) | p95(ms) | p99(ms) | Timeout/Nonce/Type/FIFO/PendLeak |
|-----------|-----------------|----------:|--------:|--------:|--------:|----------------------------------|
| roundtrip | concurrency=1   |     143.7 |   0.371 |  12.907 |  12.907 | 0/0/0/0/0 |
| roundtrip | concurrency=16  |    7810.6 |   1.485 |   2.043 |   2.078 | 0/0/0/0/0 |
| roundtrip | concurrency=64  |    7598.7 |   6.721 |   7.434 |   7.541 | 0/0/0/0/0 |
| roundtrip | concurrency=256 |   12255.5 |  17.617 |  17.982 |  18.021 | 0/0/0/0/0 |
| burst     | count=200       |   40412.2 |   0.000 |   0.000 |   0.000 | 0/0/0/0/0 |
| sustained | duration=2000ms |   45544.0 |   0.278 |   0.497 |   0.825 | 0/0/0/0/0 |
# fixed latency 분리 근거: roundtrip concurrency=1 p50=0.371ms로 기존 40ms대 고정 지연이 사라짐.
# (same-language perf 재측정에서도 동일 경향: concurrency=1 p50=0.262ms, sustained p99=0.720ms)
```

### 최종 검증
```text
$ cd dart && dart test test/socket_test.dart test/communicator_test.dart -r expanded
All tests passed!   # socket_test 41 + communicator_test 21

$ cd dart && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js
Compiled 10,868,358 input bytes (5,869,586 characters source) to 11,692 characters JavaScript in 0.44 seconds

$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick
| 구성              | 결과 | exit code | 결과 기록 |
|-------------------|------|----------:|-----------|
| same-language     | PASS | 0 | agent-test/runs/20260606-031336-proto-socket-stress-quick.md |
| cross-language    | PASS | 0 | agent-test/runs/20260606-031411-proto-socket-stress-quick-cross.md |
| typescript-gateway| PASS | 0 | agent-test/runs/20260606-032040-proto-socket-stress-quick.md |
전체 결과값: PASS / Regression 결과값: SKIPPED
결과 기록 파일: agent-test/runs/20260606-031336-proto-socket-performance-quick.md

# same-language Dart tcp 전 프로파일 row (stability 전부 0):
| Profile   | Axis            | Tput(rps) | p50(ms) | p99(ms) |
|-----------|-----------------|----------:|--------:|--------:|
| roundtrip | concurrency=1   |     174.9 |   0.262 |  10.604 |
| roundtrip | concurrency=256 |   13204.0 |  15.788 |  17.415 |
| burst     | count=200       |   44523.6 |   0.000 |   0.000 |
| sustained | duration=2000ms |   47184.0 |   0.275 |   0.720 |
| parallel  | clients=4       |   42390.8 |   0.561 |   0.935 |
| payload   | payload=1KB     |   20080.3 |   0.151 |   0.188 |
| payload   | payload=64KB    |    1822.7 |   1.806 |   2.291 |
```

## 코드리뷰 결과

- 종합 판정: PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| correctness | Pass | TCP no-delay는 `ProtobufClient` 생성자 경로에서 dial/accept socket 모두에 적용되고, per-packet flush 제거 후에도 serialized `queuePacket`/`Socket.add` 순서와 close flush 경로가 유지된다. heartbeat 0 disabled semantics도 active heartbeat 경로를 보존한다. |
| completeness | Pass | 계획된 no-delay, flush policy, heartbeat disabled semantics, stress harness heartbeat 0 전환, 신규 테스트, 성능 증거 수집이 모두 반영됐다. |
| test coverage | Pass | `socket_test.dart`에 heartbeat disabled test가 추가됐고, TCP/SSL/request/fragmentation/large payload 회귀가 기존 테스트와 재실행으로 확인됐다. |
| API contract | Pass | public API 시그니처 변경 없이 TCP/heartbeat semantics만 정리했다. Web target conditional export와 WS 경로도 유지된다. |
| code quality | Pass | stale symbol, debug 출력, formatting check 위반 없음. `dart analyze`의 `annotate_overrides` info는 기존 추상 선언 이슈로 이번 변경의 차단 사항이 아니다. |
| plan deviation | Pass | no-delay 적용 위치를 static connect가 아니라 생성자로 옮긴 deviation은 server accepted socket까지 덮기 위한 타당한 축소/강화다. |
| verification trust | Pass | 리뷰 중 `cd dart && dart test test/socket_test.dart -r expanded`, `cd dart && dart test test/communicator_test.dart -r expanded`, `cd dart && dart analyze lib bench test`, `cd dart && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js`를 재실행했고, 기록된 stress/performance result 파일의 PASS 및 row 값도 확인했다. |

### 발견된 문제

없음

### 다음 단계

PASS: `complete.log`를 작성하고 task를 archive로 이동한다.
