MMeshOps
마이크로서비스의 함정: 쪼개기 전에 알아야 할 아키텍처 설계
3예상 10중급

쪼갤수록 늘어나는 고통: 분산 시스템의 비용

서비스를 여러 개로 쪼갰을 때 발생하는 네트워크 지연(Latency), 데이터 정합성 문제 등 분산 시스템의 복잡성을 이해합니다.

우리는 종종 시스템을 작게 나누면 모든 것이 더 간단해질 것이라 믿습니다. "회원 관리는 회원 서버가, 결제는 결제 서버가 전담하면 완벽하지 않을까?"라는 논리입니다. 하지만 시스템을 분리하는 순간, 우리는 '단일 서버'라는 안전한 울타리를 벗어나 분산 시스템(Distributed Systems)이라는 험난한 야생에 발을 들이게 됩니다. 이번 에피소드에서는 서버를 쪼갰을 때 우리가 치러야 하는 숨겨진 비용과 고통들에 대해 알아봅니다.

네트워크는 결코 믿을 수 없다

단일 서버(Monolith) 환경에서 코드와 코드 사이의 대화는 '함수 호출'을 통해 이루어집니다. 이는 컴퓨터 메모리 안에서 빛의 속도로 일어나며, 메모리가 고장 나지 않는 한 절대 실패하지 않습니다.

하지만 시스템을 쪼개면 이야기가 달라집니다. '주문 서버'와 '결제 서버'는 물리적으로 떨어진 두 대의 컴퓨터가 되며, 이들은 랜선이라는 네트워크(Network)를 통해 대화해야 합니다.

피할 수 없는 네트워크 지연(Latency)

네트워크를 통한 통신은 함수 호출에 비해 압도적으로 느립니다. 아무리 빠른 네트워크망을 구축하더라도 물리적인 한계로 인해 지연 시간(Latency)이 발생합니다.

하나의 요청을 처리하기 위해 5개의 마이크로서비스를 거쳐야 한다고 가정해 보겠습니다. 클라이언트 ➔ API 게이트웨이 ➔ 주문 서비스 ➔ 재고 서비스 ➔ 결제 서비스 ➔ 알림 서비스. 각 구간마다 네트워크 지연이 누적되면, 최종 사용자에게 응답하는 속도는 눈에 띄게 느려질 수밖에 없습니다.

'통신 실패'라는 새로운 일상

가장 심각한 문제는 네트워크가 언제든 끊길 수 있다는 사실입니다.

  • 스위치 장비의 일시적 오류
  • 트래픽 폭주로 인한 타임아웃(Timeout)
  • 상대방 서버의 일시적 다운

분산 시스템에서는 이러한 네트워크 실패(Network Failures)를 예외적인 상황이 아니라 '항상 일어나는 일상'으로 가정하고 코드를 작성해야 합니다. 요청이 실패하면 몇 초 뒤에 다시 시도할지(Retry), 몇 번 시도하고 포기할지 코드로 모두 구현해야 하며, 이는 시스템의 복잡도를 기하급수적으로 높입니다.

분산 시스템에서 통신 설계 시 점검 사항

  • 대상 서비스가 응답하지 않을 때 얼마나 기다릴 것인가? (Timeout 설정)
  • 요청이 실패했을 때 재시도 로직이 안전하게 구현되어 있는가? (Retry 전략)
  • 동일한 요청을 여러 번 보내도 데이터가 안전한가? (멱등성 보장)
  • 한 서비스의 장애가 다른 서비스로 연쇄적으로 번지지 않는가? (장애 격리)

데이터는 어떻게 일치시킬 것인가?

데이터베이스를 하나로 공유하는 단일 환경에서는 트랜잭션(Transaction)이라는 강력한 마법을 쓸 수 있습니다. 돈을 이체할 때 '내 계좌에서 출금'과 '상대방 계좌로 입금'을 하나로 묶어, 중간에 에러가 나면 둘 다 취소(Rollback)해버릴 수 있습니다.

분산 트랜잭션의 악몽

하지만 마이크로서비스 환경에서는 서비스별로 각자의 독립된 데이터베이스를 가집니다. '주문 DB'와 '결제 DB'가 완전히 분리되어 있는 것입니다.

만약 주문 데이터는 성공적으로 저장되었는데, 결제 서버의 네트워크가 끊겨 결제 승인이 나지 않았다면 어떻게 해야 할까요? 이미 성공한 주문 데이터를 찾아가 다시 '취소' 상태로 업데이트하는 복잡한 보상 트랜잭션(Compensating Transaction) 코드를 작성해야 합니다.

사가(Saga) 패턴을 통한 분산 트랜잭션 복구 흐름

주문 서비스

1. 주문 생성 성공

주문 DB

주문 서비스

2. 결제 요청 전송

결제 서비스

결제 서비스

3. 결제 실패 반환

주문 서비스

주문 서비스

4. 주문 취소(보상) 실행

주문 DB

데이터의 정합성을 맞추기 위해 'Saga 패턴'이나 '2단계 커밋(2PC)' 같은 고도화된 아키텍처 패턴을 도입해야 강제로, 이는 개발자들이 비즈니스 로직보다 에러 복구 로직 작성에 더 많은 시간을 쏟게 만듭니다.

데이터 중복과 동기화 문제

쇼핑몰의 '주문 내역' 페이지를 띄워주기 위해 필요한 정보들을 생각해 봅시다.

  • 사용자 이름 (회원 서비스 소유)
  • 구매한 상품명 (상품 서비스 소유)
  • 주문 날짜와 수량 (주문 서비스 소유)

마이크로서비스 환경에서는 이 화면 하나를 그리기 위해 클라이언트가 3개의 다른 서버에 요청을 보내 조합하거나, 서버들끼리 서로 API를 호출하여 데이터를 모아야 합니다. (이를 방지하기 위해 데이터를 이중으로 저장하고 동기화하는 기법을 쓰기도 하지만, 이 역시 엄청난 관리 비용이 듭니다.)

결국 단일 DB에서 SQL의 `JOIN` 명령어 하나면 끝날 일이, 분산 시스템에서는 수십 줄의 API 호출 코드와 에러 처리 로직으로 둔갑하게 됩니다.

눈에 보이지 않는 인프라 유지보수 비용

서버가 여러 개라는 것은 관리해야 할 대상이 늘어난다는 것을 의미합니다.

단일 서버에서는 서버 한 대의 CPU와 메모리만 모니터링하면 되었습니다. 반면 마이크로서비스에서는 10개의 서비스가 있다면 10배의 서버 자원을 모니터링해야 합니다. 각 서비스 간의 통신이 원활한지, 병목이 발생하는 구간은 없는지 파악하기 위해 서비스 메시(Service Mesh)나 분산 로깅 시스템 같은 무거운 인프라 도구들을 반드시 구축해야 합니다.

시스템을 쪼갤수록 독립성은 얻을지 모르지만, 우리가 지불해야 하는 운영 복잡성(Operational Complexity)의 청구서는 무섭게 불어납니다.

개념 퀴즈

퀴즈 답을 맞춰야 학습 완료가 됩니다.

분산 시스템(마이크로서비스) 환경에서 데이터베이스가 여러 개로 분리되었을 때 발생하는 주요 문제점은 무엇인가요?

3줄 요약

  1. 1분산 시스템은 내부 함수 호출 대신 물리적 네트워크를 사용하므로, 필연적으로 통신 지연(Latency)과 실패가 발생합니다.
  2. 2서비스별로 DB가 쪼개져 있어 단일 트랜잭션을 쓸 수 없으므로, 데이터 정합성을 맞추는 과정이 극도로 복잡해집니다.
  3. 3다수의 서버 장비와 통신망을 모니터링하기 위해 인프라 관리 및 유지보수 비용이 급격하게 증가합니다.