BazelCon 2022는 11월 16~17일에 뉴욕과 온라인에서 개최됩니다.
지금 등록하기

영구 작업자

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

이 페이지에서는 영구 작업자를 사용하는 방법, 이점, 요구사항, 작업자가 샌드박스에 미치는 영향을 설명합니다.

영구 작업자는 Bazel 서버에서 시작하는 장기 실행 프로세스로, 실제 도구(일반적으로 컴파일러)를 둘러싼 래퍼로 작동하거나 도구 자체입니다. 지속적 작업자의 이익을 얻기 위해 도구는 컴파일 시퀀스를 지원해야 하며, 래퍼는 도구의 API와 아래에 설명된 요청/응답 형식 간에 변환해야 합니다. 동일한 빌드에서 --persistent_worker 플래그를 사용하여 호출하지 않고 동일한 작업자를 호출할 수 있으며 종료 시 작업자를 종료할 뿐만 아니라 도구를 적절하게 시작하고 알려야 합니다. 각 작업자 인스턴스는 <outputBase>/bazel-workers 아래에 별도의 작업 디렉터리(할당되지 않음)에 할당됩니다.

영구 작업자를 사용하는 것은 실행 전략으로, 시작 오버헤드를 줄이고 JIT 컴파일을 더 많이 실행하며 작업 실행에서 예를 들어 추상 구문 트리의 캐싱을 사용 설정합니다. 이 전략은 여러 요청을 장기 실행 프로세스에 전송하여 이러한 개선사항을 달성합니다.

영구 작업자는 자바, Scala, Kotlin 등을 포함한 여러 언어로 구현됩니다.

NodeJS 런타임을 사용하는 프로그램은 @bazel/worker 도우미 라이브러리를 사용하여 작업자 프로토콜을 구현할 수 있습니다.

영구 작업자 사용

Bazel 0.27 이상은 빌드를 실행할 때 기본적으로 영구 작업자를 사용하지만 원격 실행은 우선 적용됩니다. 영구 작업자를 지원하지 않는 작업의 경우 Bazel이 각 작업의 도구 인스턴스를 시작하는 것으로 대체합니다. 적용 가능한 도구 연상 기호에 대한 worker 전략을 설정하여 영구 작업자를 사용하도록 빌드를 명시적으로 설정할 수 있습니다. 예를 들어 localworker 전략의 대체로 지정하는 것이 좋습니다.

bazel build //my:target --strategy=Javac=worker,local

로컬 전략 대신 작업자 전략을 사용하면 구현에 따라 컴파일 속도를 크게 높일 수 있습니다. 자바의 경우 빌드가 2~4배 더 빨라질 수 있고 증분 컴파일에는 더 빨라질 수 있습니다. Bazel을 컴파일하는 작업은 작업자에 비해 약 2.5배 더 빠릅니다. 자세한 내용은 '작업자 수 선택' 섹션을 참조하세요.

또한 로컬 빌드 환경과 일치하는 원격 빌드 환경이 있다면 원격 실행과 작업자 실행을 경합하는 실험용 동적 전략을 사용할 수 있습니다. 동적 전략을 사용 설정하려면 --experimental_spawn_scheduler 플래그를 전달합니다. 이 전략은 작업자를 자동으로 사용 설정하므로 worker 전략을 지정할 필요가 없지만 local 또는 sandboxed를 대체로 사용할 수 있습니다.

작업자 수 선택

연상 기호별 기본 작업자 인스턴스 수는 4개이지만 worker_max_instances 플래그를 사용하여 조정할 수 있습니다. 사용 가능한 CPU를 잘 사용하는 것과 사용하는 JIT 컴파일 및 캐시 적중의 양 간에는 장단점이 있습니다. 더 많은 작업자를 사용하면 더 많은 대상이 JIT가 아닌 코드를 실행하고 콜드 캐시를 충족하는 시작 비용을 지불합니다. 빌드할 대상의 수가 적은 경우 단일 작업자가 컴파일 속도와 리소스 사용량의 절충안을 제공할 수 있습니다 (예: 문제 #8586). worker_max_instances 플래그는 니모닉 및 플래그 세트당 최대 작업자 인스턴스 수를 설정하므로 (아래 참고) 기본값을 유지할 경우 혼합 시스템에서 많은 메모리를 사용하게 될 수 있습니다. 증분 빌드의 경우 여러 작업자 인스턴스의 이점이 훨씬 작습니다.

이 그래프는 64GB RAM을 사용하는 6코어 하이퍼 스레드 Intel Xeon 3.5GHz Linux 워크스테이션에서 Bazel (타겟 //src:bazel)의 처음부터 컴파일 시간을 보여줍니다. 각 작업자 구성에 대해 5개의 클린 빌드가 실행되고 마지막 4개의 평균이 사용됩니다.

클린 빌드의 성능 개선 그래프

그림 1. 클린 빌드의 성능 개선 그래프

이 구성의 경우 2개의 작업자가 가장 빠른 컴파일을 제공하지만 1명의 작업자에 비해 14%밖에 향상되지 않습니다. 메모리를 더 적게 사용하려면 작업자 하나가 적합합니다.

증분 컴파일은 일반적으로 훨씬 더 유용합니다. 클린 빌드는 비교적 드물지만 컴파일 간에 단일 파일을 변경하는 것은 특히 테스트 기반 개발에서 일반적입니다. 위의 예시에는 증분 컴파일 시간을 덮어쓸 수 있는 자바 이외의 패키징 작업도 있습니다.

AbstractContainerizingSandboxedSpawn.java에서 내부 문자열 상수를 변경한 후에만 자바 소스(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar)만 다시 컴파일하면 3배 더 빨라집니다 (준비 빌드가 하나 삭제된 평균 빌드 20개).

증분 빌드의 성능 개선 그래프

그림 2. 증분 빌드의 성능 개선 그래프

처리 속도는 변경에 따라 달라집니다. 위의 상황에서 흔히 사용되는 상수가 변경될 때 계수 6의 속도가 측정됩니다.

영구 작업자 수정

--worker_extra_flag 플래그를 전달하여 니모닉으로 키가 지정된 작업자에 시작 플래그를 지정할 수 있습니다. 예를 들어 --worker_extra_flag=javac=--debug을 전달하면 Javac의 디버깅만 사용 설정됩니다. 이 플래그를 사용할 때마다 하나의 니모닉에만 하나의 작업자 플래그를 설정할 수 있습니다. 작업자는 각 연상 기호뿐 아니라 시작 플래그의 변형을 위해서도 별도로 생성됩니다. 니모닉 및 시작 플래그의 각 조합은 WorkerKey으로 결합되며, WorkerKey마다 최대 worker_max_instances개의 작업자를 만들 수 있습니다. 작업 구성에서 설정 플래그를 지정할 수 있는 방법은 다음 섹션을 참조하세요.

--high_priority_workers 플래그를 사용하여 보통 우선순위 연상 기호보다 먼저 실행해야 하는 연상 기호를 지정할 수 있습니다. 이렇게 하면 항상 중요한 경로에 있는 작업의 우선순위를 정할 수 있습니다. 요청을 실행하는 우선순위가 높은 작업자가 둘 이상인 경우 다른 모든 작업자가 실행되지 않습니다. 이 플래그는 여러 번 사용할 수 있습니다.

--worker_sandboxing 플래그를 전달하면 각 작업자 요청이 모든 입력에 별도의 샌드박스 디렉터리를 사용합니다. 샌드박스를 설정하는 데는 특히 macOS의 경우 시간이 더 걸리지만 정확성이 향상됩니다.

--worker_quit_after_build 플래그는 주로 디버깅 및 프로파일링에 유용합니다. 이 플래그는 빌드가 완료되면 모든 작업자를 강제로 종료합니다. --worker_verbose를 전달하여 작업자가 실행하는 작업에 대한 추가 출력을 얻을 수도 있습니다. 이 플래그는 WorkRequestverbosity 필드에 반영되므로 작업자 구현도 더 상세할 수 있습니다.

작업자는 <outputBase>/bazel-workers 디렉터리에 로그를 저장합니다(예: /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log). 파일 이름에는 작업자 ID와 니모닉이 포함됩니다. 연상 기호당 WorkerKey가 두 개 이상 있을 수 있으므로 특정 연상 기호에 대해 worker_max_instances개 이상의 로그 파일이 표시될 수 있습니다.

Android 빌드의 경우 Android 빌드 성능 페이지에서 세부정보를 참고하세요.

영구 작업자 구현

작업자를 만드는 방법에 대한 자세한 내용은 영구 작업자 만들기 페이지를 참조하세요.

다음은 JSON을 사용하는 작업자의 Starlark 구성을 보여주는 예시입니다.

args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
    output = args_file,
    content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
    mnemonic = "SomeCompiler",
    executable = "bin/some_compiler_wrapper",
    inputs = inputs,
    outputs = outputs,
    arguments = [ "-max_mem=4G",  "@%s" % args_file.path],
    execution_requirements = {
        "supports-workers" : "1", "requires-worker-protocol" : "json" }
)

이 정의를 사용하면 이 작업을 처음 사용할 때 명령줄 /bin/some_compiler -max_mem=4G --persistent_worker을 실행하는 것으로 시작합니다. 그러면 Foo.java를 컴파일하는 요청은 다음과 같습니다.

참고: 프로토콜 버퍼 사양은 '스네이크 케이스'(request_id)를 사용하지만 JSON 프로토콜은 '카멜 표기법'(requestId)을 사용합니다. 이 문서에서는 JSON 예에서 카멜 표기법을 사용하지만 프로토콜과 관계없이 이 필드에 관해 이야기할 때는 스네이크 케이스를 사용합니다.

{
  "arguments": [ "-g", "-source", "1.5", "Foo.java" ]
  "inputs": [
    { "path": "symlinkfarm/input1", "digest": "d49a..." },
    { "path": "symlinkfarm/input2", "digest": "093d..." },
  ],
}

작업자가 stdin에 줄바꿈으로 구분된 JSON 형식으로 이를 수신합니다 (requires-worker-protocol가 JSON으로 설정되기 때문). 그런 다음 작업자가 작업을 수행하고 stdout에 JSON 형식 WorkResponse를 Bazel로 보냅니다. 그런 다음 Bazel이 이 응답을 파싱하여 수동으로 WorkResponse proto로 변환합니다. JSON 대신 바이너리로 인코딩된 protobuf를 사용하여 연결된 작업자와 통신하기 위해 requires-worker-protocol는 다음과 같이 proto로 설정됩니다.

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

실행 요구사항에 requires-worker-protocol를 포함하지 않으면 Bazel은 기본적으로 protobuf를 사용하도록 작업자 커뮤니케이션을 수행합니다.

Bazel은 니모닉 및 공유 플래그에서 WorkerKey를 파생하므로 이 구성에서 max_mem 매개변수 변경이 허용되면 사용된 각 값에 대해 별도의 작업자가 생성됩니다. 너무 많은 변형을 사용하면 메모리를 과도하게 사용할 수 있습니다.

현재 각 작업자는 한 번에 하나의 요청만 처리할 수 있습니다. 실험용 멀티플렉스 작업자 기능을 사용하면 기본 도구가 멀티스레드이고 래퍼가 이를 이해할 수 있도록 설정된 경우 여러 스레드를 사용할 수 있습니다.

이 GitHub 저장소에서 자바는 물론 Python으로 작성된 작업자 래퍼의 예를 볼 수 있습니다. 자바스크립트 또는 TypeScript로 작업하는 경우 @bazel/worker 패키지nodejs 작업자 예시가 도움이 될 수 있습니다.

작업자가 샌드박스에 어떤 영향을 미치나요?

기본적으로 worker 전략을 사용하면 local 전략과 유사한 샌드박스에서 작업이 실행되지 않습니다. 샌드박스에서 모든 작업자를 실행하도록 --worker_sandboxing 플래그를 설정하면 도구를 실행할 때마다 필요한 입력 파일만 볼 수 있습니다. 이 도구에서는 내부적으로 캐시를 통해 요청 간에 정보가 유출될 수 있습니다. dynamic 전략을 사용하려면 작업자가 샌드박스되어야 합니다.

작업자 캐시를 통해 컴파일러 캐시를 올바르게 사용할 수 있도록 각 입력 파일과 함께 다이제스트가 전달됩니다. 따라서 컴파일러 또는 래퍼는 파일을 읽을 필요 없이 입력이 여전히 유효한지 확인할 수 있습니다.

입력 다이제스트를 사용하여 원치 않는 캐싱으로부터 보호하는 경우에도 샌드박스 작업자는 이전 요청의 영향을 받은 다른 내부 상태를 유지할 수 있으므로 순수한 샌드박스보다 덜 엄격한 샌드박스를 제공합니다.

멀티플렉스 작업자는 작업자 구현에서 지원하는 경우에만 샌드박스화될 수 있으며, 이 샌드박스는 --experimental_worker_multiplex_sandboxing 플래그를 사용하여 별도로 사용 설정해야 합니다. 자세한 내용은 디자인 문서를 참조하세요.

추가 자료

영구 작업자에 대한 자세한 내용은 다음을 참고하세요.