[대규모 시스템 설계 스터디] 1장 정리

현실도피·2026년 7월 8일
post-thumbnail

다음 포스트: [ 대규모 시스템 설계 스터디 ] 2장 정리

들어가기에 앞서서

이번 방학 기간동안 대규모 시스템 설계 스터디를 진행한다.

교재는 가상 면접 사례로 배우는 대규모 시스템 설계 기초 를 사용했다.

기간은 7월 2일 ~ 9월 12일까지로 예상한다.

각 주마다 2번씩 진행되고, 스터디 과제는 벨로그 작성과 각 단원 문제풀이로 진행하며, 중간마다 시험문제를 출제하여 수준을 검토한다.

최종적으론 대규모 프로젝트를 진행하여 스터디를 마무리한다.


1장. 사용자 증가에 따른 시스템 규모 확장

대규모 시스템이라고 해서 처음부터 복잡한 구조로 시작하는 것은 아니다.

처음에는 사용자 한 명을 처리할 수 있는 단순한 서버 구조에서 출발하고, 사용자가 증가하면서 발생하는 문제를 하나씩 해결하는 방식으로 시스템을 확장할 수 있다.

이번 장에서는 다음과 같은 흐름으로 시스템이 발전한다.

단일 서버
→ 웹 서버와 DB 분리
→ 로드밸런서
→ DB 다중화
→ 캐시
→ CDN
→ 무상태 웹 계층
→ 다중 데이터 센터
→ 메시지 큐
→ 모니터링 및 자동화
→ 데이터베이스 샤딩

결국 핵심은 특정 서버 하나에 모든 역할과 부하가 집중되지 않도록 시스템을 계속 분리하고 분산하는 것이다.


1. 가장 단순한 구조: 단일 서버

서비스 초기에는 하나의 서버에서 대부분의 기능을 처리할 수 있다.

사용자
 ↓
DNS
 ↓
웹 서버

사용자가 api.mysite.com과 같은 주소로 서비스에 접근하면 다음 과정이 진행된다.

  1. 사용자가 도메인 주소로 서비스 접속
  2. DNS를 통해 도메인에 해당하는 IP 주소 조회
  3. 조회한 IP 주소로 HTTP 요청 전송
  4. 웹 서버가 요청을 처리
  5. HTML 또는 JSON 등의 형태로 응답 반환

서비스 규모가 작다면 이 정도 구조만으로도 충분하다.

하지만 사용자가 증가하기 시작하면 하나의 서버가 요청 처리와 데이터 관리까지 모두 담당하는 구조에는 한계가 발생한다.


웹과 모바일의 서버 통신 방식

웹과 모바일은 서버로부터 필요한 데이터의 형태가 조금 다를 수 있다.

웹 브라우저

브라우저
← HTML / CSS / JS / 데이터

브라우저에서 화면을 구성하기 위해 HTML이나 JavaScript 등의 리소스를 전달받을 수 있다.

모바일 앱

모바일 앱
← JSON 데이터

모바일 앱은 애플리케이션 내부에 이미 화면을 구성하는 코드가 존재하기 때문에 서버에서는 필요한 데이터를 API 형태로 전달하는 경우가 많다.

현대적인 웹 애플리케이션 역시 React, Vue 등의 프론트엔드를 별도로 구성하고 서버에서는 JSON API만 제공하는 구조를 많이 사용한다.

즉 단순하게 이해하면 다음과 같다.

웹 서버 → 화면을 구성하는 리소스 제공 가능
API 서버 → 클라이언트가 사용할 데이터 제공

2. 웹 서버와 데이터베이스 분리

사용자가 증가하면 하나의 서버에서 모든 기능을 처리하는 구조를 유지하기 어렵다.

따라서 역할을 분리한다.

사용자
 ↓
웹 서버
 ↓
데이터베이스
  • 웹 계층: 사용자 요청 및 비즈니스 로직 처리
  • 데이터 계층: 데이터 저장 및 조회

이렇게 분리하면 웹 서버와 데이터베이스를 각각 독립적으로 확장할 수 있다.

예를 들어 요청 처리량이 부족하다면 웹 서버를 확장하고, 저장 공간이나 데이터 처리량이 부족하다면 데이터베이스를 확장할 수 있다.


관계형 DB와 NoSQL

데이터베이스는 크게 다음과 같이 구분할 수 있다.

RDBMS

관계형 데이터베이스

대표적인 예시

  • MySQL
  • PostgreSQL
  • Oracle

테이블 사이의 관계를 기반으로 데이터를 관리하며 JOIN 등의 연산을 지원한다.

NoSQL

관계형 구조를 반드시 사용하지 않는 데이터베이스

대표적으로 다음과 같은 형태가 있다.

  • Key-Value
  • Document
  • Column
  • Graph

예시

  • DynamoDB
  • CouchDB
  • HBase

일반적인 서비스에서는 관계형 데이터베이스를 많이 사용하지만 다음과 같은 상황에서는 NoSQL이 적합할 수 있다.

  • 매우 낮은 응답 시간이 필요한 경우
  • 데이터 구조가 일정하지 않은 경우
  • 대규모 데이터를 저장해야 하는 경우
  • 단순한 Key-Value 형태의 읽기·쓰기가 많은 경우

직렬화와 역직렬화

NoSQL을 공부하면서 함께 이해하면 좋은 개념이다.

직렬화 Serialization

프로그램 내부의 객체를 저장하거나 전송할 수 있는 형태로 변환하는 과정

Java Object
   ↓
JSON / Byte

예를 들어 Java 객체를 JSON 문자열로 변환하여 Redis나 네트워크를 통해 전달할 수 있다.

역직렬화 Deserialization

저장하거나 전달받은 데이터를 다시 프로그램에서 사용할 수 있는 객체로 복원하는 과정

JSON / Byte
   ↓
Java Object

Key-Value 저장소에서는 복잡한 JOIN을 수행하기보다 객체를 JSON 등의 형태로 저장해두고 필요할 때 다시 객체로 복원하여 사용하는 구조도 가능하다.


3. 서버 규모를 확장하는 두 가지 방법

사용자가 계속 증가하면 서버 성능을 확장해야 한다.

방법은 크게 두 가지다.

수직적 확장 Scale-Up

기존 서버의 성능을 높이는 방법

CPU ↑
Memory ↑
Disk ↑

장점

  • 구조가 단순함
  • 기존 시스템의 변경이 비교적 적음

하지만 한계도 명확하다.

  • 하드웨어 성능에 물리적인 한계 존재
  • 고성능 장비일수록 비용이 크게 증가
  • 서버 한 대가 장애를 일으키면 전체 서비스에 영향을 줄 가능성

특히 서버 하나에 시스템 전체가 의존하는 구조를 SPOF(Single Point of Failure)라고 한다.


수평적 확장 Scale-Out

서버 성능 자체를 높이는 대신 서버의 수를 늘리는 방법

Server 1
Server 2
Server 3
...

대규모 시스템에서는 일반적으로 수평 확장을 적극적으로 사용한다.

다만 서버가 여러 대가 되면 새로운 문제가 발생한다.

사용자의 요청을 어떤 서버로 전달할 것인가?

이 문제를 해결하기 위해 로드밸런서를 사용한다.


4. 로드밸런서

로드밸런서는 여러 웹 서버 앞에서 사용자 요청을 적절한 서버로 나누어 전달한다.

        사용자
          ↓
     Load Balancer
       ↙      ↘
 Web Server1  Web Server2

사용자는 웹 서버에 직접 접속하지 않고 로드밸런서를 통해 서비스에 접근한다.

외부에서는 로드밸런서의 공개 주소를 사용하고, 로드밸런서와 웹 서버 사이에서는 내부 네트워크의 사설 IP를 사용할 수 있다.

예를 들어 클라우드 환경에서는 다음과 같은 구조가 가능하다.

Internet
   ↓
Load Balancer
   ↓
Private Network
 ↙          ↘
EC2 #1      EC2 #2

로드밸런서를 사용하는 이유

1. 장애 대응

Server 1에 문제가 발생한다면

Server 1 ✕
Server 2 ○

정상적으로 동작하는 Server 2로 요청을 전달할 수 있다.

2. 트래픽 증가 대응

기존 서버가 트래픽을 처리하기 어려워지면 새로운 서버를 추가한다.

Server 1
Server 2
Server 3 ← 추가

로드밸런서는 추가된 서버까지 포함하여 요청을 분산할 수 있다.

즉,

로드밸런서는 서버 수평 확장을 가능하게 만드는 핵심 구성 요소 중 하나다.


5. 데이터베이스 다중화

웹 서버를 여러 대 구성하더라도 데이터베이스 하나에 장애가 발생하면 서비스가 정상적으로 동작하기 어렵다.

따라서 데이터베이스도 여러 개로 구성할 수 있다.

대표적인 방식이 Primary-Replica 구조다.

             Primary DB
             /        \
       Replica 1    Replica 2

Primary

주로 데이터 변경 담당

  • INSERT
  • UPDATE
  • DELETE

Replica

Primary의 데이터를 복제하여 보관하고 주로 조회 요청을 담당

  • SELECT

일반적으로 서비스에서는 데이터 변경보다 조회 요청이 훨씬 많을 수 있기 때문에 여러 Replica를 두어 읽기 부하를 분산할 수 있다.


DB 다중화의 장점

성능

조회 요청을 여러 Replica가 나누어 처리할 수 있다.

안정성

데이터를 여러 서버에 복제하기 때문에 서버 하나가 손상되어도 다른 서버에 데이터가 남아 있을 수 있다.

가용성

일부 서버에 장애가 발생하더라도 나머지 서버를 통해 서비스를 계속 제공할 수 있다.


DB 장애가 발생한다면

Replica 한 대가 장애

읽기 요청을 다른 DB가 처리하도록 전환한다.

여러 Replica 중 하나가 장애

정상 Replica 서버가 읽기 요청을 대신 처리한다.

Primary가 장애

Replica 중 하나를 새로운 Primary로 승격시키는 방식으로 복구할 수 있다.

실제 운영에서는 복제 지연, 장애 감지, 승격 과정 등의 추가적인 문제가 존재하지만 기본적인 개념은 다음과 같다.

데이터를 여러 서버에 복제하여 하나의 DB에 대한 의존도를 줄이는 것


6. 캐시

데이터베이스 조회가 많아지면 DB 역시 부하가 발생한다.

하지만 모든 요청이 반드시 DB까지 갈 필요는 없다.

자주 사용하는 데이터를 메모리에 저장해두고 바로 사용하는 것이 캐시(Cache)다.

사용자 요청
    ↓
   Cache
   ↓ MISS
Database

Cache Hit

필요한 데이터가 캐시에 존재

요청 → Cache → 응답

Cache Miss

캐시에 데이터가 없음

요청
 ↓
Cache MISS
 ↓
Database
 ↓
Cache 저장
 ↓
응답

이 구조를 통해 DB 조회 횟수를 줄이고 응답 속도를 높일 수 있다.


캐시 사용 시 고려할 점

어떤 데이터를 캐싱할 것인가

변경은 적지만 조회가 자주 발생하는 데이터에 적합하다.

데이터 만료 시간

캐시는 일정 시간이 지나면 데이터를 제거하도록 TTL 등을 설정할 수 있다.

만료 시간이 지나치게 짧으면

Cache Miss 증가
→ DB 요청 증가

반대로 너무 길면

Cache 데이터
≠
DB 데이터

가 될 가능성이 높아진다.

캐시 크기

메모리가 너무 작으면 필요한 데이터가 계속 제거되면서 캐시 효율이 떨어진다.

캐시 방출 정책

캐시 공간이 부족할 경우 어떤 데이터를 제거할지 결정해야 한다.

대표적으로

  • LRU
  • LFU
  • FIFO

등의 정책을 사용할 수 있다.


7. CDN

캐시는 주로 애플리케이션의 데이터 조회 성능을 개선한다면, CDN은 정적 파일 전달 속도를 개선하는 역할을 담당한다.

CDN(Content Delivery Network)은 여러 지역에 분산된 서버에 정적 콘텐츠를 저장해두고 사용자와 가까운 서버에서 전달하는 방식이다.

대표적인 정적 콘텐츠

  • 이미지
  • 영상
  • CSS
  • JavaScript

CDN 동작 방식

사용자
 ↓
가까운 CDN 서버
 ↓
Origin Server

처음 요청한 파일이 CDN에 없다면

  1. 사용자가 CDN에 이미지 요청
  2. CDN에 해당 파일이 존재하지 않음
  3. CDN이 Origin Server에서 파일 조회
  4. CDN에 파일 저장
  5. 사용자에게 전달

이후 같은 파일에 대한 요청이 들어오면

사용자
 ↓
CDN Cache
 ↓
바로 응답

할 수 있다.


S3와 CDN의 차이

처음에는 S3가 이미지 전달 속도를 직접 높여주는 역할이라고 생각했는데 둘의 역할은 다르다.

S3

파일을 저장하는 공간

CDN

파일을 사용자 가까운 위치에서 빠르게 전달하는 역할

예를 들어

이미지 저장
Web Server → S3
이미지 조회
사용자 → CDN → S3

와 같은 구조를 사용할 수 있다.

정리하면

S3  = 저장소 분리
CDN = 콘텐츠 전달 최적화

8. 무상태 웹 계층

웹 서버를 여러 대 운영하려면 한 가지 중요한 조건이 있다.

특정 사용자 정보가 특정 웹 서버에 종속되지 않아야 한다.


상태 정보를 서버가 가지고 있는 경우

예를 들어 사용자 A의 로그인 세션이 Server 1에만 저장되어 있다고 가정한다.

User A
 ↓
Server 1 ← 세션 정보 존재

이 경우 다음 요청 역시 Server 1으로 보내야 한다.

Server 2로 요청이 전달되면 사용자 정보를 알 수 없기 때문이다.

이를 해결하기 위해 Sticky Session 등을 사용할 수 있지만 서버 추가·삭제와 장애 대응이 복잡해질 수 있다.


Stateless 구조

사용자 상태를 웹 서버 외부의 공용 저장소에 보관한다.

            Shared Storage
                 ↑
       ┌─────────┼─────────┐
       ↓         ↓         ↓
    Server1   Server2   Server3

어떤 서버가 요청을 받아도 필요한 정보를 동일한 저장소에서 조회할 수 있다.

따라서

User
 ↓
Load Balancer
 ↓
아무 Web Server

형태가 가능하다.


장점

  • 서버 추가가 쉬움
  • 서버 제거가 쉬움
  • 특정 서버 장애에 대한 의존도 감소
  • 자동 확장과 결합하기 쉬움

예를 들어 트래픽이 증가하면

Server +1

트래픽이 감소하면

Server -1

하는 방식으로 자동 확장할 수 있다.


9. 여러 데이터 센터 사용

서비스가 더 커지면 하나의 지역이나 데이터 센터만 사용하는 것에도 한계가 생긴다.

따라서 여러 지역에 데이터 센터를 구축할 수 있다.

             User
              ↓
            DNS
          ↙     ↘
     Data Center A
     Data Center B

사용자의 위치에 따라 가까운 데이터 센터로 연결하는 방식을 사용할 수 있다.

이러한 방식과 관련된 개념이 GeoDNS다.

GeoDNS는 사용자의 지역에 따라 서로 다른 IP 주소를 반환할 수 있도록 DNS를 구성하는 방식이다.


데이터 센터 장애

예를 들어 Data Center A에 장애가 발생하면

Data Center A ✕
       ↓
Data Center B ○

정상 데이터 센터로 트래픽을 우회할 수 있다.

하지만 다중 데이터 센터 구조를 구축하려면 다음 문제들을 고려해야 한다.

트래픽 우회

어떤 데이터 센터로 사용자를 전달할 것인가

데이터 동기화

여러 데이터 센터가 동일한 데이터를 어떻게 유지할 것인가

테스트와 배포

여러 지역에 있는 시스템을 어떻게 안정적으로 배포하고 검증할 것인가

결국 서비스 규모가 커질수록 각 시스템 구성 요소를 분리하고 독립적으로 확장하는 구조가 중요해진다.


10. 메시지 큐

서비스가 여러 개로 분리되면 서비스 간 통신도 증가한다.

모든 작업을 동기적으로 처리하면 한 서비스의 처리 시간이 다른 서비스의 응답 속도에도 영향을 줄 수 있다.

이때 사용할 수 있는 것이 메시지 큐(Message Queue)다.

Producer
   ↓
Message Queue
   ↓
Consumer

Producer

처리할 메시지를 생성해 큐에 전달

Consumer

큐에 저장된 메시지를 가져가 실제 작업 수행


메시지 큐를 사용하는 이유

예를 들어 이미지 처리 작업이 있다고 가정한다.

메시지 큐가 없다면

사용자 요청
 ↓
이미지 처리 완료까지 대기
 ↓
응답

하지만 메시지 큐를 사용하면

사용자 요청
 ↓
Queue에 작업 등록
 ↓
응답

        Queue
          ↓
      Worker 처리

처럼 작업을 분리할 수 있다.

이를 통해 서비스 간 결합도를 낮출 수 있다.

생산자는 소비자의 작업 완료를 직접 기다릴 필요가 없고, 소비자 역시 자신의 처리 속도에 맞춰 메시지를 가져올 수 있다.


비동기 처리

비동기는 쉽게 말하면

작업이 끝날 때까지 현재 요청이 계속 기다리지 않는 구조

다.

처리 시간이 긴 작업을 별도로 분리할 때 유용하다.


11. 로그, 메트릭, 자동화

시스템 규모가 커질수록 서버가 정상적으로 동작하고 있는지 직접 확인하기 어렵다.

따라서 시스템의 상태를 관찰할 수 있는 장치가 필요하다.


로그 Log

서비스에서 발생한 사건을 기록한 데이터

예시

사용자 로그인
API 호출
DB 오류
서버 예외

로그를 중앙에서 수집하면 문제가 발생했을 때 원인을 추적하기 쉬워진다.


메트릭 Metric

시스템 상태를 숫자로 측정한 값

예를 들어

서버 메트릭

  • CPU 사용률
  • Memory 사용률
  • Disk I/O

시스템 메트릭

  • DB 처리량
  • Cache Hit Rate
  • API 응답 시간

비즈니스 메트릭

  • 일일 활성 사용자
  • 매출
  • 재방문율

등을 수집할 수 있다.

로그가

무슨 일이 발생했는가

를 확인하는 데 가깝다면,

메트릭은

현재 시스템 상태가 어떠한가

를 수치로 확인하는 데 가깝다고 이해했다.


자동화

시스템 규모가 커지면 모든 작업을 사람이 직접 수행하기 어렵다.

따라서 반복적인 운영 작업을 자동화한다.

예를 들면

트래픽 증가
 ↓
서버 자동 추가

트래픽 감소
 ↓
서버 자동 제거

와 같은 Auto Scaling도 자동화의 한 사례로 볼 수 있다.


12. 데이터베이스 규모 확장

서비스의 데이터가 계속 증가하면 결국 데이터베이스 역시 확장해야 한다.

데이터베이스도 서버와 동일하게 두 가지 방식으로 확장할 수 있다.


DB 수직 확장

하나의 DB 서버 성능을 높이는 방식

CPU ↑
Memory ↑
Storage ↑

구조는 비교적 단순하지만

  • 하드웨어 한계
  • 높은 비용
  • 단일 장애점 가능성

등의 문제가 존재한다.


DB 수평 확장: Sharding

데이터베이스 자체를 여러 서버로 나누는 방법을 샤딩(Sharding)이라고 한다.

Database
   ↓
┌───────┬───────┬───────┐
Shard 1 Shard 2 Shard 3

각 샤드는 전체 데이터 중 일부만 저장한다.

예를 들어 user_id를 기준으로 나눈다면

user_id 1 → Shard A
user_id 2 → Shard B
user_id 3 → Shard C

와 같이 데이터를 분산할 수 있다.

이때 데이터를 어떤 샤드에 저장할지 결정하는 값을 Shard Key 또는 Partition Key라고 한다.


샤딩에서 중요한 문제

1. Resharding

데이터가 증가하면서 특정 샤드의 저장 공간이 부족해질 수 있다.

또는 데이터가 고르게 나누어지지 않아 특정 샤드에 데이터가 집중될 수도 있다.

이 경우 데이터 분배 방식을 변경하고 기존 데이터를 다시 배치해야 한다.

이와 관련하여 Consistent Hashing과 같은 기법을 사용할 수 있다.


2. Hotspot Key

특정 데이터에 트래픽이 지나치게 집중되는 문제다.

예를 들어 매우 유명한 사용자의 데이터가 하나의 샤드에 저장되어 있다면

Shard A ← 요청 폭증
Shard B ← 정상
Shard C ← 정상

처럼 특정 서버만 과부하가 발생할 수 있다.

이러한 데이터는 더 세분화하거나 별도의 처리 전략을 고려해야 한다.


3. JOIN 문제

샤딩을 적용하면 서로 다른 데이터가 물리적으로 다른 서버에 존재할 수 있다.

User → Shard A
Order → Shard B

이 경우 여러 샤드를 넘나드는 JOIN은 단일 DB 내부의 JOIN보다 복잡해질 수 있다.

따라서 필요한 데이터를 일부 중복 저장하는 비정규화(Denormalization) 등의 방식을 통해 조회 구조를 단순화하기도 한다.


전체 흐름 정리

이번 장에서 가장 중요하게 느낀 부분은 각각의 기술을 따로 외우는 것보다 어떤 문제가 발생했고, 그 문제를 해결하기 위해 다음 기술이 등장했는지 이해하는 것이었다.

1단계

단일 서버

사용자가 적을 때는 충분하다.

2단계

Web Server
   ↓
Database

웹 서버와 데이터 저장소의 역할 분리

3단계

      Load Balancer
       ↙       ↘
   Server1   Server2

웹 서버 수평 확장

4단계

Primary DB
  ↓
Replicas

DB 조회 부하 분산 및 장애 대응

5단계

Cache
CDN

반복적인 DB 조회와 정적 파일 전달 비용 감소

6단계

Stateless Web Server

어떤 서버가 요청을 처리해도 동일하게 동작하도록 구성

7단계

Multiple Data Centers

지역 확장 및 데이터 센터 장애 대응

8단계

Message Queue

서비스 간 결합을 낮추고 비동기 작업 처리

9단계

Logging
Metrics
Automation

커진 시스템을 안정적으로 운영

10단계

Database Sharding

데이터베이스 자체도 여러 서버로 분산


공부하면서 정리한 핵심

처음에는 로드밸런서, 캐시, CDN, DB 복제, 샤딩 등이 각각 독립적인 기술처럼 보였다.

하지만 전체적인 흐름을 따라가 보면 모두 같은 목적을 가지고 있었다.

특정 서버나 컴포넌트 하나에 요청·데이터·책임이 집중되는 문제를 줄이는 것

사용자가 증가하면 웹 서버를 여러 대로 나누고,

DB 부하가 커지면 읽기 서버를 나누고,

반복 조회가 많아지면 캐시를 사용하고,

정적 파일 트래픽이 커지면 CDN으로 분리하고,

데이터 자체가 너무 많아지면 샤딩을 적용한다.

결국 대규모 시스템의 확장은 단순히 “서버의 성능을 높이는 것”이 아니라,

시스템을 여러 구성 요소로 분리하고 각 구성 요소가 독립적으로 확장될 수 있도록 만드는 과정이라고 이해할 수 있었다.


전반적인 후기

1강은 확실히 개념적인 요소들이 강한 것 같다.
뭔가 기존에 알고 있던 개념들도 많았지만, 새롭게 알게된 개념과 내가 오인하고 있던 개념들도 정말 많았던 것 같다.

뭔가 전반적인 시스템의 흐름의 틀을 잡을 수 있었던 내용이라 도움이 많이 된 것 같다.

바로 완성된 모습을 보여주기보단, 먼저 기존 아키텍처부터 보여주고, 어떻게 이 아키텍처를 개선해나가는 지를 순차적으로 잘 보여주는 느낌이라 같이 리팩토링하는 느낌을 받을 수 있었다.

1강은 좀 길었던 것 같다. 확실히 설명 해야할 기본 개념들이 많다보니 정리할 내용도 많아져서 나도 뭔가 두서없이 책의 내용만 정리한 것 같다. 다음엔 좀 더 내가 이해한 방식으로 스스로 설명할 수 있도록 해보겠다.

profile
현실에서 개발하는 도피입니다..

2개의 댓글

comment-user-thumbnail
2026년 9월 8일

잘 봤습니다~ 정리를 이해하기 쉽게 잘 하시는 것 같아요!

1개의 답글