Skip to content

perf(logging): eliminate hot-path allocations - #60

Merged
park285 merged 14 commits into
mainfrom
agent/optimize-logging-hot-paths
Aug 11, 2026
Merged

perf(logging): eliminate hot-path allocations#60
park285 merged 14 commits into
mainfrom
agent/optimize-logging-hot-paths

Conversation

@park285

@park285 park285 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

배경

chat-bot-go-kakao, hololive-bot, twentyq-bot이 공통으로 사용하는 pkg/logging의 enabled-log 경로를 프로파일링했습니다. 실제 비용의 핵심은 slog의 primitive attr 처리보다 다음 공통 wrapper 비용이었습니다.

  • 문자열 기반 private context key가 context.Value 호출마다 interface boxing allocation을 발생
  • ContextAttrs와 event/context/user attrs 병합을 위한 임시 slice 2개 생성
  • wrapper의 Enabled 호출 후 Logger.LogAttrs가 다시 Enabled를 호출
  • 깨끗한 slog.Group도 sanitizer가 매번 새 group/slice로 재구축

변경 사항

  • Log 계열이 level gate 뒤 slog.Record를 직접 구성하고 handler에 전달하도록 변경
    • Enabled는 정확히 한 번만 호출
    • Record의 inline attr 저장소를 사용해 일반적인 5개 이하 attr 경로에서 임시 slice 제거
    • runtime.Callers로 기존 AddSource 계약을 유지하고 direct wrapper의 실제 호출 위치 기록
  • private context key를 allocation 없는 작은 custom integer key로 변경
  • context attr을 Record에 직접 추가하는 내부 fast path 추가
  • sanitizer의 key 정규화를 attr당 한 번으로 통합
  • group sanitizer를 copy-on-write로 변경
    • 깨끗한 group은 원본 attr을 그대로 통과
    • 첫 변경 지점에서만 slice를 만들고 이후 항목을 정제
    • 호출자 소유 group은 변경하지 않음
  • allocation, Enabled 호출 횟수, source caller, key 정규화, sanitizer copy-on-write 동작을 회귀 테스트로 고정

측정 결과

격리된 pkg/logging hot path를 로컬 Go 1.23.2, Linux/amd64에서 반복 측정했습니다. 0-allocation 계약은 별도로 Go 1.26.5 CI에서도 통과했습니다.

경로 변경 전 변경 후
event + attr 2개, 빈 context 467–476 ns/op, 352 B/op, 6 allocs/op 267–278 ns/op, 0 B/op, 0 allocs/op
runtime/request ID + event + attr 2개 531–557 ns/op, 432 B/op, 6 allocs/op 318–345 ns/op, 0 B/op, 0 allocs/op
깨끗한 group sanitize 964–979 ns/op, 480 B/op, 7 allocs/op 406–410 ns/op, 0 B/op, 0 allocs/op

동기 파일 lane, source 값 복사, redaction 정책은 내구성·호출자 불변성·보안 의미가 있으므로 완화하지 않았습니다.

호환성

  • exported API signature 변경 없음
  • JSON 필드, level filtering, redaction 및 privacy masking 계약 유지
  • handler error를 반환하지 않는 기존 slog.Logger logging API 동작 유지
  • ContextAttrs 순서 유지: runtime → component → request_id → job_id
  • WithAttrs/WithGroup sanitizer 동작 유지
  • 호출자가 넘긴 attr/group/source 값은 변경하지 않음

검증

로컬에서 현재 package 소스를 재구성해 다음을 통과시켰습니다.

  • go test ./pkg/logging
  • go vet ./pkg/logging
  • go test -race -count=1 ./pkg/logging

GitHub Actions CI #388도 Go 1.26.5에서 모두 통과했습니다.

  • workflow/release/SQL ownership gates
  • gofmt
  • go vet ./...
  • golangci-lint
  • deterministic guard allocation ceilings
  • go test -race -count=1 ./...

park285 added 14 commits August 11, 2026 14:33
AddSource가 켜진 formatter는 record마다 *slog.Source를 두 번 할당하고
slog이 그것을 3-attr group으로 전개하면서 또 할당했다. alloc 프로파일에서
이 세 지점이 실제 출력 경로 할당의 약 80%를 차지했다.

shortenSource가 group 대신 "dir/file.go:line" 한 문자열을 돌려주도록 바꿔
Source 재할당과 group 전개를 함께 제거한다. PC 0 record는 빈 Source를 낳으므로
빈 Attr을 돌려주는 가드를 둔다. 이 가드가 없으면 ":0"이 실려, 지금까지 slog이
통째로 생략하던 합성 source가 async summary record에 되살아난다.

JSON 출력 계약이 바뀐다. source가 객체에서 문자열이 되고 function 필드가
빠진다. 절대 경로 미노출과 line 보존은 그대로다.
LogAndWrapError와 LogWarnWithErrorAttrs는 ErrorAttrs 결과와 호출자 attr을
mergedAttrs로 합쳐 넘겼다. log가 방금 없앤 병합 slice가 소비자들이 실제로
쓰는 진입점에 그대로 남아 있었다.

logWith가 두 attr 묶음을 따로 받아 Record에 직접 넣도록 하고, runtime.Callers
skip을 상수로 분리한다. helper 경유 경로의 source가 helper 본문이 아니라 실제
호출 지점을 가리키게 되는 것이 부수 효과다.

alloc 상한은 race 빌드에서 값이 달라지므로 promptguard와 같은 !race 파일로
분리하고, CI가 race 없는 별도 스텝에서 돌리도록 확장한다. 이 스텝이 없으면
새 상한은 CI에서 아예 실행되지 않는다.
shortenSource와 skip 상수 블록에서 시그니처와 아래 주석이 이미 말해주는
설명을 덜어낸다. PC 0 가드와 프레임 체인처럼 코드로 확인할 수 없는 설명은
그대로 둔다.
@park285
park285 marked this pull request as ready for review August 11, 2026 08:26
@park285

park285 commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

소비자 3개 저장소를 이 브랜치의 pkg/logging에 대해 교차 검증했습니다. PR CI는 shared-go 자체만 보므로 그 바깥을 확인한 결과입니다.

  • go buildshared-go, iris-client-go, hololive-bot(root/admin-dashboard/hololive-shared/api/alarm-worker/youtube-producer), chat-bot-go-kakao, twentyq-bot 전 모듈 exit 0
  • go testchat-bot-go-kakao, twentyq-bot, hololive-bot 전 모듈 exit 0
  • go test -race ./pkg/logging/... — pass

pkg/logging 직접 소비는 hololive-bot 48파일, twentyq-bot 5파일, chat-bot-go-kakao 2파일입니다.

@park285
park285 merged commit 3280420 into main Aug 11, 2026
2 checks passed
@park285
park285 deleted the agent/optimize-logging-hot-paths branch August 11, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant