Activity 동작 방식
지난 시간에 이어 확인해 보고자 하는 것은 temporal workflow 함수 내에서 수행하는 activity의 동작 방식입니다.
Client 코드는 workflow 함수를 실행하지만 실제로는 worker의 workflow를 수행하는 것을 알 수 있었습니다. 문제는 activity 인데요, activity 하나를 수행할 때마다 client와 응답을 주고 받는지가 궁금해 졌습니다. 예상해 볼 수 있는 동작 방식은 다음과 같습니다.
- Client는 workflow 수행의 최종 결과만 받는다.
- Client는 workflow invocation 정보만 temporal server에 전달
- temporal server는 worker에 workflow 수행 명령
- worker는 해당 workflow와 activity를 모두 수행 후 최종 결과를 temporal server에 전달
- temporal server는 client에 결과 전달
- client 종료
- Client는 각 activity의 응답을 받아 다음 activity 수행여부를 결정한다.
- Client는 workflow invocation 정보만 temporal server에 전달
- temporal server는 worker에 workflow 수행 명령
- worker는 workflow 수행에 필요한 리소스 확보 후 준비되었음을 temporal server에 알림
- temporal server는 client에 이를 전달
- client는 activity 수행을 요청하고 결과를 받는 것을 반복
- temporal server는 client에 결과 전달
- client 종료
분석 결과 1번 방식으로 동작하고 있었습니다.
따라서, client에서는 workflow invocation 정보만 전달할 뿐 실제 코드를 수행을 일체하지 않습니다. 혹여라도 client의 코드를 바꾸어도 실행중인 worker의 코드만 수행함을 알 수 있습니다. 다시 말해 client와 worker의 workflow 코드는 달라질 수 있습니다. 이 경우 어떠한 방식으로 동작하는지 알고 사용을 해야 합니다.
다음은 해당 결론에 이르기 까지의 분석 과정을 정리하였습니다.
Concurrently 설정
하나의 예제의 동작을 확인하려면, temporal server, worker, client를 순차적으로 실행해야 합니다.
concurrently는 동시에 여러 명령을 수행할 수 있는 개발툴 입니다.
concurrently를 사용하여 대략 다음과 같은 명령을 수행합니다.
concurrently --names 'server,worker,client' \
-c 'blue,red,green' \
'GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info temporal server start-dev' \
'sleep 1 && npm run worker' \
'sleep 3 && GRPC_VERBOSITY=DEBUG GRPC_TRACE=all npm run client'
GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info temporal server start-dev
명령은 개발용 temporal 서버를 로컬에서 수행합니다.
Temporal server는 go 언어로 작성한 grpc 라이브러리를 사용합니다.
GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info
환경변수 설정은 grpc 입출력을 파악하는데 도움이 됩니다.
worker의 경우 rust의 GRPC 구현체인 tonic을 사용합니다.
tonic
라이브러리는 입출력을 확인할 수 있는 로깅을 제공하지 않고 있어 temporal 구현체의 로깅을 위주로 동작 파악을 해야 합니다.
client는 @grpc/grpc-js 라이브러리를 사용하고 환경 변수인 GRPC_VERBOSITY=DEBUG
와 GRPC_TRACE=all
는 GRPC 송수신 로그를 출력합니다.
server, worker, client 각각 순차적으로 실행해야 함으로 sleep
명령으로 시간을 조정합니다.
names
옵션은 각 shell 명령의 출력 prefix에 붙는 이름을 지정합니다.
c
옵션은 각 shell 명령의 출력 prefix의 색상을 지정합니다.
이렇게 설정하면 다음과 같은 로그를 확인할 수 있습니다.
Client는 /temporal.api.workflowservice.v1.WorkflowService/StartWorkflowExecution
grpc 호출을 하여 workflow 수행을 server에 요청합니다.
곧이어 worker가 동작하고 첫번째 activity는 withdraw, deposit, moneyTransfer를 순차적으로 수행합니다.
Worker의 moneyTransfer
activity를 수행하고 나면 client는 그 결과를 받아 transaction id와 같은 응답값을 표시합니다.
마치며
TypeScript, Rust, Go 언어를 오가며 동작을 분석하기란 쉽지 않았습니다. 내부 구조도 제법 복잡하여 protocol 단에서 데이터 송/수신 정보를 기반으로 분석해 보려 했습니다. 하지만, Go의 경우 grpc 연결 설정의 로그만 나오고 Rust의 경우 로그를 남기는 방법을 찾지 못했습니다. 다행히 node js grpc 라이브러리는 송수신 패킷 정보를 자세하게 출력하는 로그를 확인할 수 있었습니다.
결국 worker에서 workflow를 수행하면서 하위 activity를 모두 수행하고 그 결과를 server에 전달하는 방식임을 확인할 수 있었습니다.
생각보다 Temporal 분석에 시간이 많이 들고 지지 부진한 면이 있었습니다. 원래 temporal의 공식 TypeScript 예제를 분석하고 마무리하려 했으나 시간이 많이 결러 고민이 됩니다. 일단 Temporal 분석은 part 2까지 하고 다른 아이템을 찾아 리뷰해야 겠습니다.