← 프리뷰 목록으로

단순한 아키텍처를 옹호하며

게시일: 2025년 11월 22일 | 원문 작성일: 2022년 4월 1일 | 저자: Dan Luu | 원문 보기

요약

  • Wave는 17억 달러 가치평가를 받은 회사예요. 70명의 엔지니어가 만드는 제품은? 그냥 숫자 더하고 빼는 CRUD 앱이에요. 표준적인 Python 모놀리스 + Postgres로 매월 수십억 건의 요청을 처리해요
  • 복잡성은 비용이에요. 모든 조직은 한정된 “복잡성 예산”이 있고, 이걸 전략적으로 써야 해요
  • 컨퍼런스와 블로그에는 복잡한 시스템 이야기만 많아요. 단순한 시스템은 발표 주제가 안 되거든요. 이게 선택 편향을 만들어요
  • 일부 애플리케이션은 복잡한 아키텍처가 필요해요. 하지만 대부분은 단순한 모놀리스로도 충분해요 — 심지어 상위 100위 트래픽 수준에서도요

핵심 주장: 단순한 아키텍처, 생각보다 훨씬 멀리 간다

업계 트렌드는 마이크로서비스와 복잡한 분산 시스템을 선호해요. 하지만 현실은? 요즘 하드웨어는 충분히 빨라서, 단순한 모놀리스로도 꽤 큰 규모를 감당할 수 있어요.

증거:

  • Wave는 단순한 스택으로 매월 수십억 건의 요청을 처리해요
  • Stack Overflow는 모놀리스로 트래픽 상위 100위 안에 들면서 18억 달러에 매각됐어요
  • 대부분의 회사는 실제 필요보다 훨씬 더 복잡하게 가고 있어요

컨퍼런스 편향: 선택 편향이 만드는 왜곡된 인식

Dan Luu가 어느 기술 컨퍼런스에 갔을 때 본 거예요:

주제 발표 수
마이크로서비스 복잡성/분산 시스템 6개
효과적인 모놀리스 구축 0개
양자 컴퓨팅 1개

왜 이런 선택 편향이 생길까요?

  • 복잡한 시스템은 자연스럽게 더 많은 이야기를 만들어요: 문제가 많고, 해결 과정이 험난하고, 배운 교훈이 많아요
  • 단순한 아키텍처는 발표할 게 없어요: “우리는 Postgres 쓰는데 잘 돌아가요”는 20분짜리 발표가 안 되거든요
  • 결과적으로 개발자들은 왜곡된 인식을 갖게 돼요: “진지한” 시스템은 복잡해야 하고, 성공한 회사들은 다 복잡한 아키텍처를 쓴다고 믿게 돼요

이건 능동적인 과장이 아니에요. 수동적인 선택 편향이에요. 복잡한 시스템이 자연스럽게 더 많은 콘텐츠를 만들어내니까, 그게 당연한 것처럼 보이게 되는 거죠.

“단순한 아키텍처를 쓰는 회사들은 ‘우리는 Postgres 쓰는데 잘 돌아가요’라는 블로그 글을 안 써요. 쓸 만한 이야기가 없거든요.” - Dan Luu

양자 컴퓨팅 발표가 1개 있었다는 건 재미있는 포인트예요. 효과적인 모놀리스 구축보다 양자 컴퓨팅이 더 실용적으로 보인다는 거죠!

경제학: 진짜 비용은 엔지니어링 시간이에요

Wave의 계산은 간단해요:

“우리 엔지니어링 팀 비용이 시스템 운영 비용보다 훨씬 더 커요.”

전략적 의미:

  • 복잡한 아키텍처는 다양한 분야의 전문 지식이 필요해요
  • 단순한 아키텍처라면 제너럴리스트 엔지니어도 전체 스택에서 생산적으로 일할 수 있어요
  • 대부분의 회사에서 개발자 생산성과 개발 속도가 인프라 최적화보다 중요해요

트레이드오프: 인프라 비용을 조금 더 쓰는 대신(그래도 여전히 적어요) 엔지니어링 복잡성을 확 줄이고 개발 속도를 높이는 거예요.

복잡성 예산: 정말 중요한 곳에만 쓰세요

모든 조직은 한정된 “복잡성 예산”이 있어요. 조직마다 어려운 기술 과제를 감당할 수 있는 역량이 정해져 있거든요.

Wave가 복잡성 예산을 안 쓰는 곳:

  • 너무 이른 아키텍처 고도화
  • 트렌디한 프레임워크
  • 불필요한 분산 처리

Wave가 복잡성 예산을 쓰는 곳:

  • 통신 통합: 피할 수 없는 복잡성이에요
  • 다국가 데이터 거주 요구사항 준수
  • 결제 처리 안정성

통신 통합: 엔지니어 비용보다 인프라 비용이 큰 유일한 예외

이상적으로는 모든 SMS를 SaaS 제공업체로 보내고 싶어요. 하지만 현실은 다르죠:

  • 지리적 제약: 주요 SaaS SMS 제공업체가 아프리카 전역에서 운영하지 않아요
  • 비용 구조의 역전: 모든 SMS에 SaaS를 쓰면 비용이 엄청나게 비싸요. 앞서 말한 “엔지니어링 팀 비용이 시스템 비용을 압도한다”는 원칙이 여기서는 성립하지 않아요
  • ROI가 명확해요: 통신 통합 팀이 자체 개발한 솔루션은 투자 비용을 몇 배로 회수해요

이건 Wave 아키텍처에서 유일하게 엔지니어 비용보다 인프라 비용이 더 중요한 영역이에요. 그래서 복잡성을 감수하고 직접 만든 거죠.

이게 복잡성 예산을 전략적으로 쓰는 완벽한 예시예요. 비즈니스에 실제로 영향을 주는 곳, 경제성이 분명한 곳에 복잡성을 투자하는 거예요.

온프레미스 데이터센터와 Kubernetes

Wave가 세네갈과 코트디부아르에서만 운영할 때는 완전히 클라우드에 있었어요. 하지만 우간다(그리고 미래의 다른 나라들)로 확장하면서 온프레미스 데이터센터를 배포해야 했어요. 현지 데이터 거주 법규와 규정을 준수하기 위해서예요.

Kubernetes를 쓰는 이유: 비즈니스가 성공하면(실제로 그렇게 됐어요) 계속 확장할 거라는 걸 알았어요. 각 나라마다 규정이 달라요:

  • 어떤 나라는 “주 데이터센터”를 국내에 운영하라고 요구해요
  • 어떤 나라는 국내 데이터센터로 페일오버할 수 있어야 한다고 요구해요
  • 간단한 모놀리스를 유지했기 때문에 복잡한 SOA보다 이 작업이 훨씬 쉬워요

언제 직접 만들어야 할까: Build vs Buy

초기에는 “직접 만들기(build)“보다 “구매하기(buy)“를 선호했어요. 소수 엔지니어 팀이 모든 걸 만들 여유가 없거든요. 구매한 도구가 제대로 안 돌아가도 그게 옳은 선택이었어요.

하지만 벤더가 우리에게 치명적인 버그를 고쳐주지 않는다면?

그럴 때는 더 많은 도구를 직접 만들고 사내 전문성을 키우는 게 맞아요. “핵심 역량에만 직접 만들라”는 표준 조언과 반대되지만요.

공정하게 말하면, 벤더 입장에서는 모든 고객을 위한 문제를 풀어야 하니 복잡해요. 우리는 한 고객(우리 자신)만 위해 풀면 되니 훨씬 단순하고요.

이게 또 다른 복잡성 트레이드오프예요. 벤더 도구의 복잡성을 감수할 것인가, 아니면 우리 문제에 맞춰 단순하게 직접 만들 것인가.

GraphQL: 복잡성이 정당화된 사례

GraphQL은 Wave가 채택한 기술 중 복잡성이 장점을 명확히 능가한 경우예요.

GraphQL의 장점:

  • 자동 문서화: API가 타입 시스템으로 자체 문서화돼요
  • 타입 안정성: 코드 생성으로 클라이언트가 더 안전해져요
  • API 공유: 여러 앱(사용자 앱, 지원 도구, 에이전트 앱 등)이 하나의 API를 공유해요
  • 정확한 데이터 페칭: 클라이언트가 필요한 데이터를 한 번의 요청으로 정확히 가져올 수 있어요
  • 대역폭 효율: 필요한 필드만 받아와서 아프리카의 제한된 대역폭 환경에서 중요해요

GraphQL의 단점:

  • 초기 GraphQL 라이브러리는 품질이 별로였어요
  • 기본 인코딩이 중복적이어서 대역폭이 중요한 우리에겐 문제였어요 (하지만 커스터마이징으로 해결했어요)

장점이 단점을 압도해요. 특히 여러 클라이언트를 지원하고 대역폭을 최적화해야 하는 Wave의 맥락에서요.

전략적 복잡성의 예:

기술 채택 여부 이유
Kubernetes ✅ 채택 다국가 온프레미스 배포 필요 (데이터 거주 법규 준수)
GraphQL ✅ 채택 장점 > 단점: 자체 문서화, 타입 안정성, 여러 앱이 하나의 API 공유, 정확한 데이터 페칭
비동기 프레임워크 (Eventlet) ❌ 거부 버그가 너무 많아서 운영 부담이 성능 향상을 압도함
마이크로서비스 ❌ 거부 (현재) 조직적/기술적 필요성 없음. 모놀리스로 충분히 확장 가능

핵심 통찰: 복잡성은 비즈니스에 실제로 필요해서 추가하는 거지, 유행 따라 도입하는 게 아니에요.

Wave의 기술 스택 선택

Python 모놀리스 + Postgres를 고수하는 이유:

  • 매월 수십억 건의 요청을 처리해요
  • 엔지니어가 전체 시스템을 이해할 수 있어요
  • 변경사항을 원자적으로 배포할 수 있어요
  • 디버깅이 간단해요 (단일 코드베이스, 통합 로깅)

마이크로서비스를 (아직) 안 쓰는 이유:

  • 팀마다 따로 움직여야 할 조직적 이유가 없어요
  • 기술적으로도 필요 없어요 (모놀리스로 규모 충분히 감당 가능)
  • 대신 이런 문제가 생겨요: 네트워크 복잡성, 배포 조정, 분산 디버깅, 데이터 일관성 문제
  • 비용 계산: 복잡성 관리하는 엔지니어링 시간 > 모놀리스 하드웨어 비용

비동기 프레임워크를 거부한 이유:

Wave는 보링한 동기 방식 Python을 써요. 서버 프로세스가 네트워크 요청 같은 I/O를 기다리는 동안 블로킹돼요.

Eventlet 실험:

이전에 Eventlet이라는 비동기 프레임워크를 써봤어요. 이론적으로는 Python에서 더 많은 효율을 뽑아낼 수 있었죠. 하지만 버그가 너무 많아서 CPU와 레이턴시 비용을 감수하는 게 Eventlet 문제를 다루는 운영 고통보다 낫다고 판단했어요.

경제성 계산:

  • 동기 Python은 비싸긴 해요 (네트워크 요청 기다리면서 CPU가 놀아요)
  • 하지만 Wave는 매월 수십억 건만 처리해요
  • 그 정도 규모라면, 느린 언어로 퍼블릭 클라우드 정가로 써도 비용이 낮아요
  • 비동기 프레임워크의 복잡성과 버그를 감수할 만큼 큰 비용 절감이 아니에요

최종 결정: 동기 방식 유지하고, 오래 걸리는 작업은 큐로 처리해요. 단순함이 이겨요.

통신 프로토콜의 진화: UDP에서 HTTP/3로

Wave의 통신 프로토콜 선택은 아프리카 시장의 실용적 요구를 반영해요.

초기: 커스텀 UDP 프로토콜

처음에는 성능 때문에 UDP 위에서 돌아가는 커스텀 프로토콜을 만들어 썼어요. SMS와 USSD를 폴백으로 뒀고요. 아프리카의 네트워크 환경에서는 낮은 대역폭과 불안정한 연결이 흔하니까, 커스텀 프로토콜이 필요했어요.

현재: HTTP/3로 마이그레이션

HTTP/3가 나오면서 커스텀 프로토콜을 HTTP/3로 대체할 수 있었어요. HTTP/3는 QUIC 위에서 돌아가고, 우리가 커스텀 프로토콜로 해결하려던 문제들(패킷 손실 복구, 낮은 지연 시간 등)을 표준 프로토콜로 해결해줘요.

요즘은 말리에서 있었던 인터넷 차단 같은 극단적인 경우에만 USSD가 필요해요.

이게 또 다른 “복잡성 예산” 사례죠. 초기에는 커스텀 프로토콜이 필요했지만, 기술이 발전하면서 표준 프로토콜로 전환할 수 있었어요. 불필요해진 복잡성은 제거하는 거예요.

초기에 했던 실수들

Wave도 완벽하지 않아요. 초기 몇 달간 했던 실수들이 지금도 비용을 내고 있어요:

SQLAlchemy 트랜잭션 경계를 제대로 안 정한 것

Wave 코드베이스에서 SQLAlchemy 데이터베이스 세션은 요청 전역 변수예요. DB 객체의 속성에 접근하면 암시적으로 새 트랜잭션을 시작하고, 코드 어디서든 세션에 commit을 호출할 수 있어요. 이게 모든 대기 중인 업데이트를 커밋시켜요.

문제점:

  • 데이터베이스 업데이트 시점을 통제하기 어려워요: 어떤 함수든 세션을 커밋할 수 있어서, 데이터가 언제 실제로 DB에 쓰이는지 예측하기 힘들어요
  • 미묘한 데이터 무결성 버그 발생률이 높아져요: 트랜잭션 경계가 명확하지 않아서 부분적으로만 커밋되거나 일관성이 깨지는 상황이 생겨요
  • 멱등성 키(idempotency key)나 트랜잭션 기반 작업 큐(transactionally-staged job drain)를 구축하기 어려워요: 트랜잭션 경계를 제어하지 못하면 이런 패턴을 안전하게 구현할 수 없어요
  • 실수로 오래 걸리는 DB 트랜잭션을 열어둘 위험이 있어요: 이러면 스키마 마이그레이션 운영이 악몽이 돼요. 긴 트랜잭션이 테이블을 락 걸고 있으면 마이그레이션이 막히거든요

이건 기술적으로 흥미로운 문제예요. SQLAlchemy의 암시적 트랜잭션 시작은 편리해 보이지만, 실제로는 언제 데이터가 커밋되는지 추론하기 어렵게 만들어요. 명시적 트랜잭션 경계를 처음부터 설계했다면 훨씬 나았을 거예요.

RabbitMQ + Celery 선택

  • RabbitMQ 대신 Redis를 썼어야 했어요: Wave의 용도로는 Redis를 태스크 큐로 써도 충분했을 거예요. RabbitMQ는 운영 부담만 늘렸어요. Redis 하나만 쓰면 관리할 시스템이 하나 줄어들죠
  • Celery는 우리 유스케이스에 비해 너무 복잡해요: 버전 업그레이드할 때마다 하위 호환성 문제로 여러 번 장애가 났어요. 더 단순한 태스크 큐 라이브러리로도 충분했을 거예요
  • SQLAlchemy ORM의 암시적 동작: 개발자가 코드가 어떤 DB 쿼리를 날릴지 이해하기 어려워요. ORM이 뒤에서 무슨 일을 하는지 보이지 않아서 디버깅하기 힘든 상황들을 만들어요

하지만 계속 쓰고 있어요. 새로 마이그레이션하는 비용이 지금 이대로 쓰면서 감수하는 비용보다 크기 때문이에요.

이게 진짜 실용적인 접근이죠. 모든 기술 부채를 당장 갚을 필요는 없어요.

주류 vs. 반주류 서사

주류 서사 Dan Luu의 반대 서사
마이크로서비스는 현대적이고 성숙한 아키텍처다 마이크로서비스는 종종 너무 이른 최적화예요
모놀리스는 스케일 안 된다 모놀리스는 생각보다 훨씬 큰 규모까지 버텨요
성공한 회사들은 복잡한 분산 시스템을 쓴다 성공한 회사들도 실은 단순한 시스템을 많이 써요 (그냥 얘기를 안 하는 거죠)
업계 리더들의 모범 사례를 따라야 한다 Google/Netflix의 “모범 사례”가 70명짜리 회사에는 안 맞을 수 있어요

왜 이게 중요할까요

스타트업/소기업에게:

더 오래 단순하게 가도 괜찮다는 걸 보여줘요. Netflix가 한다고 마이크로서비스 쓰지 마세요. 조직 구조나 규모상 진짜 필요해질 때 도입하세요.

중규모 회사에게:

복잡한 아키텍처 없이도 10억 달러 이상 가치평가를 받을 수 있다는 증거예요. 인프라 고도화가 아니라 제품/시장 적합성에 엔지니어링 노력을 쏟으세요.

업계에게:

“현대적” 아키텍처가 항상 최신 패턴을 따라야 한다는 통념에 도전해요. 때로는 지루한 게 더 나아요.

시간을 초월한 원칙

  • 복잡성은 그냥 기술적인 특징이 아니라 실제 비용이에요
  • 남들이 좋다고 하는 방식이 아니라, 우리한테 실제로 필요한 걸 기준으로 아키텍처를 짜세요
  • 진짜 귀한 건 엔지니어링 시간이지, 인프라가 아니에요
  • 전략적으로 집중하려면 아키텍처 유행에 “아니오”라고 할 줄 알아야 해요
“복잡성 예산”이라는 개념은 특정 기술과 관계없이 작동해요. 미래 트렌드가 서버리스든, 엣지 컴퓨팅이든, 뭐든 간에, 복잡성을 전략적으로 쓰는 원칙은 계속 유효해요.

결론

Dan Luu의 메시지는 명확해요:

  1. 컴퓨터는 충분히 빠르다: 단순한 아키텍처는 생각보다 훨씬 큰 규모까지 버텨요
  2. 복잡성은 비용이다: 한정된 예산처럼 전략적으로 써야 해요
  3. 컨퍼런스는 착각을 만든다: 선택 편향이 복잡한 게 당연한 것처럼 보이게 만들어요
  4. 화물 숭배를 피하라: Google 방식이 우리 회사에는 안 맞을 수 있어요

완벽은 출시의 적이에요. 때로는 단순하고 지루한 게 최고예요.

추가 참고사항

참고 1: Dan Luu의 원문에 따르면, Wave가 17억 달러 가치평가를 받았을 당시 엔지니어는 약 40명이었어요. 현재는 70명 규모로 성장했고요.

참고 2: “엔지니어 비용이 인프라 비용을 압도한다”는 원칙이 성립하지 않는 경우도 있어요. 광고 기반 소셜 미디어처럼 인프라 비용이 엄청나게 큰 비즈니스 모델이 그 예죠. Wave의 통신 통합도 예외 사례고요.

참고 3: Wave는 가격 경쟁력으로 아프리카의 모바일 머니 비용을 크게 낮췄어요. 단순한 아키텍처가 낮은 운영 비용을 가능하게 했고, 이게 사용자에게 혜택으로 돌아간 거죠.

저자 소개: Dan Luu는 Wave의 CTO였고, 대규모 분산 시스템을 다뤄온 엔지니어이자 기술 작가로, 엔지니어링 실용주의와 반주류 관점으로 유명해요.

참고: 이 글은 Dan Luu가 개인 블로그에 게시한 아티클을 번역하고 요약한 것입니다.

원문: In Defense of Simple Architectures - Dan Luu (2022년 4월 1일)

생성: Claude (Anthropic)

총괄: (디노이저denoiser)