<!-- task=m-performance-hotspot-optimization/07_dart_tcp_1mb_payload plan=0 tag=PERF_DART_PAYLOAD -->

# Dart TCP 1MB Payload Plan

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

이 작업은 구현 후 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채워야 완료된다. 검증 명령을 실행하고 실제 stdout/stderr 또는 저장된 결과 파일 경로를 기록한 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다.

구현 중 사용자만 결정할 수 있는 제품 선택, 사용자 소유 외부 환경, secret, 범위 충돌이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 증거, 실행한 명령/출력, 자동 후속 불가 이유, 재개 조건을 적고 중단한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

현재 payload Epic의 `[dart-tcp-1mb]`는 Dart TCP 1MB payload row의 p99 또는 throughput 개선, 또는 명확한 병목 문서화를 요구한다. idle full record `agent-test/runs/20260605-115943-proto-socket-performance-full.md`에서 Dart TCP `payload=1MB`는 throughput `8.8 rps`, p99 `907.446ms`, stability hard gate 0 PASS다. TCP receive/write path에는 large frame에서 복사를 늘릴 수 있는 `addAll`, `sublist`, `List<int>.from`, spread write와 per-packet `flush()`가 함께 있다.

## 사용자 리뷰 요청 흐름

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

## Roadmap Targets

- Milestone: `agent-roadmap/milestones/performance-hotspot-optimization.md`
- Task ids:
  - `dart-tcp-1mb`: Dart TCP 1MB payload 경로를 분석하고 p99 latency 또는 throughput을 개선한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/project/domain/dart/rules.md`
- `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/dart-smoke.md`
- `agent-test/local/proto-socket-full-matrix.md`
- `agent-test/runs/20260605-115943-proto-socket-performance-full.md`
- `agent-test/runs/20260603-062724-proto-socket-stress-full.md`
- `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh`
- `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh`
- `dart/lib/src/protobuf_client.dart`
- `dart/lib/src/protobuf_server.dart`
- `dart/lib/src/communicator.dart`
- `dart/lib/src/transport.dart`
- `dart/bench/stress.dart`
- `dart/test/socket_test.dart`
- `dart/test/communicator_test.dart`
- `dart/pubspec.yaml`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었다. payload/latency/throughput 작업은 `agent-test/local/proto-socket-performance-baseline.md`로 라우팅된다.
- `dart/**` 변경이므로 `agent-test/local/dart-smoke.md`도 읽었다.
- compatibility 영향 확인을 위해 `agent-test/local/proto-socket-full-matrix.md`도 읽었다.
- 적용 명령:
  - Dart smoke: `cd dart && dart pub get && dart test && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js`
  - targeted payload: `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --full --lang dart --transport tcp --profile payload`
  - final performance quick: `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick`
- full matrix는 protocol/API/cross-language 영향이 확인되거나 reviewer가 요구할 때 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all`로 확장한다.
- 도구 확인: `dart`, `node`, `npm`, `bash`, `typescript/node_modules/.bin/tsx`가 존재했고, `run_stress.sh`/`run_performance.sh`는 `bash -n`을 통과했다.

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

- 현재 `dart/test/socket_test.dart`는 TCP send/receive, request-response, oversized length close를 검증하지만 1MB payload roundtrip과 chunk-fragmented TCP frame을 직접 검증하지 않는다.
- `dart/bench/stress.dart`의 payload profile은 full 모드에서 1MB row를 기록하지만 성능 수치 검증은 환경 의존이다. 기능 회귀는 새 unit/integration test로 잡고, p99/throughput 판정은 stress record로 남긴다.
- write path에서 per-packet `flush()`를 제거하거나 변경하면 backpressure semantics가 달라질 수 있어 request-response 및 burst/payload stress가 모두 필요하다.

### 심볼 참조

- none. 계획된 변경은 public symbol rename/removal 없이 Dart TCP 내부 transport/frame buffer에 한정한다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- shared task group: `agent-task/m-performance-hotspot-optimization/`
- sibling subtasks:
  - `07_dart_tcp_1mb_payload`: Dart TCP 1MB payload. 독립 작업, depends_on=[].
  - `08_ts_ws_1mb_payload`: TypeScript WS 1MB payload. 독립 작업, depends_on=[].
- 두 payload Task는 Dart/TypeScript ownership boundary와 검증 명령이 다르므로 하나의 plan으로 묶지 않는다.
- 이 subtask 자체는 Dart TCP 1MB의 한 ownership boundary 안에서 frame read/write와 tests가 함께 움직인다. API/foundation과 call-site rollout이 분리되지 않고 public API를 바꾸지 않으므로 단일 plan으로 둔다.

### 범위 결정 근거

- 이 plan은 `[dart-tcp-1mb]`만 완료 대상으로 삼는다.
- `dart-tcp-fixed-latency`, `dart-tcp-burst-sustained`, `dart-tcp-buffer-copy`, `dart-heartbeat-cost`, isolate gateway 제품화 Task는 별도 Epic/Task라 Roadmap Targets에 넣지 않는다.
- `proto/**`, generated packets, Go/Kotlin/Python/TypeScript 코드는 수정하지 않는다.
- Dart WS path는 비교 기준으로만 사용하고, WS 성능 최적화는 이 plan의 목표가 아니다.

### 빌드 등급

- build lane: `cloud-G07`.
- review lane: `cloud-G07`.
- 근거: core TCP transport의 framing/backpressure/performance 변경이며, 1MB benchmark evidence와 stability hard gate를 함께 판단해야 한다.

## 구현 체크리스트

- [ ] Dart TCP read path에서 1MB frame의 누적/consume 복사를 줄이고, fragmented/concatenated frame을 순서대로 처리하는 regression test를 추가한다.
- [ ] Dart TCP write path에서 header+payload spread copy와 per-packet flush 비용을 분석하고, 안정성 hard gate를 유지하는 최소 변경을 적용하거나 병목 유지 사유를 문서화한다.
- [ ] `cd dart && dart pub get && dart test && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js`를 실행한다.
- [ ] `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --full --lang dart --transport tcp --profile payload`를 실행하고 Dart TCP `payload=1MB` row의 stability 0 및 p99/throughput 변화를 기록한다.
- [ ] `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick`를 실행해 project performance quick smoke가 회귀 없이 기록되는지 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [PERF_DART_PAYLOAD-1] TCP frame read copy 줄이기

#### 문제

- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:34)에서 `_arrivedData`는 growable `List<int>`다.
- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:62)는 chunk마다 `_arrivedData.addAll(data)`로 복사한다.
- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:83)는 header를 `sublist` 후 `Uint8List.fromList`로 다시 복사한다.
- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:103)는 packet payload를 `sublist` 후 `List<int>.from`으로 복사하고, [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:105)는 남은 buffer를 또 `sublist`로 복사한다.
- large payload full baseline은 [20260605 performance full](/config/workspace/proto-socket/agent-test/runs/20260605-115943-proto-socket-performance-full.md:60)에서 p99 `907.446ms`, throughput `8.8 rps`다.

#### 해결 방법

현재 구조:

```dart
// dart/lib/src/protobuf_client.dart:34-35
int? _length = null;
late List<int> _arrivedData = [];

// dart/lib/src/protobuf_client.dart:62-70
_arrivedData.addAll(data);
if (_isParsing) return;
_isParsing = true;
_subscription?.pause();
try {
  await _parsing();
} finally {
  _isParsing = false;
  _subscription?.resume();
}

// dart/lib/src/protobuf_client.dart:83-105
final length = Uint8List.fromList(_arrivedData.sublist(0, _headerSize))
    .buffer
    .asByteData()
    .getInt32(0);
final packetBytes = List<int>.from(
    _arrivedData.sublist(_headerSize, _headerSize + _length!));
_arrivedData = _arrivedData.sublist(_headerSize + _length!);
```

구현 방향:

```dart
// 새 private helper 예시. public API로 노출하지 않는다.
class _TcpFrameBuffer {
  final Queue<Uint8List> _chunks = Queue<Uint8List>();
  int _available = 0;

  void add(Uint8List chunk) {
    if (chunk.isEmpty) return;
    _chunks.add(chunk);
    _available += chunk.length;
  }

  int? peekInt32Be();
  Uint8List? takeFrame(int length);
}
```

- `dart:collection`을 import하고 `_TcpFrameBuffer`를 `protobuf_client.dart` 안 private helper로 둔다.
- `peekInt32Be`는 첫 4 bytes만 chunk queue에서 읽고 복사하지 않는다.
- `takeFrame(length)`는 header 4 bytes와 payload `length` bytes를 consume하며 payload는 정확히 한 번만 contiguous `Uint8List`로 만든다.
- `_parsing()`은 `_length`와 `_TcpFrameBuffer.available`로 full frame 여부를 판단하고, `PacketBase.fromBuffer(packetBytes)`와 `onReceivedData` 순서는 유지한다.
- malformed length `0`, negative, `maxPacketSize` 초과 close semantics는 기존 테스트와 동일하게 유지한다.

#### 수정 파일 및 체크리스트

- [ ] `dart/lib/src/protobuf_client.dart`: `_arrivedData`를 helper 기반 buffering으로 교체한다.
- [ ] `dart/lib/src/protobuf_client.dart`: header peek, frame consume, residual chunk 처리 테스트 가능성을 위해 private helper를 작게 유지한다.
- [ ] `dart/test/socket_test.dart`: raw TCP socket으로 1MB request-response 또는 send/listener roundtrip을 검증한다.
- [ ] `dart/test/socket_test.dart`: 한 PacketBase frame을 작은 chunks로 나누어 보내도 단일 TestData가 수신되는 regression test를 추가한다.

#### 테스트 작성

- 작성한다.
- `dart/test/socket_test.dart`에 `TCP receives fragmented large frame without reordering or truncation` 계열 테스트를 추가한다.
- raw socket으로 header와 payload chunks를 나눠 보내고, server listener가 payload message를 정확히 한 번 받는지 검증한다.
- 필요하면 1MB full test는 시간을 고려해 integration test에서 `timeout: Timeout(...)`을 명시한다.

#### 중간 검증

```bash
cd dart && dart test test/socket_test.dart -r expanded
```

기대 결과: 추가된 fragmented/large TCP tests 포함 PASS.

### [PERF_DART_PAYLOAD-2] TCP write copy와 flush 비용 분리

#### 문제

- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:130)는 모든 PacketBase를 `writeToBuffer()`로 직렬화한다.
- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:132)는 header를 만든 뒤 [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:134)에서 `[...header, ...baseBytes]`로 header+payload 전체를 새 growable list로 복사한다.
- [protobuf_client.dart](/config/workspace/proto-socket/dart/lib/src/protobuf_client.dart:135)는 packet마다 `flush()`를 기다린다. 1MB payload와 burst/sustained TCP에서 비용을 키울 수 있지만, write backpressure semantics도 함께 바뀔 수 있다.

#### 해결 방법

현재 구조:

```dart
// dart/lib/src/protobuf_client.dart:130-135
Future<void> writePacket(PacketBase base) async {
  final baseBytes = base.writeToBuffer();
  final header = Uint8List(_headerSize)
    ..buffer.asByteData().setInt32(0, baseBytes.length);
  _socket.add([...header, ...baseBytes]);
  await _socket.flush();
}
```

구현 방향:

```dart
Future<void> writePacket(PacketBase base) async {
  final baseBytes = base.writeToBuffer();
  if (baseBytes.length > maxPacketSize) {
    throw StateError('packet exceeds maximum size');
  }
  final header = Uint8List(_headerSize)
    ..buffer.asByteData().setInt32(0, baseBytes.length);
  _socket.add(header);
  _socket.add(baseBytes);
  await _socket.flush(); // 먼저 copy 제거만 적용한다.
}
```

- 1차 변경은 spread copy 제거와 max size write guard만 적용한다.
- `flush()` 제거는 별도 A/B로 판단한다. 제거 시 `queuePacket` completion timing이 달라지므로 Dart unit, targeted full payload, quick performance가 모두 PASS이고 p99/throughput이 개선될 때만 유지한다.
- `flush()`를 유지해도 row가 개선되지 않으면 결과 문서에 “copy 제거만으로는 1MB 병목이 유지됨”을 명확히 기록하고 남은 후보를 follow-up으로 남긴다.

#### 수정 파일 및 체크리스트

- [ ] `dart/lib/src/protobuf_client.dart`: `_TcpSocketTransport.writePacket`에서 spread copy를 제거한다.
- [ ] `dart/lib/src/protobuf_client.dart`: write-side `maxPacketSize` guard를 추가한다.
- [ ] `dart/test/socket_test.dart`: write guard 또는 large payload roundtrip으로 header/payload split write가 정상 동작함을 검증한다.

#### 테스트 작성

- 작성한다.
- 기존 oversized length test는 inbound only이므로 outbound guard 또는 large roundtrip test 중 하나를 추가한다.
- public API로 64MiB 초과 PacketBase를 만들기 어렵다면 `_TcpSocketTransport`가 private인 점을 고려해 1MB roundtrip functional test로 대체하고, max guard는 stress/analysis evidence로 기록한다.

#### 중간 검증

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --full --lang dart --transport tcp --profile payload
```

기대 결과: Dart TCP payload 1KB/64KB/1MB rows가 PASS, stability violations 0. 1MB row는 `agent-test/runs/20260605-115943-proto-socket-performance-full.md`의 throughput `8.8 rps` 또는 p99 `907.446ms` 대비 개선되거나, 개선되지 않은 원인을 review에 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `dart/lib/src/protobuf_client.dart` | PERF_DART_PAYLOAD-1, PERF_DART_PAYLOAD-2 |
| `dart/test/socket_test.dart` | PERF_DART_PAYLOAD-1, PERF_DART_PAYLOAD-2 |

## 최종 검증

```bash
cd dart && dart pub get && dart test && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js
```

기대 결과: 종료 코드 0, 실패한 Dart test 없음, JS compile 산출물 생성.

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --full --lang dart --transport tcp --profile payload
```

기대 결과: 결과 기록 파일 생성, Dart TCP payload rows PASS, `payload=1MB` row stability counters all 0, p99 또는 throughput 개선 여부 기록.

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

기대 결과: performance quick 결과 기록 파일 생성, same-language/cross-language/typescript-gateway 구성요소가 실행 범위에 포함되고 새 Dart 변경으로 인한 stability FAIL 없음. Kotlin deferred 등 기존 마일스톤 수용 항목이 나타나면 기존 수용 근거와 구분해 기록한다.

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