Parca

3편. parca-agent 내부 구조 분석 – eBPF 기반 수집 메커니즘

SysBPF 2025. 12. 18. 19:49

parca-agent 내부 구조 분석

eBPF 기반 수집 메커니즘

Parca를 “설치해 보는 도구”가 아니라
신뢰할 수 있는 성능 분석 도구로 받아들이기 위해서는
반드시 한 번은 짚고 넘어가야 할 질문이 있다.

parca-agent는 정확히 무엇을,
어떤 방식으로,
얼마나 자주 수집하는가?

이번 편에서는 이 질문에 답한다.


parca-agent의 역할 다시 정리

먼저 역할을 다시 명확히 하자.

parca-agent는:

  • CPU 사용량을 계산하지 않는다
  • 병목을 판단하지 않는다
  • Flamegraph를 만들지 않는다

parca-agent가 하는 일은 단 하나다.

“CPU가 인터럽트된 순간의
실행 스택을 샘플링한다.”

모든 분석은
이 단순한 행위의 반복 위에 쌓인다.


전체 수집 흐름 한눈에 보기

 

 

parca-agent의 내부 동작을 흐름으로 단순화하면 다음과 같다.

CPU 주기적 인터럽트
        ↓
perf_event 발생
        ↓
eBPF 프로그램 실행
        ↓
유저 스택 + 커널 스택 수집
        ↓
stack trace ID 생성
        ↓
user space(agent)로 전달
        ↓
protobuf 직렬화
        ↓
Parca Server 전송

 

핵심은
**“이 모든 과정이 커널 안전성 검증 하에서,
짧은 시간 안에 끝난다”**는 점이다.


perf_event 기반 샘플링

왜 타이머가 아니라 perf_event 인가?

parca-agent는 CPU profiling을 위해
Linux의 perf_event 서브시스템을 사용한다.

이 방식의 장점은 다음과 같다.

  • 하드웨어 성능 이벤트와 자연스럽게 연계
  • 커널 스케줄링과 정합성 유지
  • 이미 검증된 고성능 인터페이스

개념적으로는 다음과 같다.

“CPU가 일정 주기마다
‘지금 뭐 하고 있니?’ 하고 묻는다”


perf_event 설정 개념 예시 (단순화)

struct perf_event_attr attr = {
    .type = PERF_TYPE_SOFTWARE,
    .config = PERF_COUNT_SW_CPU_CLOCK,
    .sample_freq = 19,
    .freq = 1,
};

 

 

  • sample_freq = 19
    → 초당 약 19회 샘플링
  • 이 값이 parca-agent의 기본 철학을 보여준다
    (정밀함보다 지속성)

 

eBPF 프로그램은 언제 실행되는가?

perf_event가 발생하면
연결된 eBPF 프로그램이 실행된다.

이 eBPF 프로그램은 매우 짧고 단순해야 한다.

이유는 명확하다.

  • 커널 컨텍스트에서 실행
  • verifier 제한
  • 실행 시간 상한 존재

스택 트레이스 수집의 핵심

1. 커널 스택 수집

커널 스택은 비교적 단순하다.

bpf_get_stackid(ctx, &stack_map, BPF_F_KERNEL_STACK);

 

 

  • 현재 CPU가 커널 모드라면
  • syscall, scheduler, lock 경로 등이 잡힌다

2. 유저 스택 수집

유저 스택은 훨씬 까다롭다.

bpf_get_stackid(ctx, &stack_map, BPF_F_USER_STACK);

 

문제는 여기서 발생한다.

  • 프레임 포인터가 없을 수 있음
  • JIT 언어 (Go, Java)는 스택 구조가 다름
  • 심볼 해석은 커널에서 불가능

parca-agent는 이 문제를 역할 분리로 해결한다.

eBPF는 “주소만” 수집
심볼 해석은 user space에서


eBPF Map의 역할

 

eBPF 프로그램은 다음과 같은 map을 사용한다.

  • stack trace map
  • count / histogram map
  • temporary aggregation map

개념적 구조는 다음과 같다.

struct {
    __uint(type, BPF_MAP_TYPE_STACK_TRACE);
    __uint(max_entries, 16384);
} stack_map SEC(".maps");

 

이 map은:

  • 동일한 call stack을 하나의 ID로 관리
  • 중복 저장 방지
  • 메모리 사용량 제한

user space(parca-agent)에서 하는 일

eBPF는 여기까지다.
이제 baton은 user space로 넘어간다.

parca-agent(user space)는 다음을 수행한다.

  1. stack ID 수신
  2. 주소 목록 조회
  3. 심볼 정보 매핑
  4. label(node, pid, container 등) 부착
  5. protobuf 변환
  6. 서버 전송

중요한 점은:

agent는 “분석”을 하지 않는다

그저 구조를 보존한 채 전달할 뿐이다.


샘플링 기반이라는 것의 의미

여기서 흔히 나오는 질문이 있다.

“샘플링이면 정확하지 않은 것 아닌가?”

답은 다음과 같다.

  • 단일 샘플 → 의미 없음
  • 수천, 수만 샘플 → 통계적으로 매우 강력

Parca는:

  • 순간의 정확성 ❌
  • 시간 축의 누적 정확성 ⭕

을 선택했다.


오버헤드는 왜 낮은가?

parca-agent의 오버헤드가 낮은 이유는 구조적이다.

  • 항상 같은 짧은 eBPF 코드
  • 조건 분기 거의 없음
  • 락 없음
  • 메모리 접근 제한적
  • 낮은 샘플링 빈도

일반적인 운영 환경에서:

  • CPU 오버헤드: 수 % 미만
  • latency 영향: 측정 불가 수준
  • 장애 유발 가능성: 극히 낮음

이 때문에 상시 활성화가 가능하다.


왜 tracing이 아니라 profiling인가?

중요한 철학적 차이도 짚고 가자.

구분TracingProfiling
접근 이벤트 기반 샘플링 기반
비용 상대적으로 높음 낮음
정밀도 매우 높음 통계적
지속성 제한적 상시 가능

Parca는 의도적으로 profiling을 선택했다.

“항상 켜 두어야 의미가 있기 때문”


이 구조가 주는 실무적 의미

이제 Parca가 왜 이런 구조를 가지는지 보인다.

  • 커널 안정성 유지
  • 운영 환경 적용 가능
  • 장애 시 blame 없음
  • 장시간 비교 분석 가능

즉, parca-agent는

“운영 서버에 깔아도
죄책감이 들지 않는 eBPF 프로그램”

이라는 목표에 충실하다.


다음 편 예고

다음 편에서는
이렇게 수집된 데이터가 어떻게 Flamegraph로 변하는지,
그리고 사람이 어떻게 읽어야 하는지를 다룬다.

  • Flamegraph의 진짜 의미
  • Inclusive / Exclusive 비용
  • 흔히 저지르는 해석 실수
  • “보이는 것”과 “중요한 것”의 차이

다음 편

4편. Flamegraph 제대로 읽기 – Parca UI 해석 가이드