본문으로 건너뛰기
피드

리눅스 커널에서 fork() + exec()를 넘어서려는 새 논의가 나옴

backend 약 8분
vote
0
댓글
북마크

리눅스 커널 쪽에서 반복적인 프로세스 실행 비용을 줄이기 위한 spawn template 패치셋이 논의됐지만, 리뷰 과정에서 더 근본적인 방향으로 posix_spawn()을 제대로 지원하자는 흐름으로 바뀜. 초기 벤치마크 개선폭은 약 2%였고, 최종적으로는 pidfd 기반으로 빈 프로세스를 만들고 설정하는 API가 더 낫다는 의견이 힘을 얻음.

  • 1

    fork() 뒤에 바로 exec()를 호출하는 패턴은 복사한 프로세스 상태를 곧바로 버리기 때문에 구조적으로 비효율적임

  • 2

    제안된 spawn template은 같은 실행 파일을 반복 실행하는 애플리케이션에서 실행 정보를 캐시해 비용을 줄이려는 접근임

  • 3

    벤치마크상 개선폭은 약 2%로 크진 않지만 Git 같은 실행 파일을 반복 호출하는 패턴에서는 의미가 있을 수 있음

  • 4

    리뷰어들은 비용의 핵심이 fork() 자체에 있으므로 깨끗한 새 프로세스를 만드는 방향이 더 낫다고 지적함

  • 5

    Christian Brauner는 pidfd_open()과 pidfd_config() 같은 pidfd 기반 빌더 API로 user space posix_spawn() 구현을 지원하는 방향을 제안함

오래된 fork() + exec() 패턴이 다시 도마 위에 오름

  • 리눅스에서 프로세스를 띄우는 고전 패턴은 fork()로 자식 프로세스를 만들고, 곧바로 exec()로 실행 파일을 바꾸는 방식임

    • fork()는 부모 프로세스의 메모리와 상태를 자식 쪽에 복사하는 비싼 시스템 호출임
    • 커널이 수년간 최적화를 많이 했지만, 구조적으로 “일단 복사하고 나중에 버리는” 비용은 남아 있음
    • 특히 fork() 직후 exec()가 오면, 애써 준비한 자식 프로세스 메모리 상태 대부분을 바로 폐기하게 됨
  • 이번 패치셋은 이 반복 비용을 줄이려고 spawn template이라는 아이디어를 제안함

    • 예를 들어 어떤 프로그램이 Git 실행 파일을 반복 호출해서 저장소 정보를 읽는 상황을 상상하면 됨
    • 매번 같은 실행 파일을 띄운다면, 실행 파일 관련 준비 정보를 템플릿으로 만들어 캐시해두자는 접근임

spawn template이 하려던 일

  • 제안된 API는 spawn_template_create()로 실행 파일 템플릿을 먼저 만드는 구조임

    • 실행 파일은 파일 디스크립터(execfd)나 절대 경로(filename) 중 하나로 지정함
    • 커널은 해당 실행 파일을 열고, 이후 더 빨리 실행하는 데 필요한 정보를 캐시함
    • 반환값은 템플릿을 가리키는 파일 디스크립터임
  • 실제 실행 시점에는 spawn_template_spawn()에 인자와 환경, 파일 디스크립터 조작 같은 정보를 넘김

    • argv는 프로그램 인자 목록, envp는 환경 변수 목록임
    • actions에는 자식 프로세스에서 파일 디스크립터를 닫거나 복제하거나, 작업 디렉터리를 바꾸거나, 시그널 처리를 바꾸는 작업이 들어감
    • 예를 들어 자식에서 fd 4를 닫고 싶으면 SPAWN_TEMPLATE_ACTION_CLOSE와 fd 4를 액션에 넣는 식임
sequenceDiagram
    participant 앱 as 애플리케이션
    participant 커널 as 리눅스 커널
    participant 템플릿 as Spawn 템플릿
    participant 자식 as 새 프로세스
    앱->>커널: spawn_template_create()
    커널->>템플릿: 실행 파일 정보 캐시
    앱->>커널: argv, envp, actions 전달
    커널->>템플릿: 캐시된 실행 정보 조회
    커널->>자식: spawn_template_spawn()으로 실행
  • 성능 개선은 약 2%로 제시됨
    • 숫자만 보면 “겨우?” 싶을 수 있음
    • 하지만 같은 실행 파일을 매우 자주 띄우는 워크로드라면 2%도 누적 비용에서는 의미가 있을 수 있음
    • 커널 패치 논의에서는 이런 작은 비용도 충분히 진지하게 다뤄짐

중요

> 이 패치셋의 벤치마크 개선폭은 약 2%였음. 큰 숫자는 아니지만, 자식 프로세스를 자주 띄우는 빌드 도구나 저장소 분석 도구에서는 이런 비용이 반복해서 쌓임.

리뷰는 더 근본적인 방향으로 틀어짐

  • Mateusz Guzik은 fork + exec 관용구 자체가 문제라고 강하게 지적함

    • spawn template은 exec 쪽 일부를 최적화하지만, 정작 비용의 큰 부분인 fork()는 그대로 둠
    • 그의 핵심 주장은 현재 프로세스를 복사하지 말고, 처음부터 깨끗한 새 프로세스를 만드는 방향으로 가야 한다는 것임
  • Christian Brauner도 목표 자체에는 긍정적이었지만, API 방향은 pidfd 위에 쌓는 쪽을 제안함

    • pidfd_open()에 빈 프로세스를 만드는 옵션을 추가하는 식의 구상을 언급함
    • 이후 pidfd_config() 같은 새 시스템 호출로 환경, 실행 이미지, 설정을 단계적으로 구성하는 방식임
    • fsconfig()가 파일 시스템 설정을 빌더처럼 구성하는 것과 비슷한 발상으로 보면 됨
  • 중요한 목표는 user space에서 제대로 된 posix_spawn()을 구현할 수 있게 하는 것임

    • posix_spawn()은 fork()/exec() 패턴을 대체하기 좋은 인터페이스임
    • 하지만 현재 구현은 내부적으로 fork()와 exec()를 감추는 경우가 많아서 근본적인 비용을 없애진 못함
    • 커널이 빈 프로세스 생성과 설정을 네이티브하게 지원하면, posix_spawn()이 진짜 대체재가 될 수 있음
  • 결론적으로 spawn template 자체는 커널에 들어가지 않을 가능성이 커짐

    • 제안자인 Chen도 Brauner가 스케치한 pidfd 기반 API가 더 나은 방향이라는 데 동의함
    • 앞으로의 작업은 spawn template보다는 proper posix_spawn() 구현을 가능하게 하는 쪽으로 갈 전망임

기술 맥락

  • 여기서 핵심 선택은 “기존 fork()/exec() 경로를 살짝 빠르게 만들 것인가, 프로세스 생성 모델을 새로 설계할 것인가”예요. spawn template은 앞쪽 선택에 가깝고, pidfd 기반 빈 프로세스 생성은 뒤쪽 선택에 가까워요.

  • fork()가 비싼 이유는 새 프로그램을 실행하려는 순간에도 일단 현재 프로세스를 복제하는 모델에서 출발하기 때문이에요. copy-on-write 같은 최적화가 있어도, 커널이 다뤄야 하는 프로세스 상태와 검증 경로는 여전히 복잡하거든요.

  • pidfd 기반 접근이 매력적인 이유는 프로세스를 파일 디스크립터처럼 안정적으로 붙잡고, 실행 이미지나 환경 같은 설정을 단계적으로 넣을 수 있기 때문이에요. 이렇게 되면 user space의 posix_spawn()이 fork()/exec()를 감춘 래퍼가 아니라, 커널 기능을 제대로 쓰는 인터페이스가 될 수 있어요.

  • 이 변화는 셸 하나 빠르게 띄우는 수준보다 범위가 넓어요. Git을 반복 호출하는 도구, 빌드 시스템, 서버 런처처럼 프로세스를 자주 만드는 프로그램에서는 작은 시스템 호출 비용과 설계 차이가 전체 지연 시간에 계속 누적되거든요.

이 논의의 재미는 “2% 빠른 fork/exec 캐시”가 아니라, 리눅스가 오래된 프로세스 생성 관성을 어디까지 뜯어고칠 수 있느냐에 있음. 서버, 빌드 도구, Git 호출 많은 프로그램처럼 자식 프로세스를 자주 띄우는 시스템에서는 작은 syscall 설계 차이가 은근히 누적됨.

댓글

댓글

댓글을 불러오는 중...

backend

스타트업이 Postgres로 버티려면 알아야 할 생존 팁

Hatchet이 2년 동안 프로덕션 Postgres를 굴리며 겪은 문제를 스키마, 쿼리, 마이그레이션, 커넥션, 쿼리 플래너, autovacuum, 파티셔닝까지 한 번에 정리한 글이다. 핵심은 “인덱스만 붙이면 끝”이 아니라, 락·통계·배치·부풀어 오른 테이블까지 운영 관점에서 같이 봐야 한다는 점이다.

backend

Julia Evans가 Django로 ‘2010년식 웹사이트’를 만들며 좋았던 것들

Julia Evans가 프론트엔드 중심의 작은 앱 대신, SQL 데이터베이스와 서버 렌더링 HTML을 쓰는 Django 방식으로 웹사이트를 만들며 느낀 장점과 불편함을 정리했다. QuerySet, 템플릿 필터, 자동 마이그레이션은 생산성을 높였고, 클래스 기반 뷰와 성능 설정은 여전히 고민거리로 남았다. 특히 템플릿 캐시 설정 하나로 처리량이 초당 2-3 요청에서 약 12 요청으로 오른 대목이 실전적이다.

backend

클릭하우스가 PgBouncer 처리량을 4배로 끌어올린 방법

ClickHouse Managed Postgres 팀은 단일 스레드인 PgBouncer를 여러 프로세스로 띄우고 `so_reuseport`와 피어링(peering)을 조합해 16 vCPU 머신을 제대로 쓰게 만들었다. 동일한 AWS 환경에서 단일 프로세스는 약 8.7만 TPS 근처에서 막혔지만, 16개 프로세스 구성은 약 33.6만 TPS까지 올라갔다.

backend

SQLite 쓸 거면 STRICT 테이블을 기본값으로 보자는 주장

SQLite의 STRICT 테이블은 INTEGER 컬럼에 문자열이 들어가는 식의 타입 실수를 막아주는 기능이다. 글쓴이는 SQLite 특유의 유연한 타입 시스템보다, 실수는 빨리 터뜨리는 쪽이 실무에서 낫다고 주장한다. 다만 기존 테이블을 바로 STRICT로 바꾸기 어렵고, SQLite 3.37.0 이상에서만 쓸 수 있다는 제약도 있다.

backend

왜 아직도 Lisp를 배워볼 만한가

Lisp는 괄호가 많은 이상한 언어처럼 보이지만, 핵심은 문법이 아니라 언어 자체를 확장할 수 있다는 점에 있음. 매크로, 코드-데이터 동일성, REPL 중심 개발이 합쳐지면 프로그램을 '작성'한다기보다 살아 있는 시스템을 계속 진화시키는 방식으로 개발하게 됨.