AI Briefing

Ruby Path Methods 최적화

·2026.04.19 05:42

핵심 내용

Bootsnap과 Ruby의 `require`·`Dir`·`File.join` 병목을 2배 수준으로 줄인 최적화 사례.

자세히 보기

Intercom 모노리스의 CI setup time을 줄이기 위해 Ruby 부트 성능을 파고들며, require 경로 탐색과 디렉터리 스캔의 비용이 얼마나 큰지 분석한다.

Ruby는 require 시 $LOAD_PATH를 선형 탐색하며 파일 존재 여부를 반복 확인하기 때문에, gem이 늘수록 부팅 비용이 O(N*M) 형태로 커진다. 이 문제를 Bootsnap은 load path cache로 완화한다. 한 번 $LOAD_PATH를 훑어 requirable 파일 맵을 만들고, 이후에는 해시 조회로 상대 경로를 절대 경로로 바꿔 Ruby의 느린 탐색을 우회한다.

다만 캐시는 무효화가 어렵다. Bootsnap은 디렉터리 mtime을 기록해 재검증하지만, 하위 파일 변경이 부모 디렉터리 전체에 전파되지는 않으므로 여러 디렉터리를 재귀적으로 다시 확인해야 한다. CI에서 git 체크아웃은 mtime을 보존하지 않는 경우가 많아, 캐시 재사용보다 재스캔 성능이 중요해진다.

핵심 병목은 디렉터리 순회 중 매 항목마다 File.directory?를 호출하는 N+1 syscall 패턴이었다. Linux/BSD의 readdir(3)가 제공하는 d_type 정보를 Ruby가 Dir.foreach 블록에 노출하지 않아서 생긴 문제다. 저자는 이걸 해결하기 위해 Dir.scan API를 제안했고, 대안으로 Dir.foreach가 두 번째 인자에 타입 정보를 넘기게 하는 프로토타입도 만들었다.

초기 프로토타입은 디렉터리 재귀 탐색을 약 2배 빠르게 만들었다. 이후 nobu가 File::Stat 객체를 넘기는 더 자연스러운 설계를 제안했고, 결국 Ruby에는 새 메서드 Dir.scan 형태로 가는 쪽으로 정리됐다.

같은 영역에서 File.join도 다시 살펴보며, 가장 흔한 경우가 사실상 문자열 결합이라는 점에 주목한다. 단순한 인터폴레이션 구현과 비교한 벤치마크에서는 File.join이 더 느렸고, 이 역시 부팅 프로파일에서 볼 수 있는 개선 여지가 있었다. 결과적으로 Bootsnap은 약 3.2만 파일, 1만 개 리포지토리를 230ms에 스캔하게 되었고, 이전 구현의 500ms 대비 큰 개선을 얻었다.

이 한국어 요약은 AI가 자동으로 만들었습니다. 원문의 주장과 맥락은 원문에서 확인해 주세요. 저작권은 원저작자에게 있습니다.

AI 처리 방식을 확인하거나, 요약 오류와 출처 표기 문제, 삭제 요청을 문의 · 건의로 알려주세요.