Workspace IndexAlgorithms › Day 47

Observing Production with eBPF TODO

Algorithms · Day 47 / 100 · C. Concurrency & Performance Engineering (Day 36-51)

Concept

eBPF is a sandboxed VM that runs safely inside the kernel. When a user-written program is loaded into the kernel, a verifier statically checks termination and memory-access safety; only programs that pass are JIT-compiled and attached to kprobes, uprobes, tracepoints, perf events, network hooks, and the like. The program and userspace exchange data through shared structures called maps (hash maps, arrays, ring buffers, etc.). The key benefit is being able to observe a running system's internal events without writing a new kernel module or restarting/recompiling the application. Higher-level tools like bpftrace and BCC wrap this whole process down to a one-line script.

It's nearly the only way to trace a latency spike or a specific syscall bottleneck in production that won't reproduce elsewhere, without a code change or redeploy, and it reaches layers that application logs never touch.

Code & Formula

# eBPF로 프로덕션 관측 — 커널 프로브가 이벤트를 map에 쌓고, 유저스페이스가 읽어 히스토그램을 낸다.
# 실제 커널 훅 대신 "probe가 이벤트를 map에 기록한다"는 구조만 순수 파이썬으로 흉내낸다.

import random
from collections import defaultdict

random.seed(1)

class EbpfMap:
    """kprobe/tracepoint가 기록하는 공유 map(dict)을 흉내"""
    def __init__(self):
        self.hist = defaultdict(int)  # latency bucket(us, 로그 스케일) -> count

    def record(self, latency_us):
        bucket = 1
        while bucket * 2 <= latency_us:
            bucket *= 2
        self.hist[bucket] += 1

def bpftrace_like_probe(events, ebpf_map):
    """실제로는 커널이 syscall 진입/종료 시각차를 계산해 넣어주는 부분을 시뮬레이션"""
    for latency_us in events:
        ebpf_map.record(latency_us)

# 유휴 상태: 대부분 짧은 지연
idle_events = [max(1, int(random.gauss(50, 15))) for _ in range(200)]
# 부하 상태: 디스크 I/O 경합으로 꼬리가 길어짐
loaded_events = [max(1, int(random.gauss(50, 15))) for _ in range(200)]
loaded_events += [int(random.gauss(4000, 800)) for _ in range(20)]  # I/O 경합 스파이크

idle_map = EbpfMap()
loaded_map = EbpfMap()
bpftrace_like_probe(idle_events, idle_map)
bpftrace_like_probe(loaded_events, loaded_map)

def print_hist(name, m):
    print(f"--- {name} (syscall latency, us, log2 bucket) ---")
    for bucket in sorted(m.hist):
        print(f"  <= {bucket:5d}us : {'#' * m.hist[bucket]} ({m.hist[bucket]})")

print_hist("idle", idle_map)
print_hist("loaded", loaded_map)
print("\n부하 상태에서 4000us대 버킷이 새로 등장 -> 재배포 없이 I/O 경합 구간을 특정.")

Exercise

Use a one-line bpftrace script to pull a histogram of disk I/O latency or syscall call counts for a specific process, and compare it under load versus idle.

Practical Connection

When sync stalls on a blockchain node, or tail latency in Verex's backend API, come from the filesystem or network stack rather than application code, this lets you narrow down the cause with evidence instead of guessing.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

eBPF로 프로덕션 관측 TODO

Algorithms · Day 47 / 100 · C. 동시성·성능 엔지니어링 (Day 36–51)

개념

eBPF는 커널 안에서 안전하게 실행되는 샌드박스 VM이다. 사용자가 작성한 작은 프로그램을 커널에 로드하면 verifier가 종료성과 메모리 접근 안전성을 정적으로 검증하고, 통과한 프로그램만 JIT 컴파일되어 kprobe, uprobe, tracepoint, perf 이벤트, 네트워크 훅 등에 붙어 실행된다. 프로그램과 유저스페이스는 map이라는 공유 자료구조(해시맵, 배열, 링버퍼 등)로 데이터를 주고받는다. 커널 모듈을 새로 짜거나 애플리케이션을 재시작·재컴파일하지 않고도 실행 중인 시스템의 내부 이벤트를 관측할 수 있다는 것이 핵심 이점이다. bpftrace나 BCC 같은 상위 도구가 이 과정을 스크립트 한 줄 수준으로 감싸 준다.

프로덕션에서 재현되지 않는 지연 스파이크나 특정 syscall 병목을 코드 수정·재배포 없이, 그리고 애플리케이션 로그에 없는 계층까지 내려가 볼 수 있는 거의 유일한 수단이다.

코드 · 수식

# eBPF로 프로덕션 관측 — 커널 프로브가 이벤트를 map에 쌓고, 유저스페이스가 읽어 히스토그램을 낸다.
# 실제 커널 훅 대신 "probe가 이벤트를 map에 기록한다"는 구조만 순수 파이썬으로 흉내낸다.

import random
from collections import defaultdict

random.seed(1)

class EbpfMap:
    """kprobe/tracepoint가 기록하는 공유 map(dict)을 흉내"""
    def __init__(self):
        self.hist = defaultdict(int)  # latency bucket(us, 로그 스케일) -> count

    def record(self, latency_us):
        bucket = 1
        while bucket * 2 <= latency_us:
            bucket *= 2
        self.hist[bucket] += 1

def bpftrace_like_probe(events, ebpf_map):
    """실제로는 커널이 syscall 진입/종료 시각차를 계산해 넣어주는 부분을 시뮬레이션"""
    for latency_us in events:
        ebpf_map.record(latency_us)

# 유휴 상태: 대부분 짧은 지연
idle_events = [max(1, int(random.gauss(50, 15))) for _ in range(200)]
# 부하 상태: 디스크 I/O 경합으로 꼬리가 길어짐
loaded_events = [max(1, int(random.gauss(50, 15))) for _ in range(200)]
loaded_events += [int(random.gauss(4000, 800)) for _ in range(20)]  # I/O 경합 스파이크

idle_map = EbpfMap()
loaded_map = EbpfMap()
bpftrace_like_probe(idle_events, idle_map)
bpftrace_like_probe(loaded_events, loaded_map)

def print_hist(name, m):
    print(f"--- {name} (syscall latency, us, log2 bucket) ---")
    for bucket in sorted(m.hist):
        print(f"  <= {bucket:5d}us : {'#' * m.hist[bucket]} ({m.hist[bucket]})")

print_hist("idle", idle_map)
print_hist("loaded", loaded_map)
print("\n부하 상태에서 4000us대 버킷이 새로 등장 -> 재배포 없이 I/O 경합 구간을 특정.")

연습

bpftrace 한 줄짜리로 특정 프로세스의 디스크 I/O 지연이나 특정 syscall 호출 횟수를 히스토그램으로 뽑고, 부하를 준 상태와 유휴 상태를 비교해 보라.

실무 · Verex 연결

블록체인 노드의 동기화 정체나 Verex 백엔드 API의 꼬리 지연이 애플리케이션 코드가 아니라 파일시스템·네트워크 스택에서 나올 때, 추측 대신 증거로 원인을 좁힐 수 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 46. 벤치마크 방법론48. 분산 트레이싱과 샘플링 전략 →