[Project : Gold-Rush-Lab] 6. Check Point, 회고
Monolith 부터 Event Driven으로 시스템을 발전 시키며 느꼈던 것들
Monolith(v0.1) 부터 Event Driven(v0.5) 까지 약 3~4주에 걸쳐 기획하고, 또 실험을 설계하고 시스템을 발전 시켜보았다.
매우 축소 되고, 극단적인 상황이긴 했으나 아주 기본적인 시스템 형태에서 이를 발전 시키면서 꽤나 다양한 궁금증과 문제를 마주치곤 했었다.
가장 먼저 문제가 되었던건 가설 설정 문제였다. 어느정도 로드맵을 설정하고 이를 따라가는 형태였지만 실험을 진행하고, 또 가설을 설정하다보면 ‘너무 당연한 것을 가설로 설정한 것이 아닐까?’, ‘이게 실제로 내가 궁금한 것이 맞는가?’ 등으로 몇번이나 가설을 뒤엎고, 그로인해 완료된 실험을 재실험하는 해야하는 경우도 왕왕 발생했다.
지금 돌이켜보면 가장 어려웠던 것은 구현이 아니라 ‘무엇을 검증하고 싶은지 정의하는 일’ 이었다.
그 다음 문제는 물리적인 문제였다. 설계 과정에서 충분히 감당 가능할 것으로 생각했던 메모리가 V0.3 에서 부터 부족해져서 메모리가 금 값이 된 현재 상황에서도 눈물을 머금고 16GB 메모리를 호스트 서버에 추가했다.
그 다음은 가설을 검증하기위한 지표 설정 문제였다. 어떤 지표를 증거로 삼을지, 지표에서 실제로 수집하는 것이 가설에 대한 적절한 지표인지, 또 지표를 수집하는 타이밍이 적절한지 고려할 것이 한두개가 아니었다. 원하는 지표가 적절히 수집되지 않거나 값이 이상하면 수정하고 재 실험하고를 또 반복했다.
직접 테스트 하는건 말할 것도 없었다. V0.2 까지 한땀한땀 직접 테스트를 수행하면서 쉽지 않지만 충분히 가능할 것이라고 생각했지만, V0.3에서 가설이 추가되고, 궁금한 것들이 늘어나면서 또 재실험이 늘어났다. 결국 모든걸 지켜보면서 할 수 없을 것이라고 판단했고 codex를 통해 자동화 스크립트를 작성하기도 했다.
회고를 작성하면서, 이 프로젝트를 진행하며 아쉬웠던 점이 먼저 떠올랐다.
아쉬웠던 것들.
-
처음부터 명확한 실험 계획을 세우지 못했다.
처음에는 ‘구현하면서 생각하자’는 방식으로 프로젝트를 진행했다. 그러다 보니 실험을 진행하는 도중 새로운 궁금증이 계속 생겼고, 이미 끝난 실험을 다시 수행하거나 지표를 추가하기 위해 재실험하는 경우가 많았다. 지금 다시 시작한다면 구현에 앞서 검증하고 싶은 가설과 필요한 지표를 먼저 정리한 뒤 실험을 진행할 것 같다.
-
“정답”을 찾으려 했다.
프로젝트를 시작할 때는 ‘가장 좋은 아키텍처’를 찾을 수 있을 것이라 생각했다. 하지만 실험을 반복하면서 상황에 따라 적합한 아키텍처가 달라진다는 것을 느꼈다. 결국 정답을 찾기보다 각 선택이 가지는 Trade-Off를 이해하는 것이 더 중요했다.
-
운영 환경을 너무 늦게 고려했다.
v0.5에서 SSE, Redis Pub/Sub, File Descriptor, Heartbeat 등이 다 구현하고 나서 나왔다. 기능 구현에 집중하다 보니 운영 환경에서 발생할 문제를 충분히 예상하지 못했다. 실제로 SSE 연결 수 증가에 따른 Nginx File Descriptor 제한, Heartbeat, Emitter 정리와 같은 문제는 구현 이후에 발견하였다. 앞으로는 기능뿐 아니라 운영 환경에서 발생할 문제까지 함께 고려하며 설계하고 싶다.
이 중 에서 가장 아쉬웠던 점은 구현보다 실험 설계에 더 많은 시간을 투자하지 못한 것이다.
프로젝트를 진행하면서 새로운 궁금증이 계속 생겼고, 그때마다 가설과 지표를 수정하면서 이미 완료한 실험을 여러 번 다시 수행해야 했다. 구현 자체는 빠르게 진행되었지만, 무엇을 검증할 것인지에 대한 계획은 충분하지 못했던 셈이다.
지금 다시 시작한다면 구현보다 먼저 검증하고 싶은 가설과 필요한 지표를 모두 정의한 뒤 실험을 진행할 것 같다. 결국 좋은 실험은 좋은 구현보다 좋은 설계에서 시작된다는 것을 이번 프로젝트를 통해 배울 수 있었다.
이러한 과정을 거치면서 직접 피부로 배운 것들도 많았다.
배웠던 것들.
- 아키텍처 변경은 정말 신중해야한다.
- 아키텍처는 단순하면 단순할수록 발생하는 문제도 단순했다. v0.1 ~ v0.2 Monolith 아키텍처에서는 발생한 문제에 대해서만 집중할 수 있었다. V0.3에서 분산 시스템을 구성하며 는 loadbalncer 가 적절히 동작하는지 관리해야했고, v0.5 Event Driven으로 발전하며 늘어나는 미들웨어와 그에 상응하는 예상치 못한 문제들을 만날 수 있었다.
- 아키텍처의 변경에 따른 추가적인 비용도 있다. 각 지점별로 모니터링 지표를 심고, 또 이를 관리해야하는 추가적인 비용도 발생했다.
- 대체로 미들웨어의 도입 자체만으로는 병목은 이동할 뿐이다.
- 각 아키텍처의 변곡점 v0.1, v0.3, v0.4, v0.5 에서 미들웨어를 도입만 하면 마법처럼 해결될 줄 알았던 병목들은 DB에서 Application으로, Application에서 Redis로, 또다시 Application 에서 DB로 이동했다. 이는 미들웨어는 솔루션 그 자체이기 보다는 자료구조에 가깝다는 생각이 들었다.
- 알고리즘 문제를 풀면, 사용할 자료구조를 먼저 선택하기 보다는 현재 관리해야하는 상태, 그리고 이 상태들이 어떻게 관리되어야 하는지를 정한 후 그에 적합한 자료구조를 고른다. 미들웨어와 비슷하다는 생각이 들었다.
- 상태(현재 시스템이 처한 상황, 비즈니스 규칙)을 파악하고 이들이 어떻게 관리될 수 있을지를 고려한 후 그에 적합한 미들웨어를 선택하는 방식으로 도입을 고려하는 것이 도입하려는 미들웨어의 효율성을 최대로 끌어내며, 트레이드 오프를 최소화 할 수 있는 방법이 되겠다는 것을 느꼈다.
- DB 병목
- v0.1 ~ v0.5 에서 분산락을 제외하고는 주요 병목은 DB 병목이었다. 물론 Gold-Rush-Lab은 핫스팟에 한정된 환경이었으나 반대로 말하면 핫스팟 상황(쿠폰 발급, 예매 등) 에서의 주요 병목은 DB 병목이 될 것이라는 것이다.
flush()되는 순간 더이상 병목은 Application을 떠나 관리 불가능한 병목이 되어버린다. DB 성능은 사실 정말 강력하다. 하지만 DB 비용이 높다는 것 또한 사실이다. (여기서 비용은 Disk 와 관련된 비용이 아닌 커넥션과 I/O 와 관련된 비용이다.)
이러한 비용을 해결하지 않고
flush()해버리니 성능의 향상 폭에도 제한이 있거나, 성능 향상 자체가 이뤄지지 않는 다는 것을 확인할 수 있었다.높은 비용을 매
flush()마다 지불하기 보다는 그 전에 관리할 수 있는 Application 에서 쿼리를 튜닝하거나, 한번에 처리할 수 있는 방식을 모색하는 것이 앞으로의 성능에 관건이 될 것이다. - LLM 과 함께하는 개발
- 이 프로젝트는 최근 개발 업계가 그렇듯 구태여 기술의 발전에 역행하지 않았다 (= LLM과 함께 개발했다.) LLM을 통해 개발 속도와 학습 속도를 확보할 수 있었지만, 현재 LLM의 한계도 명확히 확인했다.
- 우선 나는 설계에 대한 토론과 의논은 ChatGpt, 개발에 대한 의논은 Codex를 활용했다.
- 설계와 흐름, 결과는 개발자가 책임져야한다.
- 설계와 흐름을 설계하며 모르는 부분이 있거나, 보충이 필요할 것 같을 때는 ChatGpt를 활용했다. 하지만 그에 빈틈이 있거나 조금이라도 추상적인 부분이 존재하면 원하는 결과와 다른 결과가 나왔다. 결과적으로 Git Diff를 확인하는 과정에서 수정할 부분들이 꽤 있었다.
- 맥락의 최종 소유자는 개발자여야한다.
- 최근 LLM은 AGENTS.md 등 중간계약을 통해 맥락을 관리하지만, 시스템이 복잡해지면 복잡해질수록 모든 맥락의 내용과 의도를 파악하기는 어렵다.
- LLM이 중간 계약을 통해서 맥락을 잘 유지할 수 있도록 하면서도, 결국 맥락의 최종 소유자는 개발자여야 할 것이라는 생각을 했다.
- LLM에서도
SRP는 적용되는 듯 하다.- 객체지향설계 원칙 중
SRP(Single Responsibility Principle = 단일책임원칙) 은 하나의 클래스나 모듈이 하나의 수행이나 사용자에 대한 책임을 가져야 한다는 의미를 가진다. 이전 뉴스레터시스템에서도 느꼈지만, 체감상 LLM을 활용하면서도 하나의 책임만을 부여하여 그 책임을 수행하게 하는 것이 더 좋은 결과를 가져왔다.
- 객체지향설계 원칙 중
-
직접 검증하기 전에는 아무것도 확신할 수 없었다.
이번 프로젝트에서는 여러 번 예상과 다른 결과를 만났다. 분산락을 적용하면 성능도 함께 좋아질 것이라 생각했지만 오히려 TPS는 감소했고, Event Driven 아키텍처를 적용하면 전체 처리량도 증가할 것이라 예상했지만 실제 병목은 Consumer와 DB로 이동했다. 반대로 큰 차이가 없을 것이라 생각했던 Message Key 전략은 시스템의 병렬성과 데이터 정합성을 크게 바꾸는 요소였다.
이번 프로젝트를 통해 가장 크게 배운 것은 직감이나 일반론보다 직접 측정한 결과가 더 중요하다는 점이었다
-
나의 실험실
처음에는 단순히 동시성을 공부하기 위해 시작한 프로젝트였다. 하지만 실험을 반복하다 보니 어느새 새로운 기술을 검증하고 궁금증을 해결할 수 있는 나만의 실험 플랫폼이 되어 있었다.
이 게시글의 제목이 CheckPoint 인 것 처럼 Gold-Rush-Lab을 하나의 프로젝트로서 완전히 마칠 생각은 없다. 앞으로도 새로운 기술이나 설계가 궁금해질 때마다 가장 먼저 실험해 볼 공간으로 계속 발전시켜 나가고 싶다.
댓글남기기