Repository navigation
Churn Performance KO
구현에 대한 근거이며 용량 보장은 아닙니다. 운영 중인 배포는 Agent 용량과 모니터링부터 확인하십시오. 아래는 과거 조사 기록이며 이번 문서 재구성으로 측정을 다시 수행한 것은 아닙니다.
2026-09-22 조사에서 누적 Peer 기록은 65,227개였고, Prometheus의 최근 5분 평균에서 Controller는 약 7.17개 코어와 초당 약 526MB의 메모리 할당을 기록했습니다. 메모리 할당률은 사용 중인 메모리 크기와 별개이며, 짧게 쓰고 버리는 객체의 생성·회수 비용도 포함합니다.
-
/api/v1/bootstrap이 주소 목록을 구하기 위해 전체 snapshot을 생성했습니다. 이 과정에서 과거 Peer 전체 복사·정렬, 실험 지표 및 토폴로지 계산이 발생했습니다. 이제 활성 노드 인덱스에서 해당 실행의 ready boot 주소만 읽습니다. - Discovery와 부분 heartbeat의 점유량 확인도 활성 노드 인덱스를 사용합니다. 부분 heartbeat에서 생략된 노드를 찾기 위한 전체 이력 순회를 제거했습니다.
- 대시보드 SSE는 종료 노드를 제외한 후 snapshot을 만듭니다. 과거 노드를 복사·정렬한 뒤 버리는 비용을 제거했습니다. 전체 inventory와 저장 결과에는 이력을 보존합니다.
- Agent·실험·최근 이벤트 목록 REST API는 필요한 목록만 복사합니다. Agent 등록도 Agent 정보만 구성하고 Peer 전체 snapshot을 만들지 않습니다.
- 완료되어 변하지 않는 실험의 Prometheus 전파 지연 히스토그램을 재사용합니다. 새로운 이벤트, 늦게 도착한 증거, 수신 기간 만료는 기존 규칙대로 요약과 히스토그램을 갱신합니다. 지표 정의와 저장 결과 계산은 유지합니다.
- Agent와 Controller의 이벤트 크기 검증은 이벤트별 인코딩 결과의 길이를 검사하고 버립니다. 검증 목적으로 전체 전송 배치를 다시 할당하지 않습니다. JSON escaping, 정확한 10MiB 경계, 잘못된 후속 이벤트의 일괄 거부를 유지합니다.
Intel Xeon E5-2620 v3, GOMAXPROCS=2에서 동일한 입력으로 측정했습니다. 조회 테스트는 종료 Peer 65,000개와 활성 Peer 100개를 사용합니다. 이벤트 검증 테스트는 4KiB 문자열을 가진 이벤트 250개를 사용합니다. MB와 KB는 각각 1,000,000바이트와 1,000바이트입니다.
| 경로 | 변경 전 시간/회 | 변경 후 시간/회 | 변경 전 할당/회 | 변경 후 할당/회 |
|---|---|---|---|---|
| Bootstrap 주소 조회 | 196.56ms | 0.111ms | 149.22MB | 28.86KB |
| 대시보드 frame 생성 | 242.60ms | 1.132ms | 171.83MB | 320.91KB |
| 이벤트 크기 검증 | 4.33ms | 1.70ms | 7.05MB | 12.26KB |
종료 이력이 없는 경우와 65,000개인 경우 모두 변경 후 활성 노드 조회의 할당량은 비슷했습니다. 실제 운영 CPU는 활성 Peer 수, 토폴로지, 이벤트 발생률, Docker 부하에도 영향을 받습니다. 위 수치는 로컬 경로별 벤치마크이며 운영 배포 후 전체 실험의 CPU·진입률을 다시 확인해야 합니다.
GOMAXPROCS=2 go test ./internal/controller ./internal/model \
-run '^$' \
-bench 'Benchmark(BootstrapChurnHistory|DashboardChurnHistory|ValidateTelemetryBatch)$' \
-benchtime=300ms -benchmem수정 적용에는 Controller와 Agent 이미지 재빌드·재배포가 필요합니다. 실험 이력 삭제를 전제로 하지 않습니다. 배포 후에는 같은 시나리오·동일 활성 Peer 규모에서 다음 Prometheus 지표와 Controller의 churn join schedule delayed 로그를 함께 비교하십시오.
rate(process_cpu_seconds_total{job="kpl-controller"}[5m])
rate(go_memstats_alloc_bytes_total{job="kpl-controller"}[5m])
sum by (state) (kpl_nodes)
kpl_local_telemetry_queue_events
기존 대용량 trace 합성 회귀 테스트는 20초간 21회 갱신에서 경량 Dashboard 스트림의 JSON 전송량을 85,767,024바이트에서 170,144바이트로 줄였습니다. 이는 회귀 테스트 입력의 측정값이며 모바일 네트워크 실측이나 임의 규모의 전송량 보장은 아닙니다. 현재 형식은 Dashboard SSE, 운영 확인은 연결 진단을 참고하십시오.
2026-09-28 코드 검토에서 Agent 프로세스 기록과 run 차단 맵이 Controller 확인 이후에도 과거 churn 횟수에 비례해 증가하는 경로를 확인했습니다. 제거가 끝난 실패 프로세스도 마지막 토폴로지·점수 컬렉션을 보관했습니다. 이제 오래된 확인 완료 기록과 캐시 밖 차단 정보는 로컬 디스크로 이동하고, 제거된 컨테이너의 토폴로지를 해제하며, 밀린 작업이 줄면 커진 map도 다시 구성합니다. 확인 완료된 스풀 참조도 비웁니다. 활성 heartbeat는 이력 저장과 독립적으로 유지하고 Controller 재시작 시 제한된 페이지로 이력을 재전송합니다.
합성 회귀 테스트는 회차마다 종료 Peer 256개를 추가하고 Controller 확인 및 이력 정리를 12회 반복합니다(총 3,072개). 매 회차 이후 메모리 종료 이력은 64개로 유지됩니다. 단일 파일 이력 저장소를 도입하기 전 수행한 이 테스트에서 테스트용 GC 후 로컬 Go heap은 첫 회차 약 1.23MiB, 마지막 회차 약 1.06MiB였습니다. 테스트 프로세스를 포함한 과거 측정값이며 현재 저장소 구현의 측정값이나 운영 메모리 상한이 아닙니다. 전달 실패, 컨테이너 제거 실패, 로컬 저장 실패/복구, 정리·재시작 뒤 중복 ID 차단, 저장된 세대 차단, 생성/정리 교차, Controller 재시작 시 제한된 이력 재전송도 검증합니다. 실제 Docker 배포에서 장시간 메모리 추세는 업데이트 후 관찰해야 합니다.
go test ./internal/agent -run '^TestChurnHistoryMemoryIsBoundedAndRetiredIDsRemainFenced$' -count=1 -v
go test -race ./internal/agent ./internal/controller -run 'History|Retire|RunFenceCache|Heartbeat' -count=1디스크 적체 회귀는 서로 다른 32 KiB 본문 1,024개(총 32 MiB)를 수락한 뒤 spool을 다시 열어 제한된 전송 구간과 전체 이벤트 순서·전달을 검증합니다. 로컬 Go 1.27.1의 테스트 전용 GC 후 heap은 시작 1.21 MiB, 전체 디스크 적체 상태 1.25 MiB, 31개 이벤트를 적재한 재시작 복구 상태 2.52 MiB였습니다. 합성 테스트 프로세스 수치이며 운영 컨테이너 RSS나 보장된 메모리 상한은 아닙니다. 기존 spool 형식·부분 수신 확인 기록 실패·바이트 한도·디코더 포화 및 자식 프로세스 없이 동작하는 Engine 대기 64개 동시 취소도 검증합니다.
go test ./internal/agent -run 'TestTelemetryDisk|TestTelemetryRecovery|TestTelemetryPartial|TestTelemetryDecoder|TestTelemetryByte|TestDockerEngine' -count=1 -v추가 회귀는 작은 저장/확인 256회를 반복하면서 payload와 ACK inode가 교체되지 않고 재사용되는지 확인합니다. payload/ACK 부분 쓰기·기존 형식 복구·원본 JSON ID와 순서·빈 활성 파일 재사용·구버전 형식 보호도 검증합니다. GC 후 Go heap 검사에 나타나지 않는 파일시스템 생성 부담을 줄이기 위한 검증입니다.
로컬 Intel Xeon E5-2620 v3에서 같은 2,000개 이벤트와 바이트 한도를 비교하면 디코딩·재인코딩은 약 63.3 ms / 할당 10.24 MB, 저장된 JSON 직접 복사는 약 7.33 ms / 할당 0.61 MB였습니다. 작업당 벤치마크 할당량·시간이며 잔류 heap이나 운영 컨테이너 메모리는 아닙니다. 배포 후 장시간의 cgroup slab_reclaimable, anonymous, 프로세스 RSS, Go live heap을 다시 확인해야 합니다. 호스트 전체 캐시 삭제나 주기적 강제 GC는 사용하지 않습니다.
go test ./internal/agent -run 'TestTelemetryLog|TestTelemetryRaw|TestTelemetryFormat' -count=1
go test ./internal/agent -run '^$' -bench '^BenchmarkTelemetryForwardingWindow$' -benchmem
go test ./internal/controller -run 'TestDeferredAgent|TestAmbiguousAgent|TestAdmissionRetry' -count=12026-09-29 Prometheus를 읽기 전용으로 조회했을 때 telemetry 개선 이후에도 컨테이너 메모리가 증가하는 추세가 남아 있었습니다. 한 Agent에서 누적 종료 Peer가 401개에서 3,133개로 늘어날 때 cgroup slab_reclaimable은 약 1.1MiB에서 7.5MiB로 늘었습니다. 이 구간 OS 스레드는 111개로 일정했고 Go live heap은 약 3.2–7.8MiB 사이에서 오르내렸습니다. 그보다 앞선 최초 부하 구간에는 런타임 스레드·스택 증가도 있었습니다. 파일시스템 메타데이터 증가 경로를 확인한 것이며 컨테이너 메모리 증가 전체의 원인이 하나라고 증명한 것은 아닙니다.
이전 이력 형식은 종료 Peer마다 JSON 파일 하나를 영구 보관하여 inode/dentry가 쌓였고 새 ID 조회에도 경로 탐색이 필요했습니다. 이제 종료 기록을 인덱스가 있는 bbolt 파일 하나에 보관합니다. 최근 메모리 이력, 실행별 인덱스, 세대 차단, 전달 확인과 중복 ID 방지는 유지합니다. 기존 JSON 이력은 시작 시 컨테이너 정리 전에 이전하며 설치된 내용과 대조한 뒤 원본 파일을 제한된 양씩 정리합니다. 이전 및 형식 호환성을 참고하십시오.
회귀 테스트는 실행마다 종료 Peer 512개씩 12회, 총 6,144개를 저장해도 Peer 이력 파일이 하나이며 실행별 인덱스만 별도로 생기는지 확인합니다. 이전·재시작 테스트는 기존 기록 300개를 세 페이지로 모두 복구하고 손상되거나 대조가 불일치하는 원본을 보존하는지 검사합니다. batch 전체 롤백, 복사한 재전송 키, 바이트 제한, 취소도 확인합니다. 이는 저장 동작 검증이며 배포된 메모리 감소량을 측정한 결과는 아닙니다. Agent 이미지를 다시 빌드·배포한 뒤 같은 부하의 휴지 구간에서 slab_reclaimable, live heap, 스레드 수와 Agent-only working set을 비교해야 합니다. 초기 20MiB로 복귀한다고 보장하지 않으며 DB 페이지와 재사용 런타임 자원은 OS·런타임 정책에 따라 회수 또는 재사용됩니다.
go test ./internal/agent -run '^TestHistoryStore' -count=1 -v
GORACE=atexit_sleep_ms=0 go test -race ./internal/agent -run 'Test(History|RunFence|Retir|Registration|Heartbeat|AgentHistory|BlockedConfig|ConfigWrite)' -count=1Home · Documentation guide · kpl-v3 source
Commands run from the kpl-v3 source checkout.