[Backend] Large-Scale Traffic Engineering — Intro

7 분 소요

시작하면서

배경

실제로 다뤄온 프로젝트 중 일부는 사용자가 늘어나고 시스템이 커질수록 보완해야 할 부분이 하나씩 드러났습니다. SI 위주로 내부 시스템을 주로 다뤄오다 보니 초기·중반 규모까지의 경험은 쌓였지만 대규모까지 갖춰진 이후의 경험은 상대적으로 적었습니다. 실제 주변에서 자주 쓰는 서비스들 예매나 주문·거래 같은 서비스들이 지금 이 순간에도 얼마나 많은 트래픽을 어떻게 감당하고 있는지에 의문과 궁금증이 생겼습니다. 제가 다뤄온 환경과 실제 주변에서 자주 사용하는 서비스와의 차이를 파악하면서 서비스의 규모와 성격에 따라 어떤 아키텍처와 스택을 선택했는지 하나씩 비교하며 확인하기 시작했습니다.

대응

스레드가 응답을 기다리는 동안 그대로 점유된 채 낭비되는 문제를 마주쳤습니다. 인프라를 늘릴 수 없는 상황이라 우선은 같은 서버 안에서 배치 작업처럼 운영 WAS에 부담을 주는 일과 JDK8에서는 쓸 수 없는 라이브러리가 필요한 일을 별도 WAS로 분리했습니다. 동기 방식으로 처리하는 이상 스레드 점유 문제 자체가 풀린 건 아니었지만 별도 프로세스라 메모리 영향과 장애·배포 영향은 서로 번지지 않게 됐습니다. 그 과정에서 장애 방어 장치와 캐시, 지표 모니터링까지 함께 다뤄봤습니다.

한계

이 중에서도 스레드 문제만큼은 CompletableFuture로 비동기 처리를 시도해도 그 작업을 실행하는 스레드 자체는 여전히 필요했습니다. 대기 비중이 높은 작업들이라 코어 수가 천장은 아니었지만 그 대기 시간을 결국 스레드 수로 버텨야 하는 구조 자체가 한계였습니다. MVC 같은 동기 모델에서 Virtual Thread까지는 스택 조합에 맞게 자원 낭비를 줄이는 방향으로 시도해봤습니다. 앞으로는 트래픽 규모나 시스템 성격에 따라 MVC·JDBC 조합이 한계에 부딪히는 지점을 살펴보고 그 조건에 맞을 때 웹 계층은 Netty 기반 WebFlux로 DB 계층은 R2DBC로 바꾸는 것도 고려해보고 싶습니다.

회고

서버 한 대에 장애가 생기면 그 위에 올라간 기능이 전부 함께 내려가는 위험은 그대로 안고 있었습니다. 그래도 이렇게 역할별로 프로세스를 나눠본 경험은 지나고 보니 MSA와 방향이 겹치는 부분이 있었다는 생각이 듭니다. 코어나 메모리까지 따로 확보하지는 못했고 닮은 건 스택을 독립적으로 가져갈 수 있었다는 정도까지였습니다. 다만 서비스를 실제로 나누는 구조로 나아가면 데이터베이스 역시 한 대로는 감당이 안 되는 시점이 오고 그때는 Read Replica나 Sharding처럼 데이터 자체를 나누는 것도 별도로 필요해진다는 것까지는 알고 있습니다. 여러 서버로 나뉘었을 때 생기는 동시성·정합성 문제나 장애 전파는 아직 직접 겪어본 적이 없어 기존 구조로는 근본적으로 풀리지 않던 문제를 진단하고 해결하는 트러블슈팅 역량을 채우고자 합니다.

목표

이 시리즈는 무조건 새로운 걸 배우자는 게 아니라 직접 부딪혀서 느낀 이 공백을 정확히 채우기 위한 학습입니다. 목표는 트래픽이 몰리는 상황에서도 안정적인 아키텍처를 설계하고 SLO를 정의하고 최종적으로 달성하는 것입니다. 정합성과 동시성과 가용성이 부딪히는 여러 상황과 장애 상황에도 흔들림 없이 대응할 수 있는 역량을 갖추는 것도 목표입니다. 더 크게 보면 서비스가 초기·중반 규모에 머물 때뿐 아니라 확장되고 커진 이후까지 대응하며 운영할 수 있는 역량을 갖추고자 합니다.

이어지는 포스팅들은 아래 로드맵 순서대로, 하나의 문제 영역씩 원리 → 구현 → 트레이드오프 → 선택 기준 순으로 다룹니다.


1. 학습 순서

앞서 이야기한 자원 제약 속 운영 경험이 학습 순서에도 그대로 이어집니다. 전체를 8개 PART로 묶고, 그 안에서 성격이 가까운 것들을 서브파트로 나눴습니다.

먼저 서버 한 대 안에서 생기는 문제를 두 갈래로 나눠 봅니다. 데이터를 지키는 계층인 인덱스·트랜잭션·락과, 그 데이터를 처리하는 자원 계층인 스레드·커넥션·GC입니다. 데이터 계층 안에서는 인덱스 → 트랜잭션·MVCC → 락 순서로 갑니다. 락이 실제로 잠그는 대상이 인덱스 레코드이고, 락과 MVCC가 서로 다른 계층이라는 것부터 정리해야 락 전략을 정확히 설명할 수 있어서 의존 관계를 따라 배치했습니다.

그다음 여러 서버로 흩어졌을 때 생기는 분산 이론과 정합성 문제로 넘어가고, 그 위에 캐싱·메시징 인프라를 얹습니다. 이후 서버를 역할별로 나누는 MSA를 요청이 흐르는 경로와 그 위의 인터페이스 설계로 나눠 다루고, 데이터 계층 자체를 여러 대로 확장하는 문제를 봅니다.

마지막은 운영입니다. 부하를 측정하고 시스템을 관측하고 SLO로 목표를 정의하는 것까지가 이 시리즈의 착지점입니다.

2. 학습·작성 방식

  • 원리 중심: DB 내부 구조, JVM 동작, 네트워크 레벨의 로우레벨 메커니즘까지 설명합니다. “무엇이다”에서 멈추지 않고 “왜 그렇게 동작하는가”까지 다룹니다.
  • 트레이드오프 명시: 모든 전략에는 장점만큼 단점과 한계도 함께 씁니다.
  • 공식 문서 기반 검증: MySQL, Redis, Kafka, Spring, Elasticsearch 등 공식 문서와 대조해 확인한 내용만 씁니다. 확인되지 않은 내용은 단정하지 않고 조건을 명시합니다. 일반적인 원칙을 확인했더라도, 그 원칙이 실제로 다루는 문장에 정확히 적용되는지까지 좁혀서 재확인합니다.
  • 경험 범위와 인지 범위 구분: DB는 직접 다뤄온 MySQL InnoDB를 기준으로 깊게 쓰고 PostgreSQL처럼 구현이 다른 스택은 차이가 나는 지점만 비교로 짚습니다. 직접 겪은 것과 이해하고 있는 것을 섞지 않고 씁니다.
  • 검증 환경 명시: 락, GC, 복제처럼 버전에 따라 동작과 기본값이 달라지는 주제는 검증에 사용한 스택과 버전을 글머리에 밝힙니다. 같은 설정 이름이 버전에 따라 다른 기본값을 갖는 경우가 많기 때문입니다.
  • 중복보다 참조: 같은 PART 안의 글끼리는 앞 글을 전제로 씁니다. 다른 PART의 내용이 필요할 때는 이해에 필요한 최소한만 자체적으로 설명하고 해당 PART로 넘깁니다. 같은 이론을 여러 글에서 반복해 설명하지 않기 위한 원칙입니다.

3. 전체 로드맵

PART 1. 단일 서버, 데이터 정합성

DB 엔진 한 대 안에서 정합성이 깨지는 경로를 다룹니다.

No 주제 다루는 내용
1-1 Index & 실행 계획 B+Tree, 클러스터드·보조 인덱스, 복합 인덱스 컬럼 순서, 카디널리티·선택도, 커버링 인덱스, EXPLAIN 해석
1-2 Transaction & MVCC ACID, 격리 수준과 이상 현상, Undo Log, ReadView 구조와 가시성 판정, 버전 체인, Purge·History list, PostgreSQL 비교
1-3 Lock 메커니즘 S·X 락, Record·Gap·Next-Key·Insert Intention, 락 호환성, MDL, AUTO-INC 락, Deadlock 판정, NOWAIT·SKIP LOCKED, 락 진단
1-4 동시성 제어 전략 비관적·낙관적 락, 원자적 UPDATE, 핫로우 경합, 트랜잭션 경계와 프록시, 재시도 설계, 결제 방어 패턴
1-5 JPA N+1 & 영속성 컨텍스트 1차 캐시, 쓰기 지연, 지연 로딩과 프록시, fetch join·EntityGraph·BatchSize, 벌크 연산과 캐시 정합

PART 2. 단일 서버, 실행 자원

그 데이터를 처리하는 스레드·커넥션·메모리의 문제를 다룹니다.

No 주제 다루는 내용
2-1 동시성 모델 MVC(Thread-per-Request), 블로킹 I/O와 스레드 점유, Virtual Thread, WebFlux, R2DBC
2-2 스레드 풀 · 커넥션 풀 · 타임아웃 계층 WAS 스레드 풀, HikariCP 풀 사이징, 락 대기가 커넥션 고갈로 번지는 경로, 타임아웃 계층 정렬
2-3 JVM GC 튜닝 G1GC·ZGC, Stop-the-World와 응답 지연 분포, Allocation Rate, GC 로그 분석, 동시성 모델과의 상호작용

PART 3. 여러 서버, 분산 이론과 정합성

서버가 흩어질 때 정합성이 어려워지는 근본 이유를 다룹니다.

No 주제 다루는 내용
3-1 CAP & PACELC CP·AP 선택, PACELC의 지연 축, Quorum, 일관성 모델 스펙트럼
3-2 분산 트랜잭션 2PC와 블로킹 문제, Saga, 보상 트랜잭션의 유실 가능성, 상태 머신 영속화, Outbox
3-3 멱등성 설계 멱등키 설계, at-least-once와 중복, 타임아웃을 상태 불명으로 다루기, 유니크 제약을 최종 방어선으로
3-4 분산 락의 한계 Redlock의 과반수와 유효시간 검증, Watchdog과 lease, 프로세스 정지로 인한 상호배제 위반, Fencing Token

PART 4. 캐싱 · 메시징 인프라

서버 간 상태를 공유하고 비동기로 주고받는 문제를 다룹니다.

No 주제 다루는 내용
4-1 Redis 자료구조 · 운영 자료구조 선택 기준, 단일 스레드 모델과 O(N) 명령의 위험, RDB·AOF, Sentinel, Cluster, eviction
4-2 캐싱 전략 Cache-Aside·Write-Through·Write-Behind, 캐시 3대 문제, TTL 지터, DB-캐시 정합성, Hot Key
4-3 Kafka 아키텍처 Topic·Partition·Offset, 순서 보장의 범위, Replication과 ISR, Consumer Group과 리밸런싱
4-4 Kafka 신뢰성 · 실무 패턴 acks와 min.insync.replicas, 전달 보장, 멱등 Producer, 트랜잭션, DLQ·재처리, 컨슈머 랙

PART 5. MSA, 요청 경로

요청이 클라이언트에서 서비스까지 도달하는 흐름을 다룹니다.

No 주제 다루는 내용
5-1 Load Balancing & API Gateway L4·L7 로드밸런싱, 헬스체크, Spring Cloud Gateway 필터 체인, Gateway의 책임 경계
5-2 Service Discovery & 선언적 HTTP 클라이언트 Eureka 하트비트와 자기보존, 레지스트리 전파 지연, Feign과 Spring Interface Clients 비교
5-3 Circuit Breaker & Rate Limiting Resilience4j 상태 전이, Timeout·Retry와의 조합 순서, Token·Leaky Bucket, Sliding Window
5-4 분산 트레이싱 Trace ID·Span ID, 컨텍스트 전파, 샘플링 전략, MDC 기반 로그 상관관계

PART 6. MSA, 인터페이스 설계

안전하게 통신할 수 있게 된 다음의 설계 선택을 다룹니다.

No 주제 다루는 내용
6-1 API 설계 스타일 REST의 HTTP 시맨틱, gRPC와 Protobuf, GraphQL, HTTP 메서드 멱등성 규약, 버저닝
6-2 인증 · 인가 심화 세션과 토큰의 차이, JWT 구조와 검증, Access·Refresh 전략, OAuth2, MSA에서 인증 위치
6-3 실시간 통신 폴링·롱폴링·SSE·WebSocket 비교, STOMP, 스케일아웃 시 세션 공유, 연결 수와 자원

PART 7. 데이터 계층 확장

서비스가 커지며 데이터 자체를 나누는 문제를 다룹니다.

No 주제 다루는 내용
7-1 Replication & 읽기 · 쓰기 분리 복제 방식과 binlog 포맷, 복제 지연의 원인과 측정, read-your-writes 문제, 읽기 라우팅
7-2 Sharding & 분산 ID 수직·수평 분할, 샤드 키 선정과 핫스팟, 크로스 샤드 조회, 리샤딩, 분산 ID와 인덱스 지역성
7-3 Consistent Hashing 모듈로 해싱의 재분배 문제, 해시 링, 가상 노드와 부하 균등, 노드 변경 시 이동량
7-4 NoSQL 데이터 모델링 쿼리 주도 설계, Cassandra의 파티션·클러스터링 키, DynamoDB의 키와 인덱스, 비정규화
7-5 검색 인프라 역인덱스, 분석기와 토크나이저, 매핑 설계, 샤드·레플리카 구성, RDB 동기화와 색인 지연

PART 8. 운영 · SLO

측정하고 관측하고 목표를 정의하고 안전하게 바꾸는 흐름을 다룹니다.

No 주제 다루는 내용
8-1 CDN & 정적 자원 Edge 캐싱과 Origin 관계, Cache-Control·ETag, 캐시 무효화, Object Storage
8-2 부하테스트 목표 지표 정의, Load·Stress·Spike·Soak 구분, k6 시나리오, 병목 식별 순서, 용량 산정
8-3 관측성 메트릭·로그·트레이스 세 축, Prometheus와 PromQL, Grafana, Micrometer, 증상 기반 알림
8-4 SLO · 에러 예산 SLI 선정 기준, SLO 목표치의 근거, 에러 예산과 소비율, 번레이트 알림, SLA와의 관계
8-5 배포 전략 Rolling·Blue/Green·Canary, 무중단 배포와 그레이스풀 셧다운, 스키마 변경과 배포 순서, 롤백
8-6 Auto Scaling 스케일업과 스케일아웃, 지표 기반 스케일링, Scaling Policy와 쿨다운, 스테이트풀 자원의 한계

4. 읽는 방법

순서대로 읽으면 데이터 정합성 → 실행 자원 → 분산 이론 → 캐싱·메시징 → MSA 요청 경로 → 인터페이스 설계 → 데이터 확장 → 운영·SLO 순으로 이어집니다.

각 PART는 독립적으로 읽을 수 있게 썼습니다. 관심 있는 PART부터 먼저 봐도 무방합니다. 다만 PART 1의 다섯 편은 의존 관계를 따라 배치했기 때문에 순서대로 읽는 것이 유리합니다. 인덱스 구조를 먼저 봐야 락이 무엇을 잠그는지가 정확히 보이고 MVCC를 먼저 봐야 락과 MVCC가 왜 다른 계층인지가 드러납니다.

같은 PART 안의 글은 앞 글을 전제로 쓰여 있고 다른 PART의 내용이 필요한 부분에는 해당 PART로 가는 링크를 두었습니다.


✅ Conclusion

이 시리즈는 정답을 알고 있어서 쓰는 글이 아니라 모놀리식에서는 보이지 않았던 문제들을 하나씩 확인하며 정리하는 기록입니다. 트래픽이 커질 때 드러나는 문제들은 대부분 새로운 지식이 아니라 이미 알려진 원리를 얼마나 정확히 이해하고 있느냐의 문제라고 생각하고 그 원리들을 공식 문서 기준으로 검증하면서 하나씩 포스팅으로 남기겠습니다. 마지막 PART에서 SLO를 정의하고 그 목표에 도달했는지 스스로 검증하는 것으로 이 시리즈를 마무리하겠습니다.