Bullshitting Blog

개소리하는 블로그

[ actor ]

액터 메시지 송신 시 직렬화 제거

말만 들어도 성능 떨어질 것 같은 피쳐다. 그동안 개발 편의성에 중점을 두기 위해 여러 메시지 타입을 지원하도록 하려고 직렬화를 이용했다.
결론만 공개하면, 여러 메시지 타입을 지원하는건 그대로 가져가면서 직렬화를 제거했다. xan-actor 6.0.0

근데 그 작업을 내 머리로 했을까? 아니다. 난 지금 건강 문제와 함께 터진 번아웃으로 인해 머리가 전처럼 안돈다. ChatGPT 과금해서 쓰고 있는데, Codex가 있길래 써봤다. 이녀석은 dyn trait을 Arc로 감싸서 컴파일 타임에 타입의 크기를 알 수 있게 우회하는걸 실제로 해줬다. 컨택스트 유지하는 비용이 큰 그 작업을 말이다.

아무래도 과거 손가락이 부러져서 코파일럿과 합을 맞춘 것처럼, 이번엔 뇌가 부러졌(?)으니 codex든 claude든, 코딩 에이전트와도 합을 맞춰가야 할 듯 하다. 주변에 claude를 아주 잘 쓰시는 분이 있는데, 그 분이 평하시기로는 codex는 보수적인 편이고, claude는 적극적인 편이라 했다. ChatGPT만 사용중이라서 선택지는 없긴 하지만, 사실 보수적인걸 좋아한다. 그냥 가자.

Actor 라이브러리 사용성 개선

액터 구조에 대한 한계점을 다시 접하게 되었다. 물론 당장 회사에서 발생한 것은 아니다. 그냥 들었다. 액터 구조 사용한 곳의 끝이 좋았던 적이 없었다고. 물론 난 아직 끝을 보지 않은 듯 하다. 하지만 한계점은 알고 있다. 이 액터 구조는, Rust와 만나면서 개발자의 생산성을 향상시킨다. 좋은거 아니냐고? 반은 좋고 반은 나쁘다. 통제해줄 사람이 없으면, 정말 개발자라는 족속들은 지맘대로 개발하는 특성이 있다. 그리고 보통 문서를 작성하는걸 싫어한다. 이직을 밥먹듯이 하는걸 당연히 하면서, 인수인계는 전혀 신경쓰지 않는 것이다. 그게 개발자다. 결국 적당한 실력으로 마음대로 만들어 올린 액터로 떡칠된 서비스는 유지보수 난이도가 굉장히 높아지게 된다.

그럼 액터 구조를 버리면 되는거 아니냐고? 그러기에는 액터 구조가 가져다주는 생산성과 그에 기반한 매출 성장을 무시할 수가 없다. 액터를 버리기 위해 Rust를 버리려고 하니, 성능을 포기할 수 없고, C++을 도입하자니, 숙련된 비싼 개발자를 구하는 것이 또 부담이며, 지금의 개발인력들을 재교육할 시간적 여유가 없다. 결국은 현실에 일부 타협하며 이래저래 좌충우돌을 겪어야 하는 것이 기술자들의 숙명인 것이다. 최소한 이래저래 삽질할 시간을 벌어다 줄 안정적인 캐시카우를 만들 때까진 말이다.

그럼 남은 방법은 액터 구조를 디버깅하는 방법을 개선하는 것, 그리고 문서화를 강제하는 시스템을 만들어가는 것이다. 그 중에서 당장 빠르게 할 수 있는 것은 전자다.

새 버전은 5.0.0. README 업데이트 두글자 더해 5.0.1이다. 아주 간단한 아이디어였는데, 지하철에서 회사로 걸어가던 중 갑자기 떠올랐다. 메시지 send를 하는 부분에 어떤 액터로 가는지가 표시되면 되는 것. 액터 타입을 send할 때 직접 어딘가 쓰면 되는 것이다. 그럼 그 타입의 정의를 타고 갈 수가 있다. 그럼 그곳에 있는 액터의 구성과 로직을 살펴볼 수가 있는 것이다. 그래서 Actor trait의 제네릭에 기존의 message, result, error 타입을 표시하던걸, trait 내 type 으로 정의하도록 바꿨다. 그리고 ActorSystem에서 send 함수 사용 시 message와 result 타입을 제네릭으로 사용하는 대신 actor 타입을 사용하도록 바꿨다. 그럼 actor_system.send(msg)를 사용할 때, actor_system.send::<MyActor>(msg)로 이용하면 된다. 이렇게 하면 여기서 MyActor를 타고 액터를 따라가서 디버깅할 수 있다.

물론 한계점은 여전히 있다. 액터 주소 체계를 중구난방으로 관리하면 대참사가 벌어진다. 4.x.x 버전부터는 broadcast 기능을 지원하는데, 여러 액터 타입에서 같은 메시지 타입을 이용하고, 액터 주소 필터링에 함께 걸릴 수 있는 구조라면, 표면적으로 제네릭으로 이용한 액터의 로직은 따라갈 수 있지만, 숨겨진 그 액터는 알 길이 없다(눈으로 정적 분석을 하는 수밖에 없다). 다만 이 문제는 그래도 4.x.x 버전보단 이번이 개선되기는 했다. 최소한 메시지 타입이 다른 경우 바로 컴파일 에러를 뱉을 것이기 때문이다.

어? 주소 체계 관리를 빡세게 만드는 뭔가를 만들면 되려나. 하지만 이건 더 잡고 있으면 모처럼 휴식 사이클을 돌리고 있는데 다 망치고 밤을 새버릴 것 같아서 멈추도록 하겠다.

Actor 라이브러리에서 proc-macro 제거

액터 라이브러리에서 proc-macro를 다시 제거했다.

결과적으로 중복코드를 많이 생성하는 것이기도 하고, 써보니까 오히려 보기에 더 지저분해짐. 물론 예쁘게 되도록 선언하지 못한 내 잘못이겠지만 말이다. 매크로는 derive macro까지 정복하고 활용해야지.

그것보다는, 결정적으로 동기만 지원하게 되어 있는 것이 가장 큰 문제였다. current 런타임을 불러와서 block_on을 이용하는데 신경 조금만 덜 쓰면 에러가 발생했다.

회사에서 리뉴얼하고 있는 서비스 개발 기한도 1주일밖에 안남은 시점인데 여기도 그걸 사용함. 그럼, 어떻게든 끼워맞춰 쓰는게 능사일까? 아니다. 내 경험 상, 그렇게 끼워맞추는데 시간이 더 들고, 실패 시 리스크가 훨씬 크다. ‘이거 하려고 내가 얼마나 공수를 많이 투입했는데’ 하는 순간 돌아올 수 없는 강을 건너게 된다. 심한 경우 라이브러리 하나 때문에 시스템 구조를 바꿔버리는 케이스도 생긴다. 설상가상으로, 그런 식으로 바꾸면 보통 문서 업데이트도 안한다. 곧 다시 개선할거라고 하며, 실제로는 그 부분을 까먹으며.

그래서 어제 모처럼 휴일이겠다, 바로 착수했다. proc-macro 걷어내고, bincode 인코딩을 통해 한 액터 시스템에서 여러 메시지 타입을 이용할 수 있도록 한 피쳐를 그 위에 올렸다. 기존에 마음에 안들던 trait 함수로 디폴트 넣어서 생략 가능하게끔 바꿈. 이제 벌써 4.0.0 버전이다.

적용은 그리 오래 걸리지 않았다. 물론 구조체 위치 등 정리할건 있지만 그냥 매크로가 써줄 애들을 다시 trait으로 원복했을 뿐이기 때문이다.

오늘은 4.1.0 버전을 개발할 생각이다. 액터 주소를 정규식으로 필터할 수 있도록 할 생각이다. 지금 글쓰는 도중에 구상 완료.

proc-macro 기반 액터 라이브러리 완성

선전포고문

전쟁 종결. 이름하여 2.0.0 버전 런칭.

proc-macro와 bincode 인코딩을 이용해서 타입 구애 없이 액터간 메시지 통신 가능하게 함. 물론 send, recv할 때 serialize, deserialize가 일어나긴 하지만, 매크로단에서 처리해 버려서 사용자는 받을 타입만 명시해 주면 됨.

하지만 아직 미흡한 부분이 많다. 메시지를 정기적으로 보낸다던가 하는 기능은 2.0 버전에서 빠짐. 그리고 에러 핸들링도 그냥 expect를 남발해 둔 상태이다. 거기서 그냥 터짐. 또한 함수 넘기는 부분에서 async가 아직 이용이 안된다. future 적절히 못쓰는 사람은 여기서 갖다 버릴 듯. 아, 이전에도 없었지만, 액터의 주소가 중복되는 케이스도 따로 핸들링 안되어 있다. 아, 라이프사이클도 상태는 관리하긴 하지만 getter가 없다. 그리고 recv할 때 따로 타입 기입 안해도 되게 하고 싶음. 요 부분은 TODO로 둘 것.

이제 회사꺼에 적용해야지 개꿀띠

하려다가 불편해서 바로 3.0.0 릴리즈함…

라이프사이클 getter도 넣었고, 주소 체계도 적용해서 “*” 적절히 써서 여러 액터에 한번에 메시지 보내기도 가능해짐. 부모-자식 관계는 물론 엄마친구 관계(?) 로 필터 걸 수도 있다.

2.x.x은 다 은퇴시켰다

역시 2.0은 뭘 해도 안되는구나.

선전포고문

image.png

직접 제작한 액터 라이브러리를 복잡한 프로그램에서 이용하려고 메시지 enum 안에 타입을 다 때려박다 보니 이런 Warning 메시지를 마주하게 되었다. 쉽게 말하면, “어떤 멍청이가 enum을 이딴 식으로 쓰냐?” 라는 말이다. 그것이 바로 나다. 어쩔? 두고봐라. 러스트 컴파일러에 전쟁을 선포한다.

그래서 제네릭은 없는데 타입에 구애받지 않는 액터 라이브러리 새 버전을 개발중이다. 힌트는 매크로이다. 겸사겸사 proc-macro도 정복하는 중이다. 이거 너무 좋잖아?

crates.io 배포

자체 개발하여 업무 및 개인 용도로 사용하고 있던 actor 라이브러리를 crates.io에 배포했다(xan-actor).

원래 이 라이브러리는 업무용으로 필요한 기능만 구현해서 사용하고 있었는데, tokio를 너무 애용하는 바람에 main 함수를 async 함수로 만들어야 하는 단점이 있었다. 이게 async 키워드만 붙인다고 되는게 아니라 tokio::main을 이용해야 한다. 따라서 tokio를 쓰고싶지 않은 사람도 tokio를 이용해야만 이 라이브러리를 사용할 수 있었음. 그런데 또 비동기에 사춘기가 들어서 그런지, 동기 메인함수를 이용하고 싶었다. 그래서 동기로 만드는데, 이김에 tokio도 걷어내보고 싶어서 걷어내다 보니, 전체 구현를 tokio 없이 할 수 있는 피쳐가 만들어졌다.

근데, 매번 이 라이브러리를 add하는데 Cargo.toml에 깃허브 리포를 매번 넣어주는게 귀찮아오던 참에 crates.io에 배포해 보기로 했다. 보니까 4년째 관리 안하는 actor 라이브러리도 떡하니 자리를 잘 차지하고 있는데, 나라고 안될까.

일단 이걸 하면 편해지는건 그냥 cargo add xan-actor 하면 라이브러리가 add된다는 것이다. 개꿀.

배포 방법

Crates.io에 크레이트 배포하기