요즘 아키텍처를 보는 취향이 조금 바뀌었다.
어떤 기술이 더 좋은지보다, 이 기술을 추가했을 때 내가 무엇을 더 운영해야 하는지를 먼저 생각하게 됐다.
아마 개발과 인프라를 같이 하고 있기 때문인 것 같다.
애플리케이션 코드만 보고 있으면 새로운 컴포넌트를 추가하는 것은 그렇게 큰 일이 아니다. 비동기 작업이 필요하면 메시지 큐를 넣고, 검색이 필요하면 검색 엔진을 붙이고, 캐시가 필요하면 별도 캐시 서비스를 추가하면 된다.
각각은 충분히 검증된 좋은 기술이다.
그런데 직접 운영까지 하다 보면 조금 다르게 보인다.
코드에서는 dependency 하나였던 것이 인프라에서는 관리해야 할 시스템 하나가 된다.
이 글은 메시지 큐를 쓰지 말자는 글이 아니다. 개발과 인프라를 같이 하면서, 코드에서는 작아 보이던 선택이 운영에서는 어떤 비용으로 보이기 시작했는지 정리하는 글이다.
Table of contents
Open Table of contents
1. 운영까지 하니 코드 뒤의 비용이 보이기 시작했다
개인 인프라를 운영하면서 이 차이가 특히 잘 보였다.
내 개인 인프라는 N150 기반 서버 두 대와 Oracle Cloud를 섞어서 운영하고 있다. Oracle Cloud도 마음껏 쓰는 환경이라기보다는, 무료 티어와 작은 유료 비용 안에서 최대한 아끼며 쓰는 쪽에 가깝다.
그래서 큰 환경에서는 별것 아닌 CPU와 메모리도 여기서는 직접적인 제약이다. 새로운 서비스를 하나 띄우려고 하면 자연스럽게 질문하게 된다.
이 프로세스가 정말 필요한가?
Oracle Cloud의 Always Free Block Volume은 boot volume과 block volume을 합쳐 200GB까지 제공한다. 넉넉해 보이지만, Kubernetes 위에서 기반 인프라를 하나씩 올리기 시작하면 생각보다 빨리 줄어든다.
Oracle Kubernetes Engine(OKE)에서는 데이터베이스나 모니터링처럼 상태를 남기는 컴포넌트를 추가할 때마다 50Gi 단위의 PVC가 따라오는 경우가 있다.
처음에는 상태를 저장하는 큰 서비스에서만 persistent volume이 필요할 거라고 생각했다. 그런데 실제로는 데이터베이스뿐 아니라 모니터링처럼 기반 인프라에 가까운 컴포넌트도 PVC를 요구한다. boot volume까지 합산하면 무료 티어의 200GB 한도를 이미 초과한 상태다. 초과분은 추상적인 클라우드 리소스가 아니라 내 지갑에서 바로 나가는 비용 항목이 된다.
기술적으로는 아무 문제가 없다. 그냥 만들면 된다.
그런데 직접 비용과 운영을 책임지고 있으면 다른 질문이 생긴다.
굳이?
작은 환경이라서 생긴 특수한 취향이라고만 보지는 않는다. 제한된 환경은 비용을 숨겨주지 않는다. 그래서 어떤 기술을 붙였을 때 따라오는 운영 항목이 더 잘 보인다.
2. NoteGate의 background job을 정리하면서 Queue부터 떠올렸다
최근 NoteGate에 PostgreSQL 기반 background job queue와 worker runtime을 정리하면서도 비슷한 일이 있었다.
처음에는 자연스럽게 별도 메시징 큐 서비스를 생각했다. 비동기 작업을 처리하는 Worker니까 Queue를 사용하는 것은 자연스러운 선택이었다.
그런데 실제 구현을 생각하다 보니 Job 상태는 어차피 PostgreSQL에 저장해야 했다.
여기서 조금 이상하다는 생각이 들었다.
어차피 PostgreSQL에 Job이 있는데 왜 같은 작업의 전달 상태를 하나 더 관리하고 있지?
내가 필요한 것은 대규모 Event Streaming이 아니었다. 먼저 다루려던 것은 상태를 가진 내부 작업에 가까웠다.
- Space 사용량을 다시 계산한다
- 링크 관계를 projection한다
- 실패하면 재시도하고, 끝나면 실행 이력을 남긴다
- 나중에 다른 background job도 같은 실행 경계에 태운다
결국 Event보다는 Job에 가까웠다.
3. 지금 필요한 것은 외부 Queue보다 Job 상태 관리에 가까웠다
그렇다면 PostgreSQL에서 Worker가 Job을 직접 가져가는 것도 충분히 가능한 선택이었다.
도메인 데이터는 기존 테이블에 두고, PostgreSQL은 작업 상태와 실행 이력을 함께 관리한다.
PostgreSQL = Job / State / Attempt history
Domain tables = Source data / Projection
Worker = Processing
Worker는 PostgreSQL에서 준비된 Job을 claim할 수 있다. 물론 이것도 공짜는 아니다. Worker가 많아지면 DB connection pool, claim query 비용과 lock contention을 고려해야 한다.
하지만 중요한 질문은 이것들이 이론적으로 존재하느냐가 아니라, 현재 내 시스템에서 실제 문제가 되느냐라고 생각한다.
PostgreSQL LISTEN/NOTIFY는 Worker를 빠르게 깨우는 신호로 쓰고, safety polling은 알림을 놓쳤을 때의 복구 경로로 둘 수 있다.
그리고 어느 순간 Worker와 Job이 충분히 많아져 PostgreSQL이 실제 병목이 된다면 그때 메시지 큐를 추가하면 된다. 메시지 fan-out이나 replay가 중요한 시스템이 된다면 별도 메시지 큐를 선택할 이유도 명확해진다.
중요한 것은 메시지 큐를 사용하지 않는 것이 아니다.
메시지 큐가 해결해야 할 문제가 생겼을 때 사용하는 것이다.
4. AI는 코드 변경 비용을 낮췄지만 운영 비용을 없애지는 않는다
이런 생각은 AI로 코딩하기 시작하면서 더 강해졌다.
과거에는 구현 자체가 비용이 큰 작업이었다. 한 번 만든 코드를 나중에 크게 바꾸는 일도 부담이 컸다. 그래서 미래의 요구사항을 어느 정도 예상해서 인터페이스를 만들고, 공통화하고, 여러 환경에서 쓸 수 있도록 추상화하는 선택이 꽤 합리적이었다.
물론 그런 선택도 공짜는 아니었다. 미래를 대비한 추상화는 지금의 복잡도를 늘리고, 실제로 오지 않을 요구사항을 위해 구조를 무겁게 만들 수 있다. 그래도 당시에는 구현 비용과 나중에 바꿀 비용이 워낙 컸기 때문에, 그 부채를 어느 정도 감수하는 편이 전체 비용을 낮추는 선택처럼 보였다.
그런데 AI를 사용하면서 구현과 변경 속도가 상당히 빨라졌다. 그러면 계산이 조금 달라진다.
AI가 인프라와 운영 비용을 높인 것은 아니다. 구현하고 변경하는 비용이 낮아지면서, 이전에는 상대적으로 작아 보였던 운영 비용이 더 큰 비중으로 보이기 시작했다.
| 비용 항목 | 과거 | AI 코딩 이후 | 상대적 변화 |
|---|---|---|---|
| 구현 비용 | 큰 비중을 차지했다 | 크게 낮아졌다 | 낮아짐 |
| 변경 비용 | 나중에 바꾸는 부담이 컸다 | 필요할 때 바꾸기 쉬워졌다 | 낮아짐 |
| 인프라 비용 | 구현 비용에 비해 덜 두드러졌다 | 여전히 그대로 발생한다 | 상대적으로 커짐 |
| 운영 복잡도 | 구현 비용에 가려져 있었다 | 모니터링, 백업, 장애 대응은 그대로 필요하다 | 상대적으로 커짐 |
| 설계 판단 | 미래 요구를 미리 반영할 이유가 있었다 | 현재에 맞게 만들고 필요할 때 바꾸는 편이 유리해졌다 | 판단의 중심이 이동함 |
이 차이를 보고 나니, 미래에 필요할지도 모르는 기능을 위해 지금 운영 복잡도를 지불하는 것보다 현재 환경에 맞는 단순한 구조를 만들고 필요해졌을 때 바꾸는 편이 더 낫다고 생각하게 됐다.
AI는 코드의 변경 비용을 낮춰줬다. 하지만 서버를 운영하는 비용까지 함께 낮아진 것은 아니라고 생각한다. 구현 비용에 가려져 있던 인프라 비용, 장애 영역, 모니터링 부담이 상대적으로 더 선명하게 보이기 시작했다.
그래서 요즘 나는 코드의 추상화보다 시스템의 구체적인 환경을 더 많이 본다.
CPU가 얼마나 있는지, 메모리가 얼마나 있는지, 스토리지 최소 단위가 얼마인지, Worker가 몇 개인지, Job이 얼마나 자주 발생하는지.
그런 다음 그 환경에서 가장 작은 구조를 먼저 찾는다.
5. 이제는 추가하지 않아도 되는 이유를 먼저 본다
예전에는 좋은 아키텍처가 무엇인지부터 생각했다면, 요즘 나는 조금 다른 질문부터 한다.
지금 이 환경에서 정말 필요한 것은 무엇인가?
개발과 인프라를 같이 하고 AI 코딩으로 코드 변경 비용이 낮아지는 경험까지 하면서, 나는 새로운 기술을 추가하는 능력만큼 추가하지 않아도 되는 이유를 찾는 능력이 중요하다고 생각하게 됐다.
새로운 컴포넌트를 추가하지 않는 것이 항상 좋은 선택이라는 뜻은 아니다. 메시지 큐가 필요한 순간은 분명히 있고, 별도 캐시 서비스나 검색 엔진, 스토리지가 문제를 단순하게 만드는 경우도 있다.
그래서 나는 기술을 추가하기 전에 그 뒤에 붙는 것들을 먼저 본다.
- CPU와 메모리를 얼마나 쓸 것인가
- 저장소와 백업은 어떻게 관리할 것인가
- 모니터링과 업그레이드는 누가 할 것인가
- 장애 영역은 얼마나 늘어나는가
- 지금 문제를 해결하는 데 정말 필요한가
개인 인프라는 이런 생각이 만들어진 실험장이었다. N150 두 대라는 제한된 환경에서 직접 운영하다 보니 코드에서 보이지 않는 비용이 숫자로 보이기 시작했다.
그 경험은 작은 개인 서비스뿐 아니라 다른 서비스를 설계할 때도 영향을 준다. 이제 나는 아키텍처를 좋은 기술을 얼마나 많이 아느냐보다, 지금 환경에서 무엇을 운영하고 무엇을 운영하지 않을지 판단하는 일로 보는 편이다.