목차21개 항목
출처와 참고
Olga Mitroshyna의 「Software Architecture & System Design: I wish I had known about this earlier…」(2022.05)를 바탕으로 재구성한 노트다. 운영 요소(CI/CD, 모니터링, 알림)는 ByteByteGo의 System Design 101 가이드 「10 Essential Components of a Production Web Application」로, 확장의 병목·전략과 단계별 성장 과정은 같은 가이드의 「8 Must-Know Scalability Strategies」, 「A Crash Course on Architectural Scalability」, 「Scaling Websites for Millions of Users」로 보강했다. 원 자료는 Artur Ejsmont의 책 『Web Scalability for Startup Engineers』(2015)다. 복제 서버의 황금률, 캐시 두 패턴, 비동기 두 방식은 「Scalability for Dummies」 1~4부(HiredInTech의 「Scalability Fundamentals」가 추린 읽을거리)로, 계층별 로드 밸런싱·캐시 무효화·오프라인 처리는 「Introduction to Architecting Systems for Scale」로, 읽기·쓰기 서비스 분리와 전역·분산 캐시, 다층 인덱스는 AOSA 2권의 「Scalable Web Architecture and Distributed Systems」로 보강했다. karanpratapsingh의 「System Design」 강의 저장소를 바탕으로 수직 확장과 수평 확장의 장단점 비교를 보강했다.
확장성이란
확장성(scalability)은 사용자·데이터·요청이 늘어도 구성 요소를 더하는 것만으로 서비스를 유지할 수 있는 성질이다. 서버 한 대를 더 좋은 것으로 바꾸는 수직 확장(scale up)은 쉽지만 금방 한계에 닿는다. 그래서 웹 아키텍처는 같은 일을 하는 서버를 늘리거나(수평 확장, scale out), 일을 나눠 맡기는 방향으로 짜인다.
| 비교 | 수직 확장(scale up) | 수평 확장(scale out) |
|---|---|---|
| 방법 | 기존 서버에 CPU·메모리·디스크를 더한다 | 같은 일을 하는 서버를 풀에 더한다 |
| 장점 | 구현·관리가 단순하다. 데이터가 한 곳이라 일관성이 쉽다 | 이중화로 결함 허용이 좋아지고, 필요한 만큼 유연하게 늘리며, 한 대씩 돌아가며 업그레이드한다 |
| 단점 | 업그레이드 때 중단이 길고, 사양에 상한이 있으며, 그 서버가 단일 장애점이다 | 구조가 복잡해지고, 복제본 사이 데이터가 어긋날 수 있으며, 늘어난 서버가 하류 서비스(DB 등)에 부하를 더 보낸다 |
확장성을 이해하려면 웹 애플리케이션을 이루는 부품 전체와 그 사이를 흐르는 데이터를 한 장으로 그릴 수 있어야 한다. 데이터가 어디서 들어와 어디를 거쳐 어디에 저장되는지(data flow)를 그리면 설계 논의를 빨리 따라가고, 누가 어느 부품을 책임지는지도 분명해진다.
한 장의 지도
flowchart TB user["사용자"] -->|"① DNS: 도메인 → IP"| cdn["⑩ CDN<br/>정적 파일(이미지·CSS·JS·영상)"] cdn --> lb["② 로드 밸런서 / 리버스 프록시"] lb --> fe["④ 프론트 웹 서버"] fe --- c3["③ 캐시"] fe --> ws["⑦ 웹 서비스(백엔드)"] ws --- c5["⑤ 캐시"] ws --- mq["⑥ 메시지 큐"] mq --- wk["⑪ 큐 워커"] ws --> ds["⑧ 데이터 저장소"] se["⑨ 검색엔진"]
| 번호 | 구성 요소 | 하는 일 |
|---|---|---|
| ① | DNS | 도메인 이름을 요청을 처리할 서버의 IP로 바꾼다. DNS |
| ② | 로드 밸런서 | 트래픽을 여러 서버에 나눈다 |
| ③⑤ | 캐시 | 미리 만든 결과를 돌려줘 응답을 빠르게 하고 뒷단 부하를 줄인다 |
| ④ | 프론트 웹 서버 | 화면(표현 계층)을 만들고, 여러 백엔드 결과를 모은다 |
| ⑥ | 메시지 큐 | 나중에 처리할 작업 요청을 쌓아 둔다 |
| ⑦ | 웹 서비스 | 비즈니스 로직이 사는 곳 |
| ⑧ | 데이터 저장소 | 웹 서비스가 데이터를 쓰고 읽는 곳 |
| ⑨ | 검색엔진 | 데이터 저장소가 잘 못하는 복잡한 검색을 맡는다 |
| ⑩ | CDN | 정적 파일을 사용자 가까이에서 내준다. CDN |
| ⑪ | 큐 워커 | 큐의 메시지를 꺼내 처리하는 별도 서버 |
지도에 그려지지 않지만 운영 중인 웹 애플리케이션에 빠지지 않는 요소가 셋 더 있다.
| 구성 요소 | 하는 일 |
|---|---|
| CI/CD 파이프라인 | 코드를 빌드·테스트해 서버 인스턴스에 배포한다. 지도의 모든 서버는 이 파이프라인으로 바뀐다 |
| 모니터링 | 로그와 지표를 모아 시스템이 정상인지 분석한다. 에러 추적(Sentry), 지표 수집과 대시보드(Prometheus, Grafana) |
| 알림 | 문제가 생기면 메신저·온콜 도구로 개발자에게 바로 알린다 |
로드 밸런서와 리버스 프록시의 역할 차이는 로드 밸런서와 API 게이트웨이에 정리했다.
3-Tier로 줄여 보기
지도를 세 티어로 줄이면 흔히 말하는 3-Tier 아키텍처다. 모든 기능이 애플리케이션 하나에 들어 있으면 모놀리식(monolithic) 이라 한다.
| 티어 | 역할 | 지도의 부품 |
|---|---|---|
| Presentation | 사용자와 만나는 화면 | ④ 프론트 웹 서버, ⑩ CDN |
| Application | 비즈니스 로직 | ⑦ 웹 서비스, ⑥⑪ 큐와 워커 |
| Data | 데이터의 저장·사용 | ⑧ 데이터 저장소, ⑨ 검색엔진 |
FE/BE 구분은 Presentation과 「나머지 전부」로 나눈 모델이다. 서버별 역할과 요청 하나의 흐름은 웹 앱의 요청·응답 구조에 있다.
확장의 세 축
| 축 | 방법 | 비용과 한계 |
|---|---|---|
| 복제(clone) | 같은 서버를 여러 대 두고 로드 밸런서로 나눈다 | 가장 쉽고 싸다. 서버가 상태(세션 등)를 갖지 않아야 한다 |
| 기능 분할(functional partitioning) | 기능별로 서비스·서버를 나눈다. 서비스 지향 아키텍처(SOA)·MSA가 여기 속한다 | 서비스마다 따로 확장할 수 있지만 통합이 어렵다. 나눌 수 있는 기능 수에 한계가 있다 |
| 데이터 분할(data partitioning) | 데이터 집합을 키 기준으로 쪼개 여러 서버에 둔다 | 효과가 크지만 복잡도가 가장 높다 |
복제부터 시도하고, 기능 분할과 데이터 분할은 필요해질 때 한다. 기능 분할은 엔지니어가 10~20명을 넘어 팀이 나뉠 때쯤 의미가 생긴다.
복제의 황금률은 「모든 서버가 같은 코드를 갖고, 사용자 데이터(세션, 업로드 파일)를 로컬 디스크나 메모리에 두지 않는다」 이다. 같은 사용자의 요청이 2번 서버, 9번 서버, 다시 2번 서버로 가도 결과가 같아야 한다. 세션은 모든 앱 서버가 닿는 외부 저장소(Redis 같은 영속 캐시가 DB보다 빠르다)에 둔다. 이렇게 상태를 빼낸 서버 하나로 머신 이미지(AWS AMI 등)를 만들어 두면, 새 인스턴스는 그 이미지에서 띄우고 최신 코드만 배포하면 된다. 배포 중 일부 서버만 옛 코드를 내보내는 일은 배포 도구로 막는다.
로드 밸런싱은 사용자 → 웹 서버 한 곳에서만 하지 않는다. 웹 서버 → 내부 플랫폼 계층, 플랫폼 계층 → DB까지 계층마다 해야 계층 전체가 수평 확장과 이중화를 얻는다.
| 구현 | 방식 | 장단점 |
|---|---|---|
| 스마트 클라이언트 | DB·캐시·서비스 클라이언트 라이브러리가 호스트 풀을 들고 부하를 나누고 죽은 호스트를 피한다 | 개발자에게 친숙해 보이지만 호스트 복구·추가 감지까지 직접 만들어야 해 견고하게 만들기 어렵다 |
| 하드웨어 로드 밸런서 | 전용 장비(Citrix NetScaler, F5 BIG-IP) | 성능은 좋지만 비싸고 설정이 어렵다. 보통 외부 요청의 첫 관문에만 쓴다 |
| 소프트웨어 로드 밸런서 | HAProxy·Envoy를 각 서버에 띄우고 서비스마다 로컬 포트를 준다(localhost:9000 플랫폼, 9001 DB 읽기 풀, 9002 DB 쓰기 풀) | 헬스 체크와 풀 관리를 맡긴다. 대부분은 여기서 시작하고, 필요가 분명할 때만 다른 방식으로 간다. 오늘날 서비스 메시의 사이드카가 같은 모양이다 |
확장을 막는 병목
확장성은 「부하가 늘 때 비용 대비 효과가 맞는 전략을 반복해서 적용할 수 있는가」로도 볼 수 있다. 같은 전략을 반복할 수 없거나 그 비용이 감당할 수 없게 되는 지점이 확장의 한계다. 그 한계를 만드는 병목은 셋이다.
| 병목 | 왜 막히나 | 푸는 원칙 |
|---|---|---|
| 중앙 집중 구성 요소 | 하나뿐인 DB·락·코디네이터가 처리량 상한이자 단일 장애점이 된다 | 무상태로 만들고 복제·분할한다 |
| 지연이 큰 구성 요소 | 느린 외부 호출·무거운 계산이 요청 경로를 붙잡는다 | 비동기 처리, 캐시 |
| 강한 결합 | 한 구성 요소만 늘릴 수 없고 함께 늘려야 한다 | 느슨한 결합, 메시지 큐 |
그래서 확장 전략은 대부분 무상태(statelessness), 느슨한 결합(loose coupling), 비동기 처리(asynchronous processing) 세 원칙의 구현이다.
| 전략 | 하는 일 |
|---|---|
| 무상태 서비스 | 서버가 특정 요청의 데이터를 들고 있지 않아 아무 서버나 요청을 받는다 |
| 수평 확장 | 서버를 더해 일을 나눈다 |
| 로드 밸런싱 | 요청을 여러 서버에 고르게 나눈다 |
| 오토스케일링 | 실시간 트래픽에 맞춰 서버 수를 자동으로 늘리고 줄인다 |
| 캐시 | 반복되는 요청을 메모리에서 답해 DB 부하를 줄인다 |
| DB 복제 | 읽기를 여러 노드로 나누고 중복성을 높인다 |
| 샤딩 | 데이터를 나눠 쓰기와 읽기를 모두 늘린다 |
| 비동기 처리 | 오래 걸리는 작업을 백그라운드 워커로 넘겨 요청 경로를 비운다 |
단계별로 커지는 과정
서버 한 대에서 시작한 서비스는 대개 이 순서로 커진다. 단계마다 그때의 병목 하나를 푼다.
flowchart TB S1["1. 서버 한 대 (앱 + DB)"] --> S2["2. 앱 서버와 DB 서버 분리<br/>← 자원 경쟁 해소"] S2 --> S3["3. 앱 서버 여러 대<br/>← 앱 처리량 한계"] S3 --> S4["4. 앞에 로드 밸런서<br/>← 서버 간 부하 분배"] S4 --> S5["5. 읽기 복제본으로 읽기·쓰기 분리<br/>← DB 읽기 병목"] S5 --> S6["6. 캐시 계층 추가, DB 분할<br/>← DB 한 대의 한계 (수직 확장은 상한이 있다)"] S6 --> S7["7. 기능별 서비스로 분리(SOA·MSA)<br/>← 팀과 기능의 결합"]
DB 쪽에서 무엇을 먼저 쓸지(인덱스·커넥션 풀·복제본·파티셔닝·샤딩의 순서)는 데이터베이스 확장과 성능 최적화에 있다.
기능 분할의 끝인 MSA(Micro Service Architecture)에서는 데이터 흐름에 새 요소가 생긴다.
| 요소 | 역할 |
|---|---|
| 서비스별 DB | 각 서비스가 자기 데이터를 소유한다 |
| BFF | 화면(웹·앱)별로 필요한 데이터를 여러 서비스에서 모아 준다 |
| 메시지 큐·브로커(Kafka, RabbitMQ) | 서비스 사이 데이터를 비동기로 전달해 결합도를 낮춘다. 아래 「비동기 처리」 |
MSA는 배포·관측·데이터 정합성의 운영 복잡도를 크게 늘린다. 팀과 트래픽 규모가 독립 배포를 요구하기 전에는 모듈을 잘 나눈 모놀리식(모듈러 모놀리스)이 대개 더 싸다. 서비스마다 컨테이너로 포장해 따로 배포하는 방식은 VM과 컨테이너에 있다.
프론트엔드 계층
웹 서비스 앞단은 DNS, CDN, 로드 밸런서, 프론트 웹 서버로 이뤄진다.
| 로드 밸런서 종류 | 예 |
|---|---|
| 관리형 서비스 | AWS Elastic Load Balancing(ALB·NLB) |
| 소프트웨어 | Nginx, HAProxy |
| 하드웨어 | 전용 장비 |
프론트 웹 서버를 복제하려면 HTTP 세션을 서버 메모리에 두지 않는다. 세션은 쿠키에 담거나, 외부 저장소(Redis 등)에 두거나, 같은 세션 쿠키를 가진 요청을 늘 같은 서버로 보내는 고정 세션(sticky session)으로 처리한다. 고정 세션은 서버 하나가 죽으면 그 사용자들의 세션이 사라지므로 마지막 수단이다. 세션과 토큰 방식의 차이는 세션 기반 인증과 토큰 기반 인증, 웹 서버와 리버스 프록시의 역할은 웹 서버, WAS, 그리고 Nginx에 있다.
웹 서비스 계층
만드는 방식
| 방식 | 내용 |
|---|---|
| 모놀리스 먼저 | 하나의 앱으로 시작해 필요할 때 서비스를 떼어 낸다 |
| API 우선(API-first) | 모바일 앱·웹 등 모든 클라이언트가 같은 API를 쓴다 |
| 혼합 | 두 방식을 섞는다 |
웹 서비스는 원격 함수를 호출하는 함수 중심(SOAP, 오늘날에는 gRPC가 대표적이다)과 자원을 객체로 보고 CRUD를 HTTP 메서드에 매핑하는 자원 중심(REST + JSON)으로 나뉜다. REST 설계는 REST API 설계 원칙에서 다룬다.
REST 서비스를 확장하는 법
- 복제: 서비스가 상태를 갖지 않으면 서버를 늘리기만 하면 된다.
- 기능 분할:
UserProfileService,ProductCatalogService처럼 기능별 서비스로 나눈다. 서비스 사이 의존이 조금 있어도 괜찮다. - HTTP 캐싱:
GET응답을 캐시해 서비스까지 가지 않고 돌려준다. REST가 확장에 유리한 큰 이유다.
처음부터 서비스를 나눌 때 흔한 첫 분할은 읽기와 쓰기의 분리다. 이미지 호스팅처럼 업로드는 느리고 드물지만 조회는 빠르고 잦은 서비스에서, 업로드가 웹 서버의 연결 슬롯(예: 최대 500개)을 오래 붙잡으면 조회까지 막힌다. 조회는 압축·청크 전송 덕에 연결 수보다 훨씬 많은 초당 요청을 처리할 수 있으므로, 두 기능을 따로 띄우면 각자의 병목에 맞게 늘릴 수 있다.
데이터 계층
데이터 저장소는 가장 확장하기 어려운 부분이다.
| 방법 | 핵심 | 한계 |
|---|---|---|
| 수직 확장 | 더 큰 서버, 더 많은 RAM·디스크 | 비용이 가파르게 오르고 상한이 있다 |
| 복제(replication) | 같은 데이터를 여러 서버에 둔다. 쓰기는 원본(primary), 읽기는 복제본(replica)으로 나눈다 | 읽기만 늘어난다. 커지는 데이터 자체는 못 푼다. 복제 지연 때문에 복제본이 옛 값을 줄 수 있다 |
| 샤딩 | 샤드 키로 데이터를 나눠 여러 서버에 둔다 | 샤드를 가로지르는 쿼리가 어렵고 운영 복잡도가 크다. 데이터베이스 샤딩 |
| NoSQL | 처음부터 분산을 전제로 설계한 저장소(Cassandra, MongoDB, Redis 등) | 분할 상황에서 일관성과 가용성 중 하나를 고른다. CAP 정리 |
복제는 읽기가 많은 서비스에 잘 맞는 첫 수다. 샤딩은 데이터가 한 서버에 담기지 않을 때 쓴다.
NoSQL을 「CP냐 AP냐」로 딱 잘라 분류하는 것은 거친 근사다. MongoDB는 기본적으로 primary에서 읽고 써 일관성 쪽에 가깝고, Cassandra는 가용성 쪽에 가깝지만, 둘 다 읽기·쓰기 정족수 설정으로 성격을 조절한다. 클라우드에서는 직접 샤딩하는 대신 Azure SQL Hyperscale, Amazon Aurora, 분산 SQL(NewSQL) 같은 관리형 선택지를 먼저 검토한다.
DB가 버거워지면 길은 둘이다. 관계형 DB를 유지하며 주·복제 구성, 더 큰 서버, 결국 샤딩·비정규화·SQL 튜닝으로 가는 길은 갈수록 한 걸음의 비용이 커진다. 다른 길은 일찍부터 조인을 없애고 비정규화해 DB를 키-값 저장소처럼 쓰고 조인은 애플리케이션에서 하는 것이다. 데이터가 작을 때 바꿀수록 옮길 코드가 적다. 어느 길이든 결국 캐시가 필요해진다.
흐름은 서비스는 기능별로 나누고, 서비스마다 필요에 맞는 저장소를 고르는 것(polyglot persistence)이다.
캐시
캐시는 다시 계산하지 않고 완성된 결과를 돌려줘 성능과 확장성을 함께 높인다. 지표는 캐시 적중률(hit ratio) 이다. 같은 응답을 여러 번 재사용할수록 효과가 크다. 그래서 읽기가 많은 서비스에 효과적이고, 쓰기가 많은 서비스에는 거의 쓸모가 없을 수 있다. 캐시는 나중에 붙여도 되므로 처음부터 모든 곳에 넣지 않는다.
캐시는 브라우저의 HTTP 캐시, 캐싱 프록시·리버스 프록시, CDN, 애플리케이션이 직접 다루는 객체 캐시(클라이언트 저장소, 서버 로컬 메모리, Redis 같은 분산 캐시)로 층마다 놓인다. 층별 쓰임과 읽기·쓰기 패턴, 교체 정책은 캐시 전략에, HTTP 헤더로 동작을 제어하는 법은 HTTP 캐시에 있다.
분산 캐시를 운영할 때 생기는 장애와 핫 키 문제는 캐시 클러스터 운영과 장애 대응에서, 트래픽 급증 때 CDN·웹 서버로 앞단을 지키는 법은 대규모 트래픽에서 서비스를 지키는 설계에서 다룬다.
캐시에 무엇을 담느냐에는 두 패턴이 있다.
| 패턴 | 키 | 문제 |
|---|---|---|
| 쿼리 결과 캐시 | 쿼리 문자열의 해시 | 표의 한 칸이 바뀌면 그 칸을 포함할 수 있는 모든 쿼리 캐시를 지워야 한다. 복잡한 쿼리일수록 무효화가 사실상 불가능하다 |
| 객체 캐시 | 조립이 끝난 객체(product:42) | 여러 쿼리로 조립한 결과를 통째로 담아, 무언가 바뀌면 그 객체만 지운다. 워커가 미리 객체를 조립해 두는 비동기 처리와도 잘 맞는다 |
객체 캐시가 대개 낫다. 담기 좋은 것: 세션, 렌더링이 끝난 글, 활동 스트림, 친구 관계. 파일 기반 캐시는 서버를 복제·자동 확장할 때 걸림돌이 되므로 메모리 캐시(Redis, Memcached)를 쓴다.
캐시는 애플리케이션 캐시(코드가 캐시를 먼저 보고 없으면 DB에서 읽어 채운다)와 DB 캐시(DB 설정을 접근 패턴에 맞춰 조정하면 코드 변경 없이 빨라진다)로도 나뉘고, 대부분의 시스템은 둘 다 쓴다. 캐시 전략은 일찍 정하는 편이 낫다. 캐시로 흉내 낼 수 없는 접근 패턴을 먼저 최적화해 두면 나중에 캐시를 끼우기 어렵다.
무효화의 기본은 값이 바뀔 때마다 캐시에 새 값을 쓰거나(write-through) 지워서 다음 읽기가 채우게 하는 것이다. 어려운 경우는 두 가지다.
- 모호한 질의: 전문 검색 결과 앞의 캐시처럼 어떤 키가 영향을 받는지 알 수 없다. DB 쪽 캐시에 맡기거나 짧은 TTL을 건다.
- 개수를 모르는 변경:
DELETE FROM a WHERE created < 일주일 전대신 조건에 맞는 행을 먼저 조회해 해당 캐시 키를 지우고, 기본 키로 하나씩 지운다.
여러 노드가 캐시를 함께 쓰는 방식은 둘이다. 전역 캐시는 모든 요청 노드가 하나의 캐시 공간을 쓴다(대개 캐시가 원본 조회까지 맡아 같은 키 요청이 몰려도 원본에는 한 번만 간다). 분산 캐시는 일관된 해싱으로 키를 노드에 나눠, 노드를 더할수록 캐시 공간이 커진다. 같은 키에 대한 동시 요청을 하나로 합쳐 원본에 한 번만 보내는 것을 요청 병합(collapsed forwarding) 이라 하고, 프록시 계층에서 흔히 한다.
인덱스도 같은 원리로 계층을 쌓는다. 데이터가 커지면 「인덱스 → 하위 인덱스 → 데이터」처럼 여러 층의 인덱스가 지도 역할을 하고, 같은 데이터에 다른 관점의 인덱스(역인덱스 등)를 여러 개 둘 수 있다.
비동기 처리
| 방식 | 동작 | 결과 |
|---|---|---|
| 동기 처리 | 요청을 보내고 응답이 올 때까지 기다린다 | 느린 작업이 있으면 사용자도 기다린다 |
| 비동기 처리 | 요청을 맡기고 바로 자기 일을 계속한다(fire-and-forget) | 응답성이 좋고, 느린 작업을 뒤로 미룰 수 있다 |
반응이 빠른 서비스는 오래 걸리는 일(메일 발송, 이미지 변환, 결제 후처리)을 비동기로 뺀다.
비동기 처리에는 두 모양이 있다.
| 방식 | 비유 | 웹에서 |
|---|---|---|
| 미리 해 두기 | 밤에 빵을 구워 아침에 판다 | 동적 페이지를 변경 때마다 정적 HTML로 미리 렌더링해 CDN·오브젝트 스토리지에 올린다. cron 같은 주기 작업으로 집계를 미리 계산한다 |
| 맡기고 알려 주기 | 주문 케이크는 내일 찾으러 오게 한다 | 요청을 작업 큐에 넣고 바로 「처리 중」을 답한다. 워커가 끝내면 프론트엔드가 완료 신호를 받아 알린다 |
사용자에게 어떻게 보일지에 따라 요청 안에서 할 일과 큐로 넘길 일의 경계가 정해진다. VM 생성처럼 거의 아무것도 하지 않고 「곧 처리된다」고 알리는 방식이 있고, 글 게시처럼 사용자 화면에는 즉시 반영하고 팔로워 타임라인 갱신은 나중에 하는 방식이 있다. 큐 워커를 별도 서버 풀로 두면 병목인 곳에만 자원을 더할 수 있다. 주기 작업은 cron 서버가 직접 일을 하지 말고 큐에 메시지만 넣게 하면 cron 서버는 스케줄링만 맡는다.
메시지 큐
flowchart LR p["생산자(producer)"] -->|"메시지"| q["메시지 큐(버퍼)"] --> c["소비자(consumer, 큐 워커)"]
| 구성 | 역할 |
|---|---|
| 생산자 | 클라이언트 코드의 일부. 메시지를 만들어 큐에 넣는다 |
| 큐 | 메시지를 쌓아 두고 소비자에게 넘긴다 |
| 소비자 | 메시지를 받아 처리한다. 주기적으로 꺼내 가는 풀(pull, cron 형) 과 큐가 밀어주는 푸시(push, 데몬 형) 가 있다 |
큐를 두면 생산자와 소비자가 서로의 속도에 묶이지 않는다. 트래픽이 몰려도 큐가 버퍼가 되고, 워커 수만 늘리면 처리량이 오른다. 큐 제품을 고르는 기준과 큐·로그 구조의 차이는 메시지 큐와 Kafka에 있다.
| 플랫폼 | 특징 |
|---|---|
| Amazon SQS | 관리형. 단순하고 실용적이라 초기 스타트업에 알맞다 |
| RabbitMQ | 복잡한 라우팅 등 기능이 많고 유연하다 |
| Apache ActiveMQ (Artemis) | Java 생태계 메시지 브로커. 현재 주력은 차세대인 Artemis다 |
| Apache Kafka | 메시지를 로그로 보관해 여러 소비자가 다시 읽을 수 있다. 대용량 이벤트 스트리밍의 표준이다 |
이벤트 기반 아키텍처
요청·응답 모델은 「이 일을 해 달라」고 부탁한다. 이벤트 기반 아키텍처(event-driven architecture)는 「이 일이 이미 일어났다」고 알린다. 이벤트는 무언가 일어났음을 나타내는 메시지다. 발행자와 구독자는 서로를 모르고, 이벤트 메시지의 형식과 의미만 안다. 그래서 새 기능을 붙일 때 발행자를 고치지 않고 구독자만 추가하면 된다. 이벤트·커맨드의 구분, 페이로드 설계, 발행 시점과 정합성, 전달 모델은 이벤트 기반 아키텍처 설계에 있다.
검색
데이터 저장소는 인덱스가 없으면 전체 테이블을 훑는다(full table scan). 인덱스는 이를 피하는 기본 수단이다. 관계형 모델은 테이블과 관계를 먼저 설계하지만, NoSQL 모델은 쓰임새(use case)와 쿼리를 먼저 정하고 그에 맞춰 데이터를 설계한다.
단어·구절 검색처럼 복잡한 질의는 역인덱스를 쓰는 검색엔진(Elasticsearch, OpenSearch, Solr, 관리형으로 Amazon OpenSearch Service, Azure AI Search 등)에 맡긴다. 원리는 검색엔진의 원리: 역인덱스와 랭킹에 있다.
구간별 보안
보안은 데이터가 지나는 구간마다 따로 설계한다.
| 구간 | 방법 |
|---|---|
| 전송 | TLS로 중간 탈취를 막는다. Let's Encrypt 인증서는 Certbot 같은 ACME 클라이언트로 발급·갱신한다. HTTPS와 TLS |
| 저장 | 비밀번호는 bcrypt·Argon2 같은 단방향 해시로, 복호화가 필요한 민감 데이터는 AES-256 같은 대칭키 암호로 저장한다 |
| 인증 상태 | 서버 세션(즉시 무효화)과 JWT(무상태, 취소 어려움) 중 고른다. 세션 기반 인증과 토큰 기반 인증 |
| API 경계 | 입력 검증, 인가, 레이트 리밋. API 보안 체크리스트, 인증·인가 로직은 어디에 두나 |
아키텍처 밖의 확장
확장성은 아키텍처만의 문제가 아니다.
자동화
테스트, 빌드·배포, 모니터링·알림, 로그 수집을 자동화해야 서버와 서비스가 늘어도 사람이 늘지 않는다.
나와 팀을 확장하기
| 원칙 | 실천 |
|---|---|
| 더 똑똑하게 일한다 | 야근을 피한다. 번아웃은 생산성을 갉아먹는다 |
| 가치로 우선순위를 정한다 | 일의 실제 가치를 따져 고른다. 마감은 협상한다 |
| 작게 만든다 | 단순하고 최소한의 기능을 만든다. 작게 배포하고 피드백을 받는다 |
| 바퀴를 다시 만들지 않는다 | 서드파티 서비스를 쓴다 |
| 나눈다 | 위임하고, 지식을 공유하고, 협업한다 |
| 팀을 작게 | 제품 영역별(예: 결제) 4~9명의 교차 기능 자율 팀을 만든다 |
| 절차를 유연하게 | 지나친 절차와 표준은 창의성을 막는다. 공통 목표와 좋은 엔지니어링 문화로 팀을 정렬한다 |
정리
- 웹 아키텍처는 DNS → CDN·로드 밸런서 → 프론트 웹 서버 → 웹 서비스 → 데이터 저장소의 흐름에 캐시, 메시지 큐, 검색엔진이 붙은 모양이다. 세 티어로 줄이면 3-Tier다.
- MSA는 서비스별 DB·BFF·메시지 큐로 흐름이 복잡해지는 대가로 독립 배포를 얻는다. 그 전에는 모듈러 모놀리스가 싸다.
- 확장의 세 축은 복제, 기능 분할, 데이터 분할이고, 쉬운 복제부터 쓴다. 복제하려면 서버가 상태를 갖지 않아야 한다.
- 데이터 계층은 읽기 복제 → 샤딩·분산 저장소 순으로 어려워진다.
- 캐시는 적중률이 전부이고, 읽기가 많은 곳에 둔다. 느린 일은 메시지 큐로 비동기 처리한다.
- 확장성은 부품을 늘리는 기술이면서, 자동화와 작은 팀으로 사람의 한계를 넘는 기술이다.
더 읽을거리: 「10만명 접속을 허용하는 시스템 만들기」(한 서버에서 관리형 DB·로드 밸런서·CDN·캐시·읽기 복제본으로 단계적으로 키우는 과정), 「Awesome Scalability」(확장성·가용성·안정성·성능 사례 글 모음)
관련 노트: 시스템 설계의 품질 속성과 트레이드오프, 대규모 트래픽에서 서비스를 지키는 설계, 이벤트 기반 아키텍처 설계, 캐시 클러스터 운영과 장애 대응, 데이터베이스 샤딩, 웹 앱의 요청·응답 구조
원본 기록13
- 원문 (새 창) medium.com/@olgamitroshyna/software-architecture-i-wish-i-had-known-about-this-earlier-4df43eae57db
- 원문 (새 창) bytebytego.com/guides/10-essential-components-of-a-production-web-application
- 원문 (새 창) bytebytego.com/guides/8-must-know-scalability-strategies
- 원문 (새 창) bytebytego.com/guides/a-crash-course-on-architectural-scalability
- 원문 (새 창) bytebytego.com/guides/how-to-scale-a-website-to-support-millions-of-users
- 원문 (새 창) www.lecloud.net/post/7295452622/scalability-for-dummies-part-1-clones
- 원문 (새 창) www.lecloud.net/post/7994751381/scalability-for-dummies-part-2-database
- 원문 (새 창) www.lecloud.net/post/9246290032/scalability-for-dummies-part-3-cache
- 원문 (새 창) www.lecloud.net/post/9699762917/scalability-for-dummies-part-4-asynchronism
- 원문 (새 창) www.hiredintech.com/system-design/scalability-fundamentals/
- 원문 (새 창) lethain.com/introduction-to-architecting-systems-for-scale/
- 원문 (새 창) aosabook.org/en/v2/distsys.html
- GitHub 원문 (새 창) github.com/karanpratapsingh/system-design