Status | Authors | Coach | DRIs | Owning Stage | Created |
---|---|---|---|---|---|
proposed | devops secure | - |
GitLab Secret Detection ADR 003: 서브프로세스 내에서 스캔 실행
맥락
spike에서 Pre-receive Secret Detection을 위해 정규식을 평가하기 위해 수행된 과정에서 Ruby에서 RE2 라이브러리를 사용한 결과가 가장 뛰어났습니다. Ruby는 수용할 수 있는 정규식 성능을 가지고 있지만, 언어 제한으로 인해 메모리 소비가 늘어나며, 언어는 멀티 스레딩 및 Ractors(3.1+)를 지원하나 I/O 바운드 작업을 병렬로 실행하기에 적합하지만 CPU 바운드 작업에는 적합하지 않습니다.
임계 경로에서 Pre-receive Secret Detection 기능을 실행하는 것 중 하나는 스캔에 관여되는 정규식 작업으로 인한 메모리 소비, 특히 300개 이상의 정규식 기반 규칙 패턴을 커밋 블롭의 각 라인에서 실행할 때, 메모리가 커밋 블롭의 크기의 약 2-3배로 증가할 수 있습니다1. 스캔 작업이 완료되더라도 차지한 메모리가 가비지 콜렉터를 트리거할 때까지 해제되지 않으며, 결국 서버에서 메모리 부족 현상이 발생할 수 있습니다.
원래의 논의 이슈는 이러한 우려 사항과 더 많은 배경을 다루고 있습니다.
접근 방법
우리는 스캔을 메인 프로세스에서 fork된 별도의 프로세스 내에서 실행함으로써 메모리 소비 문제를 어느 정도 해결할 수 있습니다. 스캔이 완료되면 생성된 프로세스를 종료하여 메모리가 가비지 콜렉터를 트리거하는 대기를 기다리지 않고 즉시 OS로 반환되도록 합니다.
기술적 해결책
프로세스의 라이프사이클을 관리하는 동안 여러 시나리오를 고려해야 합니다. 그러지 않으면 제어할 수 없는 고아 프로세스가 생기며, 메모리를 보존하는 전체 목적을 무너뜨릴 수 있습니다. 우리는 부모 및 자식 프로세스 간의 통신, 종료 신호 처리 및 프로세스 수 제한을 쉽게 제어할 수 있는 Ruby 라이브러리인 Parallel
를 통해 이 부담을 완화시킵니다. 또한 병렬성을 지원하여 별도의 문제를 해결하는데에도 이 해결책이 적합합니다.
서브프로세스 내 작업 범위
새 프로세스를 생성하는 것에는 OS에서 추가적인 대기 시간이 발생하므로 어떤 작업이 서브프로세스에서 실행될지 결정하는 것이 중요합니다(파일 디스크립터 복사 등). 예를 들어, 각 블롭에서 스캔을 새로운 서브프로세스에서 실행하는 것은 주 프로세스에서 실행하는 것보다 약 2.5배 느립니다. 그러나 모든 블롭에서 스캔을 단워크플로에서 실행하는 것은 메모리가 빨리 해제되지 않기 때문에 실행이 어렵습니다.
Bucket Approach: 두 가지 극단 사이의 타협점은 누적 크기가 일정한 청크 크기(2MiB
in our case) 이상인 모든 블롭을 그룹화한 후 아래에 설명된 것처럼 각 그룹을 별도의 서브프로세스에서 실행하는 것입니다.
추가 내용
-
서브프로세스 내에서 작업을 실행하는 것은 위에서 언급된 문제에 대한 마법같은 해결책은 아닙니다. 메모리를 가비지 콜렉터를 통해 일반적인 프로세스보다 빠르게 해제함으로써 서버가 메모리에 찔려들지 않도록 지연시킨다고 할 수 있습니다. 심지어 이 접근 방법도 처리해야 할 요청이 너무 많아서 실패할 수 있습니다^.
-
프로세스 생성에는 항상 라이프사이클의 대기 시간 부담이 따릅니다. 더 작은 커밋에 대해서는 스캔 작업의 대기 시간이 주 프로세스에서 실행될 때보다 느릴 수 있습니다.
-
현재 요청당 생성되는 프로세스의 병렬성 요소 또는 생성된 프로세스의 수는 현재
5
processes)로 제한되어 있으며, 이상의 대기 중인 요청은 리소스 고갈을 피하기 위해 대기열에 있습니다.
^참조용 임계값이 곧 여기에 추가될 예정입니다.