test(perf): 성능 기준선 검증 표준을 추가한다

성능 측정을 cross-lang matrix와 분리하고, gateway 단일 신뢰 경로와 언어별 병목 개선 순서를 마일스톤에 반영한다.
This commit is contained in:
toki 2026-06-03 21:59:51 +09:00
parent 4eacd9a881
commit 3a7e41861d
6 changed files with 645 additions and 20 deletions

View file

@ -35,6 +35,7 @@ Proto Socket의 proto schema sync와 지원 언어 전체 테스트 상태를
- “proto 동기화 검사”, “프로토 sync” 요청은 `proto`로 실행한다.
- “언어간 크로스 테스트”, “크로스 테스트”, “통신 테스트” 요청은 사용자가 유닛 테스트도 요구하지 않는 한 `cross`로 실행한다.
- “유닛 테스트”, “동일 언어 테스트” 요청은 `unit`으로 실행한다.
- “성능 측정”, “성능 기준선”, “performance baseline”, “regression gate” 요청은 matrix가 아니라 성능 전용 shell을 실행한다.
2. **스크립트 실행**
- 저장소 루트에서 다음 중 하나를 실행한다.
@ -44,6 +45,14 @@ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --unit
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --cross
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --proto
```
- 성능 전용 검증은 cross-lang compatibility matrix와 분리해 실행한다.
```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --full
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick --baseline agent-test/runs/<baseline-record>.md
```
3. **수동 fallback**
@ -114,6 +123,7 @@ tools/check_proto_sync.sh
## 실행 결과 검증
- [ ] 요청 범위의 모든 명령이 실행되었는가
- [ ] 성능 요청이면 `run_performance.sh` 결과 파일과 regression 비교 여부를 보고했는가
- [ ] 실패 명령의 재현 명령과 로그 요약을 남겼는가
- [ ] 최종 답변에 `PASS`/`FAIL` 표가 포함되었는가
- [ ] `git status --short`로 의도치 않은 소스 변경이 없는지 확인했는가

View file

@ -0,0 +1,423 @@
#!/usr/bin/env bash
set -u
usage() {
cat <<'USAGE'
Usage: run_performance.sh [--quick|--full] [--no-cross]
[--baseline <record.md>] [--fail-on-regression]
[--throughput-drop-pct N] [--p99-worse-pct N]
Runs the Proto Socket performance validation standard separately from the
compatibility matrix. The wrapper executes run_stress.sh components, merges the
measured rows into one local record, and compares them with a prior performance
record when a baseline is supplied.
Defaults:
--quick
throughput regression warning threshold: 20%
p99 latency regression warning threshold: 25%
Recommended:
Quick performance signal:
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick
Full local baseline candidate:
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --full
Regression comparison:
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick --baseline agent-test/runs/<baseline>.md
USAGE
}
mode="quick"
run_cross=1
baseline_file=""
fail_on_regression=0
throughput_drop_pct="20"
p99_worse_pct="25"
while [ "$#" -gt 0 ]; do
case "$1" in
--quick) mode="quick" ;;
--full) mode="full" ;;
--no-cross) run_cross=0 ;;
--baseline=*) baseline_file="${1#--baseline=}" ;;
--baseline) shift; baseline_file="${1:-}" ;;
--fail-on-regression) fail_on_regression=1 ;;
--throughput-drop-pct=*) throughput_drop_pct="${1#--throughput-drop-pct=}" ;;
--throughput-drop-pct) shift; throughput_drop_pct="${1:-}" ;;
--p99-worse-pct=*) p99_worse_pct="${1#--p99-worse-pct=}" ;;
--p99-worse-pct) shift; p99_worse_pct="${1:-}" ;;
-h|--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
shift
done
case "$mode" in
quick|full) ;;
*) usage >&2; exit 2 ;;
esac
case "$throughput_drop_pct" in
''|*[!0-9.]*)
printf 'invalid --throughput-drop-pct: %s\n' "$throughput_drop_pct" >&2
exit 2
;;
esac
case "$p99_worse_pct" in
''|*[!0-9.]*)
printf 'invalid --p99-worse-pct: %s\n' "$p99_worse_pct" >&2
exit 2
;;
esac
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../../../../.." && pwd)"
stress_script="$script_dir/run_stress.sh"
run_started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
run_record_stamp="$(date -u +%Y%m%d-%H%M%S)"
record_profile="proto-socket-performance-${mode}"
record_dir="$repo_root/agent-test/runs"
record_file="$record_dir/${run_record_stamp}-${record_profile}.md"
log_dir="$(mktemp -d "${TMPDIR:-/tmp}/proto-socket-performance.XXXXXX")"
git_ref="$(git -C "$repo_root" rev-parse --short HEAD 2>/dev/null || echo "unknown")"
mkdir -p "$record_dir"
if [ -n "$baseline_file" ]; then
case "$baseline_file" in
/*) ;;
*) baseline_file="$repo_root/$baseline_file" ;;
esac
if [ ! -f "$baseline_file" ]; then
printf 'baseline file not found: %s\n' "$baseline_file" >&2
exit 2
fi
fi
run_cmd="bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --$mode"
[ "$run_cross" -eq 0 ] && run_cmd="$run_cmd --no-cross"
[ -n "$baseline_file" ] && run_cmd="$run_cmd --baseline ${baseline_file#$repo_root/}"
[ "$fail_on_regression" -eq 1 ] && run_cmd="$run_cmd --fail-on-regression"
run_cmd="$run_cmd --throughput-drop-pct $throughput_drop_pct --p99-worse-pct $p99_worse_pct"
component_names=()
component_cmds=()
component_logs=()
component_records=()
component_results=()
component_exits=()
add_component() {
component_names+=("$1")
component_cmds+=("$2")
}
same_profiles="roundtrip,burst,sustained,parallel,payload"
all_langs="dart,go,kotlin,python,typescript"
all_transports="tcp,ws"
add_component "same-language" "bash $(printf '%q' "$stress_script") --$mode --lang $all_langs --transport $all_transports --profile $same_profiles"
if [ "$run_cross" -eq 1 ]; then
add_component "cross-language" "bash $(printf '%q' "$stress_script") --$mode --lang $all_langs --transport $all_transports --profile cross"
fi
add_component "typescript-gateway" "bash $(printf '%q' "$stress_script") --$mode --lang typescript --transport $all_transports --profile gateway"
component_result_from_exit() {
local exit_code="$1" parsed="$2"
if [ -n "$parsed" ]; then
printf '%s' "$parsed"
return
fi
case "$exit_code" in
0) printf 'PASS' ;;
3) printf 'BLOCKED' ;;
4) printf 'INCOMPLETE' ;;
*) printf 'FAIL' ;;
esac
}
run_component() {
local index="$1"
local name="${component_names[$index]}"
local cmd="${component_cmds[$index]}"
local log_file="$log_dir/${name}.log"
local exit_code parsed_result record_path
printf 'RUN performance %s: %s\n' "$name" "$cmd" >&2
(
cd "$repo_root" &&
bash -c "$cmd"
) >"$log_file" 2>&1
exit_code=$?
tail -n 20 "$log_file" >&2 || true
parsed_result="$(sed -n 's/^- 전체 결과값: //p; s/^전체 결과값: //p' "$log_file" | tail -n 1)"
record_path="$(sed -n 's/^결과 기록 파일: `\(.*\)`/\1/p' "$log_file" | tail -n 1)"
if [ -n "$record_path" ]; then
case "$record_path" in
/*) ;;
*) record_path="$repo_root/$record_path" ;;
esac
fi
component_logs[$index]="$log_file"
component_records[$index]="$record_path"
component_results[$index]="$(component_result_from_exit "$exit_code" "$parsed_result")"
component_exits[$index]="$exit_code"
}
extract_measurement_rows() {
local file="$1"
[ -f "$file" ] || return 0
awk '
/^## 측정 결과/ { in_rows=1; next }
in_rows && /^## / { in_rows=0 }
in_rows && /^\| / {
if ($0 ~ /^\|---/) next
if ($0 ~ /^\| Profile /) next
print
}
' "$file"
}
build_component_summary() {
local i
printf '| 구성 | 결과 | exit code | 결과 기록 | 로그 |\n'
printf '|---|---|---:|---|---|\n'
for i in "${!component_names[@]}"; do
local record="${component_records[$i]:-}"
local log="${component_logs[$i]:-}"
[ -n "$record" ] && record="${record#$repo_root/}"
[ -n "$log" ] && log="$log"
printf '| %s | %s | %s | %s | %s |\n' \
"${component_names[$i]}" "${component_results[$i]:--}" "${component_exits[$i]:--}" \
"${record:-미기록}" "$log"
done
}
trim_awk='
function trim(s) {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
return s
}
'
compare_rows() {
local baseline_rows="$1" current_rows="$2" out_file="$3" status_file="$4"
awk -F'|' \
-v throughput_limit="$throughput_drop_pct" \
-v p99_limit="$p99_worse_pct" \
-v status_file="$status_file" \
"$trim_awk"'
function row_key() {
return profile "|" axis "|" language "|" transport "|" payload "|" clients "|" gateway
}
function parse_row() {
profile=trim($2)
axis=trim($3)
language=trim($4)
transport=trim($5)
payload=trim($6)
clients=trim($7)
request_count=trim($8)
throughput=trim($9) + 0
p99=trim($12) + 0
gateway=trim($20)
result=trim($22)
}
BEGIN {
warning_count=0
compared_count=0
missing_count=0
printf "| Key | Baseline throughput | Current throughput | Drop %% | Baseline p99 | Current p99 | Worse %% | 결과 |\n"
printf "|---|---:|---:|---:|---:|---:|---:|---|\n"
}
FNR == NR {
parse_row()
key=row_key()
base_throughput[key]=throughput
base_p99[key]=p99
next
}
{
parse_row()
key=row_key()
label=sprintf("%s / %s / %s / %s / %sB / clients=%s / requests=%s / gateway=%s", profile, axis, language, transport, payload, clients, request_count, gateway)
if (!(key in base_throughput)) {
missing_count++
printf "| %s | - | %.3f | - | - | %.3f | - | SKIPPED(no baseline) |\n", label, throughput, p99
next
}
compared_count++
drop=0
worse=0
row_status="PASS"
if (base_throughput[key] > 0) {
drop=((base_throughput[key] - throughput) / base_throughput[key]) * 100
}
if (base_p99[key] > 0) {
worse=((p99 - base_p99[key]) / base_p99[key]) * 100
}
if (drop >= throughput_limit || worse >= p99_limit) {
row_status="WARN"
warning_count++
}
printf "| %s | %.3f | %.3f | %.1f | %.3f | %.3f | %.1f | %s |\n", \
label, base_throughput[key], throughput, drop, base_p99[key], p99, worse, row_status
}
END {
if (warning_count > 0) {
print "WARN" > status_file
} else {
print "PASS" > status_file
}
printf "%d\n", compared_count > status_file ".compared"
printf "%d\n", missing_count > status_file ".missing"
printf "%d\n", warning_count > status_file ".warnings"
}
' "$baseline_rows" "$current_rows" >"$out_file"
}
for i in "${!component_names[@]}"; do
run_component "$i"
done
overall_result="PASS"
for result in "${component_results[@]}"; do
case "$result" in
FAIL) overall_result="FAIL" ;;
esac
done
if [ "$overall_result" = "PASS" ]; then
for result in "${component_results[@]}"; do
case "$result" in
INCOMPLETE) overall_result="INCOMPLETE" ;;
esac
done
fi
if [ "$overall_result" = "PASS" ]; then
for result in "${component_results[@]}"; do
case "$result" in
BLOCKED) overall_result="BLOCKED" ;;
esac
done
fi
current_rows_file="$log_dir/current.rows"
: >"$current_rows_file"
for record in "${component_records[@]}"; do
extract_measurement_rows "$record" >>"$current_rows_file"
done
regression_result="SKIPPED"
comparison_file="$log_dir/regression-comparison.md"
compared_count=0
missing_count=0
warning_count=0
if [ -n "$baseline_file" ]; then
baseline_rows_file="$log_dir/baseline.rows"
extract_measurement_rows "$baseline_file" >"$baseline_rows_file"
status_file="$log_dir/regression.status"
if compare_rows "$baseline_rows_file" "$current_rows_file" "$comparison_file" "$status_file"; then
regression_result="$(cat "$status_file" 2>/dev/null || echo "PASS")"
compared_count="$(cat "$status_file.compared" 2>/dev/null || echo "0")"
missing_count="$(cat "$status_file.missing" 2>/dev/null || echo "0")"
warning_count="$(cat "$status_file.warnings" 2>/dev/null || echo "0")"
if [ "$overall_result" = "PASS" ] && [ "$regression_result" = "WARN" ]; then
overall_result="WARN"
fi
else
regression_result="FAIL"
overall_result="FAIL"
printf '| 오류 | baseline 비교 파서 실패 | - | - | - | - | - | FAIL |\n' >"$comparison_file"
fi
fi
{
printf '%s\n' '---'
printf 'test_env: local\n'
printf 'record_type: performance-result\n'
printf 'test_profile: %s\n' "$record_profile"
printf 'created_at: %s\n' "$run_started_at"
printf 'overall_result: %s\n' "$overall_result"
printf 'regression_result: %s\n' "$regression_result"
printf '%s\n\n' '---'
printf '# %s local 결과 기록\n\n' "$record_profile"
printf '## 실행 정보\n\n'
printf -- '- 실행 일시: %s\n' "$run_started_at"
printf -- '- git ref: %s\n' "$git_ref"
printf -- '- 실행 명령: `%s`\n' "$run_cmd"
printf -- '- mode: %s\n' "$mode"
printf -- '- 로그 디렉터리: `%s`\n' "$log_dir"
if [ -n "$baseline_file" ]; then
printf -- '- baseline: `%s`\n' "${baseline_file#$repo_root/}"
else
printf -- '- baseline: 없음\n'
fi
printf -- '- throughput regression threshold: %s%% drop\n' "$throughput_drop_pct"
printf -- '- p99 regression threshold: %s%% worse\n' "$p99_worse_pct"
printf -- '- 전체 결과값: %s\n' "$overall_result"
printf -- '- regression 결과값: %s\n\n' "$regression_result"
printf '## 성능 판정 기준\n\n'
printf -- '- stability hard gate: timeout, nonce mismatch, response type mismatch, per-connection FIFO violation, pending leak, queue/gateway backlog leak가 모두 0이어야 한다.\n'
printf -- '- performance regression gate: baseline이 주어지면 같은 row key에서 throughput %s%% 이상 하락 또는 p99 latency %s%% 이상 악화를 WARN으로 기록한다.\n' "$throughput_drop_pct" "$p99_worse_pct"
printf -- '- 절대 throughput/latency는 local 환경 기준값이며, baseline 비교는 같은 host/runtime/profile에서 실행한 결과끼리 수행한다.\n'
printf -- '- `--fail-on-regression`을 지정하면 regression WARN도 non-zero exit로 처리한다.\n\n'
printf '## 구성요소 요약\n\n'
build_component_summary
printf '\n## 측정 결과\n\n'
printf '| Profile | Axis | Language | Transport | Payload(bytes) | Clients | Requests | Throughput(rps) | p50(ms) | p95(ms) | p99(ms) | Timeout | NonceMis | TypeMis | FIFOViol | PendLeak | QueueBacklog | GatewayBacklog | Gateway | Mem(MB) | 결과 |\n'
printf '|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|---:|---|\n'
cat "$current_rows_file"
printf '\n## Regression 비교\n\n'
if [ -n "$baseline_file" ]; then
printf -- '- baseline: `%s`\n' "${baseline_file#$repo_root/}"
printf -- '- compared rows: %s\n' "$compared_count"
printf -- '- missing baseline rows: %s\n' "$missing_count"
printf -- '- warning rows: %s\n\n' "$warning_count"
cat "$comparison_file"
else
printf -- '- SKIPPED: `--baseline <record.md>`가 지정되지 않았다.\n'
fi
printf '\n## git status 요약\n\n'
printf '```text\n'
git -C "$repo_root" status --short 2>/dev/null || true
printf '```\n\n'
printf '## 구성요소 로그 tail\n\n'
for i in "${!component_names[@]}"; do
printf '### %s\n\n' "${component_names[$i]}"
printf '```text\n'
tail -n 30 "${component_logs[$i]}" 2>/dev/null || true
printf '```\n\n'
done
} >"$record_file"
printf '\n=== performance 측정 결과 ===\n' >&2
build_component_summary >&2
printf '\n전체 결과값: %s\n' "$overall_result"
printf 'Regression 결과값: %s\n' "$regression_result"
printf '결과 기록 파일: `%s`\n' "${record_file#$repo_root/}"
case "$overall_result" in
PASS) exit 0 ;;
WARN)
if [ "$fail_on_regression" -eq 1 ]; then
exit 5
fi
exit 0
;;
BLOCKED) exit 3 ;;
INCOMPLETE) exit 4 ;;
*) exit 1 ;;
esac

View file

@ -36,7 +36,7 @@ Profiles (comma separated, default: per-language all):
payload payload size matrix request-response (quick: 1KB/64KB, full: 1KB/64KB/1MB)
with per-payload latency, throughput, peak memory, stability counters
cross cross-language request-response correctness / stability adapter
gateway (TypeScript only) worker_threads gateway on/off frame-ingest baseline
gateway (TypeScript only) worker_threads gateway frame-ingest/transport baseline
언어/transport별로 구현이 준비되지 않았거나 toolchain이 없으면 PASS로 기록하지 않고 BLOCKED 또는
SKIPPED(사유 포함)로 남긴다.

View file

@ -30,6 +30,7 @@ Proto Socket은 여러 언어와 플랫폼에서 일관되게 동작하는 얇
- [안정화 기준선](milestones/stability-baseline.md) - 상태: 완료; 목표: 현재 5개 언어 구현을 완성형 후보로 보고 안정성 판단과 유지 기준을 정리한다.
- [수신 큐와 처리 순서 보장](archive/phase/stability-maintenance/milestones/inbound-queue-ordering.md) - 상태: 완료; 목표: 현재 5개 언어 구현에 per-connection 수신 큐와 언어별 worker gateway 후보를 도입해 수신 처리 순서, 자동 응답 출력 순서, thread-safe한 공유 상태 접근, 대량 처리 성능 개선 가능성을 보장한다.
- [고성능 병렬 운용 기준선과 최적화](milestones/high-performance-parallel-operations.md) - 상태: [진행중]; 목표: 현재 5개 언어 구현의 병렬 운용 성능을 언어별/transport별로 측정하고 gateway, queue, worker, serialization 병목을 최적화한다.
- [언어별 성능 병목 개선](milestones/performance-hotspot-optimization.md) - 상태: [진행중]; 목표: 측정 결과에서 확인된 Dart TCP fixed latency/large payload 및 isolate receive path hardening, TypeScript WS large payload, Kotlin WS latency/slow-mix 검증 모델, TypeScript gateway `worker_threads` overhead 병목을 안정성 hard gate와 단일 mandatory receive path 원칙을 유지하면서 개선하고, Go/Python reference 기준점을 보강한다.
### 남은 native platform 포팅

View file

@ -2,7 +2,7 @@
## 목표
`수신 큐와 처리 순서 보장` Milestone에서 확보한 FIFO, nonce matching, close cleanup 안정성을 바탕으로 Dart, Go, Kotlin, Python, TypeScript 구현이 실제 고성능 병렬 운용에서 어느 정도까지 안전하고 빠르게 동작하는지 측정하고 최적화한다. 단순 PASS/FAIL 안정성 검증을 넘어, 언어별 TCP/WS 경로, payload 크기, connection 수, sustained load, gateway on/off 조건에서 병목을 식별하고 기본 정책을 정한다.
`수신 큐와 처리 순서 보장` Milestone에서 확보한 FIFO, nonce matching, close cleanup 안정성을 바탕으로 Dart, Go, Kotlin, Python, TypeScript 구현이 실제 고성능 병렬 운용에서 어느 정도까지 안전하고 빠르게 동작하는지 측정하고 최적화한다. 단순 PASS/FAIL 안정성 검증을 넘어, 언어별 TCP/WS 경로, payload 크기, connection 수, sustained load, gateway 처리 조건에서 병목을 식별하고 기본 정책을 정한다.
이 Milestone의 결과는 package registry 릴리즈나 외부 CI/CD 연결이 아니라, Git ref/tag 기반 소비자가 참고할 수 있는 로컬 성능 기준선과 최적화 근거다.
@ -24,7 +24,7 @@
- Dart, Go, Kotlin, Python, TypeScript의 same-language TCP/WS 성능 기준선을 모두 확보한다.
- 지원 가능한 크로스 언어 request-response 경로에서 병렬 운용 안정성과 주요 성능 지표를 측정한다.
- concurrency, client connection 수, payload size, burst size, sustained duration을 축으로 하는 stress/benchmark matrix를 만든다.
- gateway on/off가 실제 transport 수신 경로에서 이득인지 언어별로 판단하고, 이득이 불명확하거나 손해인 경우 안전한 fallback/default-off 정책을 명확히 한다.
- gateway가 실제 transport 수신 경로에서 제품 기본 신뢰 경로로 동작할 수 있게 순차 보장, event loop 보호, bounded backlog, close cleanup 조건을 명확히 한다.
- queue depth, pending request 수, gateway backlog, memory peak/growth, close cleanup을 직접 계측한다.
- 병목이 확인된 언어/경로는 queue/channel/lock/worker/serialization 비용을 우선순위에 따라 최적화한다.
- 결과는 local baseline으로 저장하고, 안정성 hard gate와 성능 regression gate를 분리한다.
@ -49,20 +49,20 @@
- [x] [soak-load] sustained load를 30초에서 5분 smoke, 30분 full 후보로 확장한다. 검증: p95/p99 latency trend, throughput trend, memory growth, queue/pending/gateway backlog trend가 결과 파일에 기록된다.
- [x] [slow-mix] 빠른 handler와 느린 handler가 섞인 혼합 부하를 추가한다. 검증: 느린 handler가 같은 connection FIFO를 계약대로 지연시키되 다른 connection의 nonce/FIFO 상태를 오염시키지 않는다.
### Epic: [gateway-policy] gateway 성능 정책
### Epic: [gateway-policy] gateway 신뢰 성능 정책
worker gateway가 실제 성능 이득을 주는 조건과 fallback 조건을 수치로 정한다.
gateway receive coordinator를 제품 기본 수신 경로로 유지하기 위한 신뢰 hard gate와 성능 hardening 조건을 수치로 정한다. TypeScript의 `worker_threads` per-frame offload는 현재 측정 대상인 구현 세부사항이며, 제품 정책의 사용자/운영 옵션으로 두지 않는다.
- [ ] [gateway-real-path] TCP/WS 실제 수신 경로에서 gateway on/off baseline을 추가한다. 검증: in-process `onReceivedFrame`뿐 아니라 실제 transport frame ingest에서 gateway 사용 여부별 throughput, p95/p99, memory, backlog가 기록된다.
- [x] [gateway-threshold] gateway 기본 정책 기준을 선언한다. 검증: payload/traffic 조건별로 default on, default off, adaptive candidate 중 하나가 문서화되고, 이득이 불명확하면 fallback을 기본으로 둔다.
- [ ] [gateway-cleanup] gateway worker close/cancel/drain 계측을 추가한다. 검증: close 후 worker, queue, pending 상태가 비어 있고 repeated run에서 memory growth가 누적되지 않는다.
- [ ] [gateway-real-path] TCP/WS 실제 수신 경로에서 gateway baseline을 추가한다. 검증: in-process `onReceivedFrame`뿐 아니라 실제 transport frame ingest에서 gateway path throughput, p95/p99, memory, backlog가 기록된다.
- [x] [gateway-threshold] gateway 기본 정책 기준을 선언한다. 검증: mandatory gateway hard gate와 단일 receive path hardening 조건이 문서화된다.
- [ ] [gateway-cleanup] gateway close/cancel/drain 계측을 추가한다. 검증: close 후 gateway queue, pending, worker-backed implementation 상태가 비어 있고 repeated run에서 memory growth가 누적되지 않는다.
### Epic: [optimize] 병목 최적화
측정 결과를 근거로 병목을 줄이고 regression gate를 만든다.
- [ ] [hotspot-report] 언어별 병목 리포트를 작성한다. 검증: queue/channel/lock/serialization/worker/transport write path 중 주요 병목 후보와 근거가 결과 파일 또는 작업 문서에 남는다.
- [ ] [targeted-opt] 측정으로 확인된 상위 병목을 우선 최적화한다. 검증: 최적화 전후 같은 local profile에서 stability hard gate를 유지하고, 대상 metric이 개선되거나 fallback 정책이 명확해진다.
- [ ] [targeted-opt] 측정으로 확인된 상위 병목을 우선 최적화한다. 검증: 최적화 전후 같은 local profile에서 stability hard gate를 유지하고, 대상 metric이 개선되거나 남은 병목과 hardening 계획이 명확해진다.
- [ ] [regression-gate] 성능 regression gate를 추가한다. 검증: 같은 환경의 이전 baseline 대비 throughput 20% 이상 하락 또는 p99 latency 25% 이상 악화가 발생하면 WARN/FAIL 후보로 표시하되, 환경 차이는 별도 baseline으로 분리할 수 있다.
- [x] [matrix] 전체 로컬 검증 매트릭스를 실행해 최적화 후에도 프로토콜 호환성과 크로스 언어 통신이 유지되는지 확인한다. 검증: `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` 통과.
@ -72,15 +72,14 @@ worker gateway가 실제 성능 이득을 주는 조건과 fallback 조건을
- local full baseline 최소 축: concurrency 1/16/64/256, burst 10k/100k, sustained 5분 smoke와 30분 full 후보, parallel clients 16/128/512/1024 후보, payload 1KB/64KB/1MB.
- 초기 성능 기대선: same-language TCP 기준 concurrency 256에서 p99 100ms 이하, sustained 5분 smoke에서 unexpected timeout 0, memory growth가 안정 구간 진입 후 선형 증가하지 않을 것.
- regression gate: 같은 환경과 같은 profile에서 throughput 20% 이상 하락 또는 p99 latency 25% 이상 악화를 WARN/FAIL 후보로 본다.
- gateway 정책 기준: gateway on이 같은 payload/profile에서 p95/p99 또는 throughput을 유의미하게 개선하지 못하거나 memory/backlog 비용이 크면 default off 또는 adaptive fallback을 기본으로 둔다.
- gateway 정책 기준: gateway는 순차 보장과 event loop 보호를 담당하는 제품 기본 신뢰 경로다. 같은 payload/profile에서 p95/p99 또는 throughput 회귀, memory/backlog 비용이 크면 gateway를 끄는 정책이 아니라 gateway hardening 대상으로 분류한다.
- 절대 throughput 수치는 hardware/runtime 의존 baseline으로 저장하고, release gate보다 regression 분석과 병목 우선순위 선정에 사용한다.
## gateway 정책
- 기본값: default off. 현재 안정적으로 검증된 gateway는 TypeScript `Communicator.onReceivedFrame` opt-in frame-ingest 경로이며, 일반 TCP/WS transport read loop는 inline decode 경로를 기본으로 유지한다.
- default on 후보: 실제 TCP/WS 수신 경로에서 같은 payload/profile 기준 p95/p99 latency 또는 throughput이 반복 측정에서 유의미하게 개선되고, memory peak와 gateway backlog 비용이 안정성 hard gate를 해치지 않는 경우에만 후보로 둔다.
- adaptive 후보: payload가 크거나 frame ingest가 CPU-bound로 확인되는 조건에서만 별도 후보로 둔다. 해당 조건도 `gateway-real-path` 결과가 있어야 선언한다.
- fallback: 개선 폭이 불명확하거나 memory/backlog 비용이 크거나 close/cancel/drain 계측이 불충분하면 inline decode fallback을 기본으로 유지한다.
- 운영 기본값: gateway는 순차 이벤트 보장과 event loop 보호를 담당하는 mandatory receive path로 정의한다.
- 단일 경로 원칙: gateway/worker on-off를 사용자 또는 운영 옵션으로 노출하지 않는다. TypeScript `worker_threads` per-frame offload가 성능 회귀를 만들면, 이를 끄는 정책이 아니라 단일 gateway 경로 내부의 제거/재설계 대상으로 분류한다.
- legacy inline control: inline decode 비교 row는 성능 분석용 대조군으로만 사용하고 운영 정책으로 문서화하지 않는다.
## 완료 리뷰
@ -104,19 +103,20 @@ worker gateway가 실제 성능 이득을 주는 조건과 fallback 조건을
- 관련 경로: `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh`, `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh`, `typescript/bench/stress.ts`, `dart/`, `go/`, `kotlin/`, `python/`, `typescript/`
- 선행 작업: 수신 큐와 처리 순서 보장
- 후속 작업: 릴리즈 준비 또는 사용처별 성능 요구가 생길 때 package/runtime 정책 정리
- 후속 작업: 언어별 성능 병목 개선, 릴리즈 준비 또는 사용처별 성능 요구가 생길 때 package/runtime 정책 정리
- 현재 근거: `agent-test/runs/20260602-095911-proto-socket-stress-full.md`는 TypeScript same-language local baseline으로 roundtrip, burst, sustained, parallel, gateway profile에서 stability violations 0을 기록했다.
- 완료 근거:
- [bench-schema] `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh``typescript/bench/stress.ts`의 stress result row/table schema가 language, transport, payload bytes, client count, queue backlog, gateway backlog 필드를 기록하도록 확장되었다. 검증: `cd typescript && npm run check`, `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile gateway` PASS, 결과 기록 `agent-test/runs/20260602-112945-proto-socket-stress-quick.md`.
- [cross-baseline] `run_stress.sh --profile cross`가 Dart/Go/Kotlin/Python/TypeScript 서버/클라이언트 방향에서 TCP/WS와 concurrency 1/16/64/256 조합을 별도 실행하도록 정리되었다. 검증: `agent-test/runs/20260602-194252-proto-socket-stress-quick-cross.md`, `agent-test/runs/20260602-200010-proto-socket-full-matrix.md`, 완료 로그 `agent-task/archive/2026/06/m-high-performance-parallel-operations/02+01_cross_baseline/complete.log`.
- [lang-baseline] Dart, Go, Kotlin, Python, TypeScript same-language TCP/WS full roundtrip/payload baseline이 required SKIP 없이 stability hard gate를 통과했다. 지연 사유였던 Kotlin TCP delayed-ACK/TCP_NODELAY 부재, Kotlin WS frame별 coroutine launch에 따른 FIFO 불안정, Go WS 기본 32KiB read limit, Kotlin server-side listener 설치 전 초기 frame 소비 race는 `TcpClient.tcpNoDelay`, Kotlin WS 단일 receive queue, Go WS `SetReadLimit(MaxPacketSize)`, Kotlin stress server setup barrier로 제거했다. 검증: `agent-test/runs/20260603-062724-proto-socket-stress-full.md` PASS.
- [payload-matrix] Dart, Go, Kotlin, Python, TypeScript stress harness가 quick 1KB/64KB와 full 1KB/64KB/1MB payload rows를 기록하고, Python WS 1MB 회귀를 통과하도록 정리되었다. 검증: `agent-test/runs/20260602-205755-proto-socket-stress-full.md`, `agent-test/runs/20260602-205849-proto-socket-full-matrix.md`, `cd python && python3 -m pytest -q`, `git diff --check`, 완료 로그 `agent-task/archive/2026/06/m-high-performance-parallel-operations/03+01_payload_matrix/complete.log`.
- [gateway-threshold] gateway 기본 정책은 default off로 선언했다. 실제 TCP/WS 수신 경로에서 p95/p99 또는 throughput 개선이 반복 측정되고 memory/backlog 비용이 안정적일 때만 default on/adaptive 후보로 승격하며, 이득이 불명확하거나 cleanup 계측이 부족하면 inline decode fallback을 유지한다.
- [gateway-threshold] gateway는 순차 이벤트 보장과 event loop 보호를 담당하는 mandatory receive path로 선언했다. 성능 회귀가 확인되면 gateway 비활성화 정책이 아니라 worker hop 제거/재설계, backpressure, reorder, cleanup hardening 대상으로 분류한다. inline decode 비교 row는 성능 분석용 대조군으로만 남긴다.
- [burst-100k], [parallel-1k], [soak-load] Dart/Go/Kotlin/Python/TypeScript stress harness가 full load profile 축을 100k burst, 128/512/1024 clients, 30초/5분/30분 sustained 후보로 확장했다. 검증: `agent-test/runs/20260602-232000-proto-socket-stress-full.md`는 Dart/Go/Python TCP 축에서 hard gate 위반 0을 기록했고, TypeScript 512/1024 client 회귀는 `agent-test/runs/20260603-015953-proto-socket-stress-full.md`에서 0 violation PASS로 재확인되었다.
- [slow-mix] Dart, Go, Kotlin, Python, TypeScript stress harness에 빠른/느린 handler 혼합 부하 profile이 추가되었다. 검증: `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`는 Dart/Python/TypeScript TCP slow-mix PASS와 Go deferred를 기록했고, `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`는 Kotlin TCP/WS deferred를 병목 후보로 기록했다.
- [matrix] slow-mix 작업 후 전체 로컬 검증 매트릭스가 프로토콜 호환성과 크로스 언어 통신을 유지했다. 검증: `agent-test/runs/20260603-035403-proto-socket-full-matrix.md` PASS.
- 진행 근거:
- [gateway-real-path] 실제 transport 수신 경로 benchmark는 큰 작업으로 분리해 `agent-task/m-high-performance-parallel-operations/06_gateway_real_path/PLAN-cloud-G07.md`에 구현 계획과 review stub을 만들었다.
- [gateway-cleanup] gateway worker close/cancel/drain 계측은 `gateway-real-path` 완료 후 진행하는 큰 작업으로 분리해 `agent-task/m-high-performance-parallel-operations/07+06_gateway_cleanup/PLAN-cloud-G06.md`에 구현 계획과 review stub을 만들었다.
- 보강 필요: gateway 실제 transport 수신 경로 baseline, gateway cleanup 계측, Go/Kotlin slow-mix deferred 축, 언어별 병목 리포트와 regression gate가 남아 있다.
- 작업 동기화:
- 2026-06-03 기준 full matrix, 성능 전용 quick 표준, baseline 비교 경로, 5개 언어별 병목 분석이 완료되었다.
- 현재 활성 `agent-task/` 작업 디렉터리는 없다. 예전에 분리 후보로 기록한 gateway 실제 transport 경로와 cleanup 계측은 새 `언어별 성능 병목 개선` Milestone의 작업 순서 안에서 다시 계획한다.
- 이 Milestone은 성능 기준선과 측정 표준의 선행 근거로 유지하고, 실제 최적화 구현은 `agent-roadmap/milestones/performance-hotspot-optimization.md`에서 진행한다.
- 후속 이관: gateway 실제 transport 수신 경로 baseline, gateway cleanup 계측, Dart TCP 40ms대 고정 지연 및 1MB payload 병목 개선, Dart isolate receive gateway의 실제 transport 경로 적용/backpressure/error ordering/large payload transfer/cleanup hardening, Go slow-mix 검증 모델 보정, Kotlin WS 40ms대 고정 지연 개선, Kotlin slow-mix 검증 모델 보정, Kotlin large payload heap peak 관찰, Go/Python sustained memory 측정 artifact 분리, Python 고동시성 p99 기준점과 gateway API 정책 검토, 언어별 병목 리포트와 regression gate는 `언어별 성능 병목 개선` Milestone으로 넘긴다.
- 확인 필요: 없음

View file

@ -0,0 +1,191 @@
# 언어별 성능 병목 개선
## 목표
`고성능 병렬 운용 기준선과 최적화` Milestone에서 확보한 stress/performance 측정 결과를 근거로, 안정성 hard gate를 유지하면서 언어별 주요 성능 병목을 줄인다. 우선순위는 같은 local idle baseline에서 반복 재현되는 p99 latency, throughput, memory/backlog 비용이 큰 경로를 기준으로 정한다.
## 단계
안정화와 유지
## 상태
[진행중]
## 구현 잠금
- 상태: 해제
- 결정 필요: 없음
## 범위
- Dart TCP 경로의 40ms대 고정 지연, burst/sustained 처리량 저하, 1MB payload p99/throughput 병목, buffer copy, per-packet flush, heartbeat timer churn을 분석하고 개선한다.
- Dart isolate receive gateway는 현재 테스트 가능한 보조 구현 수준이므로 실제 TCP/WS transport 수신 경로 hardening 대상으로 격상한다. `../dart-app-core`의 isolate manager를 그대로 가져오지 않는다면, proto-socket 자체 구현이 backpressure, ordered error, large payload transfer, lifecycle cleanup, 계측 기준에서 동등 이상임을 증명해야 한다.
- TypeScript WS large payload 경로의 p99 latency와 throughput 병목을 분석하고 개선한다.
- Kotlin WS roundtrip/payload 경로의 40ms대 지연 패턴과 frame/queue 처리 비용을 분석하고 개선한다.
- Kotlin TCP 경로는 현재 기준에서 큰 최적화 대상이 아니라 기준선 유지 대상으로 둔다. Kotlin 작업은 WS 고정 지연, slow-mix 검증 모델, large payload JVM heap peak 관찰로 좁힌다.
- TypeScript gateway 경로가 실제 TCP/WS transport에서 큰 손해를 보이는 원인을 정리한다. 이 작업은 TypeScript에 한정하며, 현재 `worker_threads` 기반 per-frame offload를 제품 기본 경로에서 제거하거나 재설계해 순차 보장과 event loop 보호를 단일 mandatory receive path로 유지한다.
- Go는 현 측정 기준에서 최우선 성능 최적화 대상이 아니라 reference fast path로 둔다. Go 작업은 slow-mix 검증 모델과 sustained memory 측정 방식 보정처럼 기준점 신뢰도를 높이는 범위로 제한한다.
- Python은 현 측정 기준에서 최우선 런타임 최적화 대상은 아니지만, reference fast path 보조 기준으로 삼아 다른 언어의 병목 후보와 비교한다. Python 작업은 고동시성 p99 기준점, sustained memory 측정 방식, gateway 관련 API 정책처럼 기준점 신뢰도와 제품 일관성을 높이는 범위로 제한한다.
- 개선 전후 같은 host/runtime/profile에서 `run_performance.sh` baseline 비교를 수행하고, 전체 cross-lang compatibility matrix로 프로토콜 호환성을 재확인한다.
## 기능
### Epic: [baseline] 재현 가능한 기준점 확정
idle 상태에서 성능 기준점을 다시 찍고, 개선 전후 비교 기준으로 사용할 baseline record를 고른다.
- [ ] [idle-full] idle host에서 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --full`을 실행해 개선 전 full baseline을 남긴다. 검증: 결과 파일이 `overall_result: PASS`이고 same-language, cross-language, TypeScript gateway 구성요소가 포함된다.
- [ ] [compare-rule] baseline 비교는 같은 host/runtime/profile의 `proto-socket-performance-*` 기록끼리만 수행한다. 검증: `run_performance.sh --full --baseline <baseline>` 실행 결과가 compared rows와 regression WARN rows를 기록한다.
### Epic: [hotspot] 병목 리포트
기존 결과와 idle baseline을 비교해 언어별 병목 우선순위를 확정한다.
- [ ] [hotspot-report] Dart TCP fixed latency/large payload/isolate gateway hardening, TypeScript WS large payload, Kotlin WS latency/slow-mix 검증 모델, TypeScript gateway overhead를 병목 리포트로 정리한다. 검증: 각 병목 후보에 근거 결과 파일, 대상 row, p99/throughput/memory 수치, 개선 전 기준값이 기록된다.
- [ ] [fast-path-ref] Go/Python 결과를 reference fast path로 정리해 다른 언어 경로와 비교한다. 검증: Go/Python concurrency 256 및 1MB payload rows가 같은 표에 포함되고, Go/Python은 최우선 런타임 최적화 대상이 아니라 기준점 보강 대상으로 분류된다.
### Epic: [go-reference] Go reference 기준점 보강
Go는 현재 측정에서 가장 빠르고 안정적인 구현이므로 병목 최적화 대상이 아니라 비교 기준으로 유지한다. 남은 작업은 제품 구현 변경보다 검증 모델과 측정 artifact 제거에 둔다.
- [ ] [go-slow-mix-model] Go slow-mix deferred 축을 제품 결함으로 단정하지 않고, public request API의 goroutine scheduling 의존 completion order를 어떻게 검증할지 모델을 정리한다. 검증: `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`의 Go deferred 사유가 병목 리포트에 반영되고, 필요한 경우 결정론적 검증 helper 또는 별도 row 기준이 제안된다.
- [ ] [go-sustained-memory-metric] Go 30분 sustained memory peak 증가가 구현 leak인지 bench artifact인지 분리한다. 검증: latency 샘플 누적을 streaming percentile 또는 bounded sampling으로 바꿀 필요가 있는지 정리하고, `agent-test/runs/20260602-232000-proto-socket-stress-full.md`의 Go sustained 30초/5분/30분 memory rows가 재해석된다.
- [ ] [go-reference-guard] Go는 idle full baseline에서 안정성 hard gate와 reference 수치를 유지하는지 확인한다. 검증: Go TCP/WS concurrency 256, 1MB payload, burst 100k, parallel 1024, sustained rows가 병목 리포트의 reference table에 남고 regression WARN이 있으면 원인을 분리한다.
### Epic: [python-reference] Python reference 기준점 보강
Python은 현재 측정에서 slow-mix와 same-language 안정성 hard gate를 통과한 중간 reference 구현이다. 남은 작업은 큰 런타임 최적화보다 고동시성 p99, sustained memory 측정 artifact, gateway 관련 API 정책을 정리하는 데 둔다.
- [ ] [python-high-parallel-p99] Python TCP parallel 128/512/1024 clients에서 p99 latency가 커지는 구간을 기준점으로 정리한다. 검증: `agent-test/runs/20260602-232000-proto-socket-stress-full.md`의 Python parallel rows가 병목 리포트에 기록되고, stability violation 0인 correctness 결과와 고동시성 p99 비용이 분리된다.
- [ ] [python-sustained-memory-metric] Python 30분 sustained memory peak 증가가 구현 leak인지 bench artifact인지 분리한다. 검증: 30초/5분/30분 memory rows가 latency sample 누적, Python 객체 allocation, asyncio task/queue 잔여 상태 중 어느 후보에 가까운지 재해석된다.
- [ ] [python-gateway-api-policy] Python `gateway_type`, `payload_size_threshold`, `process/asyncio` gateway 경로가 제품 정책상 사용자/운영 옵션인지 내부 검증 경로인지 정리한다. 검증: 기본 클라이언트 경로가 단일 검증 경로로 유지되는지 확인하고, 외부 옵션으로 품질 일관성을 깨는 API가 필요하면 제거 또는 내부화 후보로 기록된다.
- [ ] [python-reference-guard] Python은 idle full baseline에서 안정성 hard gate와 중간 reference 수치를 유지하는지 확인한다. 검증: Python TCP/WS concurrency 256, 1MB payload, slow-mix, parallel 1024, sustained rows가 병목 리포트의 reference table에 남고 regression WARN이 있으면 원인을 분리한다.
### Epic: [payload] large payload 최적화
큰 payload에서 두드러지는 지연과 처리량 저하를 줄인다.
- [ ] [dart-tcp-1mb] Dart TCP 1MB payload 경로를 분석하고 p99 latency 또는 throughput을 개선한다. 검증: 같은 idle baseline 대비 Dart TCP `payload=1MB` row가 stability hard gate를 유지하고 p99 또는 throughput이 개선되거나 병목 원인이 명확히 문서화된다.
- [ ] [ts-ws-1mb] TypeScript WS 1MB payload 경로를 분석하고 p99 latency 또는 throughput을 개선한다. 검증: 같은 idle baseline 대비 TypeScript WS `payload=1MB` row가 stability hard gate를 유지하고 p99 또는 throughput이 개선되거나 병목 원인이 명확히 문서화된다.
### Epic: [dart-tcp] Dart TCP 및 isolate receive path hardening
Dart는 WS small roundtrip은 양호하지만 TCP roundtrip/payload/burst/sustained에서 40ms대 고정 지연, 처리량 저하, 1MB payload 병목이 반복된다. 현재 `IsolateInboundGateway`는 단위 테스트로는 동작하지만 실제 `ProtobufClient`/`WsProtobufClient` transport 수신 경로에 연결되어 있지 않으므로, TCP write/read path 개선과 isolate receive gateway 제품화 조건을 함께 다룬다.
- [ ] [dart-tcp-fixed-latency] Dart TCP 40ms대 고정 지연 원인을 분리한다. 검증: `tcpNoDelay` 부재, per-packet `flush()`, write serialization, heartbeat timer churn, stream pause/resume 중 어느 후보가 TCP concurrency 16/64/256 및 sustained p99를 만드는지 같은 idle baseline에서 기록된다.
- [ ] [dart-tcp-burst-sustained] Dart TCP burst/sustained 처리량 저하를 fixed-latency 분석과 함께 분리한다. 검증: `agent-test/runs/20260602-232000-proto-socket-stress-full.md`의 Dart TCP burst 100k throughput 및 sustained 30분 throughput/p99 rows가 개선 전 기준으로 기록되고, write flush, heartbeat timer churn, receive buffering 중 원인 후보가 구분된다.
- [ ] [dart-tcp-buffer-copy] Dart TCP large payload의 write/read buffer copy를 줄인다. 검증: `_socket.add([...header, ...baseBytes])`, `_arrivedData.sublist`, `Uint8List.fromList`, `List<int>.from` 비용이 개선 전후로 비교되고, TCP 64KB/1MB payload row의 p99 또는 throughput이 개선되거나 남은 병목이 문서화된다.
- [ ] [dart-heartbeat-cost] Dart heartbeat timer churn을 성능 경로에서 분리한다. 검증: frame 수신마다 `sendHeartBeat()`가 timer를 cancel/recreate하는 비용을 계측하고, interval 0이 비활성화가 아닌 즉시 발화로 동작하는 현재 semantics가 테스트 표준과 제품 정책에서 정리된다.
- [ ] [dart-isolate-equal-or-better] `../dart-app-core` isolate manager를 가져오지 않는 결정이 타당한지 비교 기준을 작성한다. 검증: 현재 proto-socket `IsolateInboundGateway``dart-app-core`의 long-running isolate, ready/exit event, message routing, lifecycle 관리 수준 대비 어떤 장단점이 있는지 기록하고, 독자 구현을 유지한다면 동등 이상 조건이 명시된다.
- [ ] [dart-isolate-real-path] Dart isolate receive gateway를 실제 TCP/WS transport 수신 경로 hardening 대상으로 연결한다. 검증: 테스트 전용 `attachInboundGateway`에 머물지 않고 제품 내부 단일 수신 경로에서 FIFO, nonce matching, close cleanup, web fallback을 유지하며, 사용자/운영 on-off 옵션 없이 성능 row에 반영된다.
- [ ] [dart-isolate-backpressure] Dart isolate gateway에 bounded backpressure와 backlog 계측을 추가한다. 검증: `SendPort.send()` 무제한 mailbox 의존을 제거하거나 제한 조건을 문서화하고, queued/in-flight/reorder/sink residual backlog가 stress/performance 결과에 기록된다.
- [ ] [dart-isolate-error-ordering] Dart isolate gateway decode error를 입력 순서대로 보고하고 transport disconnect semantics와 연결한다. 검증: malformed frame이 isolate를 죽이거나 reorder를 영구 대기시키지 않고, ordered error result, close, pending cleanup 테스트가 추가된다.
- [ ] [dart-isolate-large-payload-transfer] Dart isolate gateway의 large payload 전달 비용을 줄인다. 검증: 1MB frame에서 isolate message copy 비용을 `TransferableTypedData` 또는 동등한 최소-copy 방식으로 줄일 수 있는지 확인하고, 적용 전후 TCP/WS 1MB payload p99/throughput과 memory row가 기록된다.
- [ ] [dart-isolate-cleanup] Dart isolate gateway lifecycle cleanup을 hard gate로 만든다. 검증: close 후 isolate, ReceivePort, SendPort, result stream, reorder buffer, pending queue가 잔여 없이 정리되고 repeated run memory/backlog 증가가 0으로 기록된다.
### Epic: [ws-latency] Kotlin WS 지연 및 검증 모델 개선
Kotlin TCP는 현재 same-language 기준에서 안정적인 유지 대상이다. Kotlin 개선은 WS roundtrip/payload의 40ms대 고정 지연 패턴을 줄이고, slow-mix 검증 모델을 보정하되 FIFO와 nonce matching 계약을 깨지 않는 데 둔다.
- [ ] [kotlin-ws-delay] Kotlin WS roundtrip/payload 경로의 고정 지연을 분석하고 병목을 줄인다. 검증: OkHttp callback, `ByteString.toByteArray()` copy, `receiveQueue` Channel staging, communicator inbound queue, write/send flush 비용이 분리되고, Kotlin WS roundtrip concurrency 16/64/256 및 1KB/64KB/1MB payload rows가 stability hard gate를 유지한 채 p99 또는 throughput이 개선되거나 병목 원인이 명확히 문서화된다.
- [ ] [kotlin-slow-mix-model] Kotlin slow-mix deferred 축을 제품 결함으로 단정하지 않고, 같은 client의 concurrent request completion order가 coroutine scheduling에 의존하는 검증 모델을 보정한다. 검증: 결정론적 helper, sequential same-client row, 또는 per-connection FIFO를 직접 관찰하는 별도 row 중 하나가 제안되고, `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`의 Kotlin deferred 사유가 병목 리포트에 반영된다.
- [ ] [kotlin-large-payload-memory] Kotlin large payload heap peak를 WS 지연 개선과 함께 관찰한다. 검증: `agent-test/runs/20260603-062724-proto-socket-stress-full.md`의 Kotlin TCP/WS 1MB payload memory rows가 개선 전 기준으로 기록되고, JVM allocation/GC 영향과 실제 queue/backlog 잔여 상태가 분리된다.
- [ ] [kotlin-tcp-reference-guard] Kotlin TCP는 idle full baseline에서 현재 기준선이 유지되는지 확인한다. 검증: Kotlin TCP concurrency 256 및 TCP 1MB payload rows가 reference table에 남고, WS 개선 작업이 TCP p99/throughput 또는 stability hard gate를 악화시키지 않는다.
### Epic: [gateway] TypeScript gateway 단일 수신 경로
이 Epic은 TypeScript gateway에 한정한다. gateway는 순차 이벤트 보장과 event loop 보호를 담당하는 mandatory receive path이며, 사용자/운영 설정으로 켜고 끄는 옵션을 두지 않는다. 현재 `worker_threads` per-frame offload 비용은 gateway를 끄는 근거가 아니라, worker hop을 기본 경로에서 제거하거나 내부 구현으로 재설계해야 하는 hardening 근거로 분류한다.
- [ ] [ts-gateway-single-path] TypeScript gateway를 제품 기본 수신 경로로 유지하기 위한 단일 경로 조건을 정의한다. 검증: gateway가 FIFO, nonce matching, close cleanup, bounded backlog hard gate를 유지하고, gateway/worker on-off 같은 외부 옵션 없이 같은 idle baseline 대비 p99/throughput 회귀 기준을 넘지 않는 조건이 문서화된다.
- [ ] [ts-worker-hop-removal] TypeScript `worker_threads` 기반 per-frame offload를 기본 gateway 경로에서 제거하거나, 동일 단일 경로 내부 구현으로 재설계한다. 검증: worker hop, buffer copy, reorder wait, sink serialization, backpressure 차이, memory 증가, p99 악화 원인이 줄어들고, 결과가 `run_performance.sh --full --baseline <baseline>` 병목 리포트에 기록된다.
- [ ] [gateway-overhead] TypeScript gateway 경로의 남은 overhead를 legacy inline control row와 비교해 정량화한다. 검증: 차이가 제품 정책 옵션으로 해석되지 않도록 control row는 분석용 대조군으로만 기록되고, 결론은 단일 mandatory receive path hardening 항목으로 남는다.
### Epic: [verify] 회귀 검증
개선 후 안정성과 호환성 회귀가 없는지 확인한다.
- [ ] [perf-regression] 개선 후 `run_performance.sh --full --baseline <baseline>`을 실행해 throughput 20% 이상 하락 또는 p99 25% 이상 악화 WARN 후보를 확인한다. 검증: regression WARN row가 없거나, 남은 WARN row의 원인과 후속 처리가 기록된다.
- [ ] [compat-matrix] 개선 후 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all`을 실행한다. 검증: Proto sync, 동일언어, Dart.web/Dart.web(WSS), 전체 cross-lang matrix가 PASS다.
## 작업 순서
현재 host가 느려진 상태에서는 full 성능 측정을 기준값으로 쓰지 않는다. full baseline은 idle host에서만 확정하고, 그 전에는 문서/설계/검증 모델 정리와 harness sanity check만 수행한다.
1. 기준점 확정
- [idle-full], [compare-rule]을 먼저 수행한다.
- `run_performance.sh --quick`은 harness sanity check 용도로만 사용하고, 병목 우선순위와 개선 전후 판정은 idle host의 `--full` 결과로만 한다.
- [hotspot-report], [fast-path-ref]에서 언어별 병목과 Go/Python reference 기준점을 한 표로 정리한다.
2. 신뢰 경로와 검증 모델 정리
- TypeScript: [ts-gateway-single-path]로 mandatory receive path 조건을 먼저 고정한다.
- Dart: [dart-isolate-equal-or-better], [dart-isolate-backpressure], [dart-isolate-error-ordering], [dart-isolate-cleanup]으로 isolate gateway 제품화 조건을 먼저 정한다.
- Go/Kotlin/Python: [go-slow-mix-model], [kotlin-slow-mix-model], [python-gateway-api-policy]처럼 제품 결함과 검증 모델/정책 문제를 분리한다.
3. 구현 우선순위
- 1순위: TypeScript [ts-worker-hop-removal]. 순차 보장과 event loop 보호를 유지하면서 worker hop 비용을 제거하거나 재설계한다.
- 2순위: Dart [dart-tcp-fixed-latency], [dart-tcp-burst-sustained], [dart-tcp-buffer-copy], [dart-heartbeat-cost], [dart-isolate-real-path], [dart-isolate-large-payload-transfer]. TCP 고정 지연과 1MB payload 병목, isolate receive path hardening을 같이 다룬다.
- 3순위: Kotlin [kotlin-ws-delay], [kotlin-large-payload-memory]. WS 고정 지연과 large payload heap peak를 같이 본다.
- 4순위: TypeScript [ts-ws-1mb]. gateway 단일 경로가 안정된 뒤 WS large payload를 개선한다.
- 5순위: Go/Python reference guard와 memory metric 보강. 최적화보다 측정 artifact와 회귀 감시에 둔다.
4. 회귀 검증
- 각 구현 묶음 후 [perf-regression]으로 같은 idle baseline 대비 회귀를 확인한다.
- 마지막에는 [compat-matrix]로 전체 cross-language compatibility를 재확인한다.
## 작업 동기화
- 2026-06-03 기준 지원 5개 언어 분석은 완료했다. Go/Python은 reference 기준점 보강, Kotlin은 WS latency/slow-mix 검증 모델, Dart는 TCP fixed latency/large payload/isolate receive path, TypeScript는 gateway worker hop과 WS 1MB가 핵심 작업이다.
- `고성능 병렬 운용 기준선과 최적화` Milestone의 기준선/매트릭스 결과를 선행 근거로 사용한다.
- 현재 활성 `agent-task/` 작업 디렉터리는 없다. 새 구현 작업을 시작할 때는 위 작업 순서에 맞춰 작은 작업 단위로 plan을 만든다.
- 사용자 확인 전에는 host 부하가 큰 상태에서 full performance baseline을 새 기준값으로 확정하지 않는다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 없음
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 없음
## 범위 제외
- wire format, proto schema, nonce 의미를 변경하지 않는다.
- 성능 개선을 위해 FIFO, request-response matching, close cleanup 계약을 완화하지 않는다.
- TypeScript 외 언어에 `worker_threads` 결론을 일반화하지 않는다.
- gateway on/off, worker on/off 같은 사용자/운영 옵션을 추가하지 않는다.
- Dart isolate gateway를 테스트 전용 또는 외부 옵션으로만 남겨 성능/신뢰 경로를 우회하지 않는다.
- Go에 대해 충분한 기준점 없이 런타임 최적화 작업을 새로 시작하지 않는다.
- Python에 대해 충분한 기준점 없이 런타임 최적화 작업을 새로 시작하지 않는다.
- Kotlin TCP 경로는 충분한 병목 근거 없이 구조 변경하지 않는다.
- package registry 릴리즈, 외부 CI/CD runner 연결을 시작하지 않는다.
- C# 또는 Swift 포팅을 시작하지 않는다.
- 애플리케이션 레벨 priority queue, routing key, room/session semantics를 추가하지 않는다.
- 서로 다른 host/runtime/profile의 절대 throughput 수치를 고정 합격선으로 삼지 않는다.
## 작업 컨텍스트
- 관련 경로: `dart/`, `go/`, `kotlin/`, `python/`, `typescript/`, `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh`, `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh`
- 선행 작업: 고성능 병렬 운용 기준선과 최적화
- 후속 작업: 릴리즈 준비 또는 사용처별 성능 요구가 생길 때 package/runtime 정책 정리
- 기준 표준: `agent-test/local/proto-socket-performance-baseline.md`
- 참고 결과:
- `agent-test/runs/20260603-062724-proto-socket-stress-full.md`는 5개 언어 TCP/WS full `roundtrip,payload` 측정에서 required SKIP 없이 `stability_violations=0`을 기록했다.
- Go는 full baseline에서 TCP concurrency 256 throughput 약 `60748.4 rps`, p99 약 `4.432ms`, WS concurrency 256 throughput 약 `62631.5 rps`, p99 약 `4.872ms`로 reference fast path 후보였다. 1MB payload도 TCP p99 약 `21.076ms`, WS p99 약 `35.880ms`로 주요 병목 후보가 아니었다.
- `agent-test/runs/20260602-232000-proto-socket-stress-full.md`는 Go TCP burst 100k throughput 약 `201132.8 rps`, parallel 1024 clients throughput 약 `214665.2 rps`, p99 약 `154.621ms`, sustained 30분 throughput 약 `16264.9 rps`, p99 약 `0.646ms`, stability violation 0을 기록했다. 다만 sustained memory peak는 30초 약 `57.1MB`, 5분 약 `143.4MB`, 30분 약 `794.1MB`로 증가해 구현 leak보다 bench latency sample 누적 artifact 가능성을 별도로 확인해야 한다.
- `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`는 Go slow-mix를 deferred로 기록했다. 사유는 public request API의 concurrent `SendRequestTyped` completion order가 goroutine scheduling에 의존해 same-connection slow/fast response-arrival order를 결정론적으로 검증하기 어렵다는 점이며, 성능 병목으로 단정하지 않는다.
- Python은 full baseline에서 TCP concurrency 256 throughput 약 `10396.7 rps`, p99 약 `26.480ms`, WS concurrency 256 throughput 약 `7790.6 rps`, p99 약 `29.769ms`로 안정적인 중간 reference 후보였다. 1MB payload는 TCP p99 약 `18.856ms`, WS p99 약 `47.424ms`였고 stability violation은 0이었다.
- `agent-test/runs/20260602-232000-proto-socket-stress-full.md`는 Python TCP parallel 128 clients p99 약 `93.378ms`, 512 clients p99 약 `501.368ms`, 1024 clients p99 약 `1288.999ms`로 고동시성 p99 증가를 기록했다. 같은 결과의 sustained memory peak는 30초 약 `133.8MB`, 5분 약 `181.1MB`, 30분 약 `614.5MB`로 증가해 구현 leak과 bench artifact를 분리해야 한다.
- `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`는 Python slow-mix를 throughput 약 `378.5 rps`, p99 약 `210.836ms`, stability violation 0으로 PASS 기록했다.
- Kotlin은 TCP concurrency 256 throughput 약 `20960.7 rps`, p99 약 `21.173ms`, TCP 1MB payload throughput 약 `430.7 rps`, p99 약 `24.975ms`로 TCP 경로는 현 기준에서 큰 병목 후보가 아니었다.
- Kotlin WS는 concurrency 16/64/256에서 각각 p99 약 `47.578ms`, `43.345ms`, `44.877ms`로 40ms대 고정 지연 패턴이 반복되고, WS 1KB payload p99 약 `42.651ms`, WS 1MB payload p99 약 `133.842ms`를 기록했다.
- Kotlin large payload memory peak는 TCP 1MB 약 `500.3MB`, WS 1MB 약 `668.3MB`로 기록되어, WS 지연 개선 시 JVM allocation/GC 영향과 queue/backlog 잔여 상태를 함께 분리해야 한다.
- `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`는 Kotlin TCP/WS slow-mix를 deferred로 기록했다. 현재 보강 방향은 제품 결함 단정이 아니라, 같은 client의 concurrent request completion order가 coroutine scheduling에 의존하는 검증 모델을 결정론적으로 보정하는 것이다.
- Dart는 WS small roundtrip은 양호했지만 TCP concurrency 16/64/256에서 각각 p99 약 `43.626ms`, `44.404ms`, `49.109ms`로 40ms대 고정 지연이 반복되었다. TCP burst 100k throughput은 약 `154.4 rps`, TCP sustained 30분은 throughput 약 `385.3 rps`, p99 약 `44.079ms`였고, TCP 1MB payload는 throughput 약 `9.6 rps`, p99 약 `892.819ms`로 large payload TCP 병목 후보가 뚜렷했다.
- Dart WS는 concurrency 256 throughput 약 `42912.0 rps`, p99 약 `7.920ms`, WS 1MB payload throughput 약 `63.1 rps`, p99 약 `111.490ms`로 TCP보다 양호해, Dart 우선순위는 WS보다 TCP path hardening에 둔다.
- Dart 코드 확인 결과 `dart/lib/src/inbound_gateway_io.dart`에는 long-running `IsolateInboundGateway`가 있으나, `../dart-app-core``IsolateManager`/`IsolateBase`/`IsoMessege` 모듈을 가져온 형태는 아니며 현재 제품 `ProtobufClient`/`WsProtobufClient` 기본 수신 경로에는 연결되어 있지 않다. 현재 사용은 `dart/test/communicator_test.dart`의 수동 attach 테스트 중심이다.
- `cd dart && dart test test/communicator_test.dart -r expanded`는 isolate gateway 단위 동작을 PASS로 확인했지만, 이는 실제 TCP/WS transport 성능 병목 해소 근거가 아니다. `cd dart && dart analyze`는 오류 없이 통과했고 `heartbeat_mixin.dart``@override` annotation info 1건만 남았다.
- TypeScript는 TCP/WS small roundtrip은 대체로 양호했지만 WS 1MB payload p99 약 `228.648ms`로 large payload WS 병목 후보가 확인되었다.
- `agent-test/runs/20260603-080241-proto-socket-stress-full.md`는 TypeScript gateway full 결과에서 legacy inline control row가 WS throughput 약 `16242.7 rps`, p99 약 `1.887ms`였고, 현재 `worker_threads` gateway row는 throughput 약 `2499.3 rps`, p99 약 `205.654ms`로 악화되어 TypeScript worker-hop hardening 필요 근거가 되었다. TCP gateway row도 p99 약 `193.306ms`로 legacy inline control row p99 약 `43.095ms`보다 크게 나빴다. 이 수치는 gateway 비활성화 결론이 아니라, TypeScript mandatory receive path에서 `worker_threads` per-frame offload를 제거하거나 재설계해야 하는 overhead 기준이다.
- `agent-test/runs/20260603-092819-proto-socket-performance-quick.md`는 현재 HEAD `4dbeecf`에서 성능 전용 quick 표준이 same-language, cross-language, TypeScript gateway 구성요소를 모두 PASS로 aggregate 기록했다.
- `agent-test/runs/20260603-095712-proto-socket-performance-quick.md`는 baseline 비교 경로가 `compared rows: 90`, `missing baseline rows: 0`, `warning rows: 0`으로 동작함을 확인했다.
- 확인 필요: idle host에서 full baseline을 다시 측정한 뒤, 위 참고 결과 중 환경 잡음이 큰 quick 수치를 최종 개선 기준으로 쓸지 여부를 판단한다.