Hacker News

Dropping eBPF CPU Cost by About 90% with Memoization (Not AI Gen)

메모이제이션으로 eBPF CPU 비용 약 90% 절감하기 (AI 생성 글 아님)

eBPF 기반 파일 접근 제어 보안 에이전트에서, 정책 적용(allow/deny) 자체보다 어떤 정책이 적용되는지 찾는 경로 탐색이 CPU를 가장 많이 쓴다는 프로파일링 결과를 바탕으로 inode 기반 캐시를 도입한 사례입니다. 동일 파일 20만 회 오픈 벤치마크에서 커널 사이클이 280억에서 30.3억으로, 약 90% 감소했습니다.

본문

eBPF로 파일 오픈을 감시하고 경로 기반 정책을 적용하는 보안 에이전트를 처음부터 빠르게 설계해 온 저자가, 최근 프로파일링을 통해 예상 밖의 병목을 발견하고 메모이제이션으로 커널 CPU 비용을 약 90% 줄인 과정을 정리한 글입니다. 핵심은 정책 판정(allow/deny) 자체가 아니라, 주어진 파일 오픈에 어떤 정책이 적용되는지 찾아내는 작업이 가장 비쌌다는 점입니다.

왜 느렸는가 — dentry 역추적의 반복

정책이 경로 기반이기 때문에, 에이전트는 LSM 훅이 file open에서 발동할 때마다 경로를 재구성하고 부모 dentry를 거슬러 올라가며 각 단계에서 해당 경로에 매칭되는 정책이 있는지 확인한 뒤 결과를 합쳐 최종 정책을 만듭니다. 예를 들어 Postgres가 /var/lib/postgres만 접근하도록 제한하는 정책이 있을 때, /var/lib/postgres/data/base/ 아래 여러 파일을 반복해서 열면 매번 전체 경로 순회를 다시 수행해야 합니다. 저자는 이 비효율적인 경로 순회를 '느린 경로(slow path)'라고 부릅니다.

캐시 설계 — inode 기반 LRU 해시

처음에는 dentry를 캐시 키로 쓰는 것을 고려했지만, dentry는 포인터라서 eBPF 맵에 저장할 수 없고, 내용을 구조체로 복사하면 키가 너무 무거워집니다. 그래서 inode 기반 캐시를 선택했고, 키는 마운트 네임스페이스 ID, 마운트 ID, inode 번호의 세 필드로 구성했습니다. inode 번호는 특정 마운트 트리 안에서만 유일하기 때문에 단독으로는 못 쓰고, 네임스페이스가 다른 환경에서 캐시 엔트리를 잘못 재사용하는 것도 막아야 하기 때문입니다. 값에는 정책 비트마스크의 비트 위치인 access_index와 캐시 상태(state)를 담고, BPF_MAP_TYPE_LRU_HASH로 최대 10,000개 엔트리를 유지합니다. 조회에 히트하면 캐시된 결과로 바로 판정하고, 미스면 느린 경로로 계산한 뒤 결과를 저장합니다.

성능 — 280억에서 30.3억 사이클로

같은 파일을 20만 번 여는 벤치마크에서 커널 사이클이 280억에서 30.3억으로 줄었습니다. 캐시 도입 전에는 스택에서 tail_call_security_check가 89.2%, is_restricted_filepath가 81.9%, path_check_callback이 63.7%를 차지했지만, 캐시 후에는 is_restricted_filepath와 path_check_callback이 각각 약 0.02%로 줄어 flamegraph에서 사실상 사라졌습니다. 측정은 perf의 cycles:k 이벤트로 커널 측 CPU 비용을 프로파일링한 결과입니다.

엣지 케이스 — 하드링크와 정확성

하드링크처럼 여러 경로가 하나의 inode를 공유하는 경우가 문제가 됩니다. 경로가 다른데 같은 캐시 엔트리를 쓰면 잘못된 정책이 적용될 수 있기 때문입니다. 해결책은 우회에 가깝지만 단순합니다. inode의 링크 카운트(i_nlink)를 읽어 1보다 크면 캐시를 사용하지 않고 느린 경로로 폴백합니다. 캐시 커버리지를 일부 포기하는 트레이드오프지만, 정확한 결과가 캐시 성능보다 중요하다는 판단입니다. 또한 캐시가 완전히 내부 구현이라 사용자의 정책을 바꾸지 않고도 에이전트가 빨라진다는 점을 강조합니다. 코드는 github.com/bomfather/agent에 오픈소스로 공개되어 있습니다.

Hacker News 반응

  • @mrbluecoat — 'AI 생성 아님'을 강조하면서 정작 솔루션은 'Agent'라고 부르는 게 재밌다고 농담합니다.
  • @jaggederest — '에이전트'는 특정 목적을 위해 상주하는 데몬을 가리키는 오래된 용어라며, 이제는 무조건 AI를 떠올리게 된 것이 다소 아쉽다고 말합니다.
  • @cstrahan — agent가 이미 오래 전부터 확립된 용어임을 보여 주는 여러 레퍼런스 링크를 공유합니다.
  • @jaggederest — 2010년 New Relic Ruby 에이전트 메인테이너였다며, 고객 인프라에 호스팅되지만 벤더가 관리하는 소프트웨어였기에 agent라 불렀다고 경험을 덧붙입니다.
  • @27183 — '살인 면허나 비밀 장치는 없었다'는 말에, 빌드·배포·배포망이 몰래 무언가 주입했을 수도 있다는 농담으로 받아칩니다.

원문: nathannaveen.dev / 번역·요약: Trawling