인프라
인프라이야기
웹 서버는 뭐 하는 놈임?
웹 서버는 사용자가 요청한 페이지나 데이터를 보내주는 서버임.
예를 들어 사용자가 웹사이트에 접속하면 브라우저가 서버한테 말함.
“메인 페이지 보여줘.”
그러면 웹 서버는 HTML, CSS, JavaScript, 이미지 같은 파일을 보내줌.
여기서 HTML은 웹페이지의 뼈대임.
제목은 어디에 있고, 글은 어디에 있고, 버튼은 어디에 있고, 이미지가 어디에 들어가는지 정해주는 문서라고 보면 됨.
근데 HTML만 있으면 디자인이 심심함.
그래서 CSS가 꾸미고, JavaScript가 움직이게 만듦.
정리하면 이거임.
HTML = 웹페이지 구조
CSS = 디자인
JavaScript = 동작
웹 서버 = 이 파일들을 브라우저에 보내주는 컴퓨터
브라우저는 서버가 준 HTML을 읽어서 우리가 보는 화면으로 바꿔줌.
사용자가 주소를 입력하면 브라우저가 먼저 DNS한테 “이 주소 어디 서버임?” 하고 물어봄.
DNS가 IP 주소를 알려주면, 브라우저가 그 IP 주소로 찾아가서 웹 서버에 요청을 보내는 거임.
쉽게 말하면 주소창에 글자를 입력했을 뿐인데, 뒤에서는 브라우저가 알아서 길 찾고 서버한테 말을 거는 거임.
둘 다 맞음.
일상적으로 “웹 서버”라고 하면 보통 웹 요청을 처리하는 컴퓨터를 말하기도 하고, 그 안에서 돌아가는 Nginx, Apache 같은 서버 프로그램을 말하기도 함.
정확히는 웹 서버 프로그램이 설치된 컴퓨터가 웹 요청을 받아서 응답하는 구조임.
서버는 HTML, CSS, JavaScript 같은 재료를 보내줌.
브라우저는 그 재료를 읽어서 실제 화면으로 그려줌.
즉, 서버는 “설계도와 재료”를 주고, 브라우저가 “눈에 보이는 화면”으로 조립한다고 보면 됨.
기본적으로는 그렇다고 보면 됨.
웹사이트 파일을 어딘가에서 보내줘야 하는데, 그 역할을 하는 게 웹 서버임.
다만 정적 파일만 있는 사이트는 CDN이나 정적 호스팅 서비스가 웹 서버 역할을 대신할 수도 있음.
브라우저는 사용자의 컴퓨터에 있는 프로그램이고, 웹사이트 데이터는 보통 외부 서버에 있음.
그래서 브라우저가 서버한테 “이 페이지에 필요한 파일 좀 주세요”라고 요청해야 함.
이 요청과 응답의 반복이 웹의 기본 구조임.
처음에는 서버 한 대로도 됨
서비스 초반에는 사용자가 별로 없으니까 서버 한 대로도 충분할 수 있음.
이 서버 한 대가 웹 요청도 처리하고, 데이터도 저장하고, 로그인도 처리하고, 게시글도 저장하고, 다 함.
근데 사용자가 늘어나면 문제가 생김.
한 서버가 너무 많은 일을 하게 됨.
웹 요청 처리해야 함
모바일 앱 요청 처리해야 함
데이터베이스 읽어야 함
데이터베이스에 써야 함
이미지도 보내야 함
로그인 상태도 기억해야 함
이러면 서버가 점점 힘들어짐.
그래서 보통 역할을 나눔.
웹/모바일 요청을 처리하는 서버는 웹 계층으로 빼고, 데이터를 저장하는 서버는 데이터 계층으로 따로 둠.
이렇게 나누면 좋은 점이 있음.
웹 요청이 많아지면 웹 서버만 늘리면 되고,
데이터가 많아지면 DB 서버를 따로 키우면 됨.
즉, 각자 독립적으로 확장할 수 있음.
꼭 그렇진 않음.
처음부터 서버를 여러 대 두면 구조가 복잡해지고 비용도 늘어남.
사용자가 적을 때는 서버 한 대로 시작하고, 병목이 보일 때 역할을 나누는 게 보통 더 현실적임.
사용자가 적을 때는 괜찮음.
근데 사용자가 늘면 웹 요청 처리와 DB 처리 둘 다 한 서버에 몰림.
그러면 CPU, 메모리, 디스크 I/O가 다 같이 바빠지고, 하나가 느려지면 전체 서비스가 같이 느려질 수 있음.
정해진 숫자는 없음.
서비스 종류, 요청량, DB 사용량, 이미지 처리 여부, 코드 효율에 따라 완전 다름.
중요한 건 “사용자 수”보다 “초당 요청 수, DB 부하, 응답 시간, 서버 자원 사용률”을 보고 판단하는 거임.
처음에는 응답이 느려짐.
페이지 로딩이 늦어지고, 로그인이나 조회가 오래 걸리고, API 응답 시간이 늘어남.
더 심해지면 요청이 실패하거나, 서버가 죽거나, DB 연결이 꽉 차서 서비스 전체가 터질 수 있음.
각자 하는 일이 분리되기 때문임.
웹 요청이 많으면 웹 서버를 늘리고, 데이터 처리가 느리면 DB를 튜닝하거나 확장하면 됨.
문제가 생겼을 때도 “웹 계층 문제인지, DB 계층 문제인지” 나눠서 보기 쉬워짐.
데이터베이스는 뭘 써야 함?
데이터베이스는 크게 관계형 데이터베이스랑 비관계형 데이터베이스가 있음.
관계형 데이터베이스는 RDBMS라고도 부름.
대표적으로 MySQL, PostgreSQL, Oracle 같은 게 있음.
얘네는 데이터를 표처럼 저장함.
예를 들어 회원 테이블이 있고, 주문 테이블이 있고, 상품 테이블이 있음.
그리고 SQL을 써서 여러 테이블을 조인해서 볼 수 있음.
예를 들면 이런 거임.
회원 테이블에서 김민수 찾고
주문 테이블에서 김민수의 주문 찾고
상품 테이블에서 주문한 상품 정보까지 연결해서 가져옴
이런 관계가 명확한 데이터에는 RDBMS가 좋음.
대부분의 일반적인 서비스는 일단 RDBMS부터 시작하면 됨.
검증도 오래 됐고, 도구도 많고, 자료도 많고, 구조도 명확함.
반면 NoSQL은 조금 다름.
NoSQL은 꼭 표 형태로만 데이터를 저장하지 않음.
키-값, 문서, 그래프, 칼럼 형태 등 여러 방식이 있음.
예를 들어 Redis는 키-값 저장소 느낌이고, MongoDB는 문서 저장소 느낌이고, Neo4j는 그래프 저장소 느낌임.
NoSQL은 보통 이런 상황에서 고려함.
응답 속도가 아주 빨라야 할 때
데이터 구조가 자주 바뀔 때
JSON 같은 형태로 저장하면 충분할 때
엄청나게 많은 데이터를 다뤄야 할 때
관계형 조인이 별로 필요 없을 때
근데 중요한 건 이거임.
대부분은 RDBMS부터 시작해도 됨.
요구사항이 특이해지면 그때 NoSQL을 같이 보면 됨.
괜히 처음부터 NoSQL 쓰면 편할 것 같지만, 조인이나 정합성 처리에서 오히려 머리 아파질 수 있음.
데이터의 모양과 사용 방식이 다르기 때문임.
회원, 주문, 결제처럼 관계가 명확한 데이터도 있고, 로그, 문서, 그래프, 캐시처럼 형태가 자유로운 데이터도 있음.
그래서 목적에 따라 RDBMS, NoSQL, 캐시 저장소 같은 선택지가 나뉘는 거임.
가능은 함.
근데 모든 데이터가 표에 잘 맞는 건 아님.
예를 들어 JSON 형태의 유동적인 설정값이나, 친구 관계처럼 연결 구조가 중요한 데이터는 NoSQL이나 그래프 DB가 더 편할 수 있음.
RDBMS는 정합성과 관계형 데이터 처리에 강함.
근데 엄청난 규모, 아주 낮은 지연시간, 유연한 데이터 구조가 필요하면 NoSQL이 더 맞을 수 있음.
즉, RDBMS가 기본 선택지인 건 맞는데, 모든 상황의 정답은 아님.
여러 테이블에 나뉜 데이터를 관계를 기준으로 합쳐서 볼 수 있음.
예를 들어 회원 테이블, 주문 테이블, 상품 테이블이 따로 있어도 SQL 조인으로 한 번에 연결해서 조회할 수 있음.
정규화된 데이터를 깔끔하게 관리하면서 필요한 순간 합쳐서 볼 수 있다는 게 장점임.
JSON은 구조가 유연함.
필드가 조금씩 달라도 문서 형태로 저장하기 좋음.
RDBMS는 보통 컬럼 구조가 고정되어 있어서 데이터 모양이 자주 바뀌면 스키마 변경이 번거로운데, 문서형 NoSQL은 이런 점에서 더 편할 수 있음.
서버 확장에는 두 가지가 있음
서버가 힘들어지면 키워야 함.
방법은 크게 두 가지임.
하나는 스케일 업.
다른 하나는 스케일 아웃.
스케일 업은 기존 서버를 더 좋은 서버로 바꾸는 거임.
CPU 더 좋은 거 넣고, RAM 더 늘리고, 디스크 더 좋은 거 쓰는 방식임.
쉽게 말하면 한 대를 존나 강하게 만드는 거임.
장점은 단순함.
서버 구조를 크게 안 바꿔도 됨.
그냥 더 좋은 장비 쓰면 됨.
근데 단점도 큼.
한 대 서버는 무한히 강해질 수 없음.
장비 가격도 점점 비싸짐.
그리고 그 서버 한 대가 죽으면 서비스 전체가 죽을 수 있음.
이게 단일 장애 지점, 즉 SPOF임.
반대로 스케일 아웃은 서버를 여러 대로 늘리는 거임.
서버 한 대가 모든 일을 하는 게 아니라, 여러 대가 나눠서 처리함.
대규모 서비스는 보통 스케일 아웃이 더 적합함.
작은 서비스 = 스케일 업으로도 버팀
큰 서비스 = 결국 스케일 아웃이 필요함
초반에는 그게 제일 쉬움.
CPU, RAM 늘리면 바로 성능이 좋아질 수 있음.
근데 서버 한 대를 무한히 키울 수는 없고, 고성능 장비는 비용도 급격히 올라감. 결국 한계가 옴.
하드웨어 한계 때문임.
아무리 좋은 서버도 CPU, 메모리, 디스크를 무한대로 넣을 수는 없음.
그리고 한 대에 의존하면 그 서버가 죽었을 때 서비스 전체가 멈추는 위험도 커짐.
보통 로드밸런서가 나눠줌.
사용자는 로드밸런서로 접속하고, 로드밸런서가 뒤에 있는 여러 웹 서버로 요청을 분산함.
그래서 서버가 여러 대여도 사용자는 복잡한 걸 몰라도 됨.
그 서버가 모든 역할을 맡고 있으면 대체할 놈이 없기 때문임.
웹, DB, 세션, 파일 처리까지 한 서버에 몰려 있으면 그 서버가 죽는 순간 전체 서비스도 같이 죽음.
이런 구조를 단일 장애 지점, SPOF라고 함.
트래픽이 적고 구조를 단순하게 가져가고 싶으면 스케일 업이 좋음.
빠르게 대응할 수 있고 관리도 쉬움.
근데 트래픽이 계속 늘고, 장애에도 버텨야 하고, 대규모로 가야 하면 스케일 아웃이 더 적합함.
로드밸런서는 왜 필요함?
서버를 여러 대 두면 질문이 생김.
사용자의 요청을 어느 서버로 보내야 함?
이걸 처리하는 게 로드밸런서임.
로드밸런서는 교통정리 담당이라고 보면 됨.
사용자는 서버 1, 서버 2에 직접 붙는 게 아니라 로드밸런서의 공개 IP로 접속함.
그러면 로드밸런서가 뒤에 있는 웹 서버들한테 요청을 나눠 보냄.
예를 들어 서버가 2대 있으면,
이번 요청은 서버 1
다음 요청은 서버 2
그다음은 다시 서버 1
이런 식으로 트래픽을 분산함.
장점은 큼.
서버 한 대에 트래픽이 몰리지 않음.
서버 한 대가 죽어도 다른 서버로 요청을 보낼 수 있음.
트래픽이 늘면 웹 서버를 더 추가하면 됨.
즉, 로드밸런서는 확장성과 가용성을 올려주는 핵심 장치임.
사용자는 보통 개별 서버가 아니라 로드밸런서로 접속함.
로드밸런서가 대표 입구 역할을 함.
그 뒤에서 실제 요청을 서버 1, 서버 2, 서버 3 중 어디로 보낼지 결정함.
막 보내는 건 아님.
서버 상태, 부하, 응답 가능 여부 등을 보고 분산함.
가장 단순하게는 번갈아 보내는 방식도 있고, 더 똑똑하게는 현재 덜 바쁜 서버로 보내는 방식도 있음.
헬스 체크를 함.
로드밸런서가 주기적으로 서버에게 “살아있냐?” 하고 확인함.
서버가 응답을 못 하면 그 서버는 죽었다고 판단하고, 더 이상 요청을 보내지 않음.
로드밸런서도 SPOF가 될 수 있음.
그래서 실제 운영에서는 로드밸런서도 이중화해서 구성함.
로드밸런서 하나가 죽어도 다른 로드밸런서가 트래픽을 이어받게 만드는 거임.
사용자는 로드밸런서만 바라봄.
뒤에 서버가 몇 대 있는지는 몰라도 됨.
그래서 트래픽이 늘면 뒤에 웹 서버를 더 붙이고, 로드밸런서가 새 서버에도 요청을 나눠주게 만들면 됨.
데이터베이스도 여러 대로 나눠야 함
웹 서버는 여러 대로 늘리면 됨.
근데 데이터베이스는 조금 더 조심해야 함.
데이터는 틀리면 안 됨.
회원 정보, 주문 정보, 결제 정보가 꼬이면 큰일남.
그래서 보통 데이터베이스 다중화를 함.
가장 기본적인 방식은 주 데이터베이스와 부 데이터베이스를 나누는 거임.
주 DB는 쓰기를 담당함.
insert
update
delete
이런 데이터 변경 작업은 주 DB로 감.
부 DB는 읽기를 담당함.
select
조회
검색
목록 불러오기
이런 건 부 DB에서 처리함.
왜 이렇게 하냐면 대부분의 서비스는 쓰기보다 읽기가 훨씬 많음.
사람들이 글을 쓰는 횟수보다 글을 읽는 횟수가 훨씬 많고,
상품을 등록하는 횟수보다 상품을 보는 횟수가 훨씬 많음.
그래서 읽기 요청을 여러 부 DB로 나누면 성능이 좋아짐.
또 주 DB의 데이터를 부 DB로 복제해두면 안정성도 좋아짐.
부 DB 하나가 죽어도 다른 부 DB가 읽기 요청을 처리하면 됨.
주 DB가 죽으면 부 DB 중 하나를 새 주 DB로 승격할 수도 있음.
다만 실제 운영에서는 복제 지연, 데이터 정합성, 장애 복구 같은 문제가 있어서 생각보다 복잡함.
그래도 핵심은 이거임.
쓰기는 주 DB
읽기는 부 DB
데이터는 복제
읽기 부하는 여러 DB로 분산
그렇게 단순하진 않음.
웹 서버는 같은 코드 여러 대 띄우면 되지만, DB는 데이터 정합성이 중요함.
여러 DB에 데이터가 다르게 저장되면 큰일 나기 때문에 복제, 동기화, 쓰기 위치 같은 규칙이 필요함.
쓰기까지 여러 DB에서 동시에 하면 데이터 충돌이 생기기 쉬움.
그래서 보통 데이터 변경은 주 DB에서만 처리함.
반면 읽기는 데이터를 바꾸는 작업이 아니니까 여러 부 DB로 분산하기 좋음.
목표는 같게 유지하는 거임.
근데 실제로는 복제 지연이 생길 수 있음.
주 DB에 방금 쓴 데이터가 부 DB에 반영되기까지 아주 짧은 시간이 걸릴 수 있음.
가능함.
예를 들어 방금 프로필을 수정했는데, 바로 조회했을 때 예전 프로필이 보일 수 있음.
이걸 복제 지연 또는 eventual consistency 문제로 볼 수 있음. 그래서 중요한 조회는 주 DB를 읽게 하기도 함.
구성에 따라 가능함.
부 DB 중 하나를 새로운 주 DB로 승격시키는 걸 failover라고 함.
다만 실제 운영에서는 데이터 유실 여부, 복제 지연, 애플리케이션 연결 전환 같은 문제가 있어서 자동화가 잘 설계되어 있어야 함.
캐시는 왜 쓰는 거임?
서비스가 커지면 DB를 계속 부르는 게 부담됨.
예를 들어 인기 게시글 목록, 상품 정보, 사용자 프로필 같은 데이터는 자주 읽힘.
그런데 매번 DB까지 가서 읽으면 느림.
그래서 캐시를 둠.
캐시는 자주 쓰는 데이터나 비싼 연산 결과를 빠른 메모리에 잠깐 저장해두는 곳임.
예를 들어 사용자가 인기 게시글을 요청함.
먼저 캐시에 물어봄.
“인기 게시글 있음?”
있으면 바로 줌.
이걸 캐시 히트라고 함.
없으면 DB에서 가져오고, 그 결과를 캐시에 저장한 뒤 사용자에게 줌.
이걸 캐시 미스라고 함.
다음 사용자가 같은 데이터를 요청하면 DB까지 안 가도 됨.
캐시에서 바로 주면 됨.
그래서 응답이 빨라지고 DB 부하도 줄어듦.
근데 캐시도 막 쓰면 안 됨.
중요한 원본 데이터는 DB에 있어야 함.
캐시는 휘발성이라 서버가 재시작되면 날아갈 수 있음.
또 만료 시간을 잘 정해야 함.
너무 짧으면 DB를 자주 읽게 돼서 캐시 의미가 줄어듦.
너무 길면 오래된 데이터를 보여줄 수 있음.
그래서 캐시는 보통 이런 데이터에 좋음.
자주 읽힘
자주 바뀌지는 않음
조금 늦게 갱신돼도 큰 문제 없음
DB 부하를 줄이면 효과가 큼
DB는 정확한 원본 데이터를 저장하는 곳이지만, 모든 요청을 DB가 직접 처리하면 부담이 큼.
자주 읽는 데이터를 캐시에 두면 DB까지 가지 않고 빠르게 응답할 수 있음.
즉, 캐시는 속도 올리고 DB 부담 줄이려고 쓰는 거임.
캐시는 보통 메모리에 데이터를 둠.
메모리는 디스크 기반 DB보다 접근 속도가 훨씬 빠름.
그리고 복잡한 쿼리나 계산 없이 바로 값을 꺼내줄 수 있어서 응답이 빨라짐.
맞음.
그래서 캐시는 만료 시간, 갱신 전략, 무효화 전략이 중요함.
캐시를 빠르게 만드는 것보다 “얼마나 최신 상태를 유지할지”를 같이 설계해야 함.
그래서 중요한 원본 데이터는 캐시에만 두면 안 됨.
캐시는 임시 저장소고, 원본은 DB 같은 영속 저장소에 있어야 함.
캐시가 날아가도 DB에서 다시 가져와서 채울 수 있어야 정상적인 구조임.
자주 읽히고, 자주 바뀌지 않고, 조금 오래돼도 큰 문제가 없는 데이터가 좋음.
예를 들면 인기 게시글 목록, 상품 상세, 설정값, 프로필 일부 같은 것들임.
반대로 결제 상태, 재고 수량처럼 실시간 정확성이 중요한 데이터는 조심해야 함.
CDN은 뭐임?
CDN은 정적 파일을 사용자 가까운 곳에서 빠르게 보내주는 네트워크임.
이미지, 동영상, CSS, JavaScript 같은 파일은 매번 내 웹 서버에서 직접 줄 필요가 없음.
예를 들어 한국 사용자가 미국 서버에 있는 이미지를 받으면 느릴 수 있음.
근데 CDN이 한국 근처에 캐시 서버를 두고 있으면 거기서 바로 받을 수 있음.
그러면 훨씬 빠름.
CDN 동작은 대충 이럼.
사용자 A가 image.png를 요청함.
CDN에 그 이미지가 없으면 원본 서버에서 가져옴.
CDN은 가져온 이미지를 자기한테 저장함.
사용자 A에게 이미지를 줌.
나중에 사용자 B가 같은 이미지를 요청함.
이번에는 원본 서버까지 안 감.
CDN이 저장해둔 이미지를 바로 줌.
이게 CDN 캐싱임.
장점은 명확함.
정적 파일 로딩이 빨라짐
원본 서버 부하가 줄어듦
전 세계 사용자에게 더 안정적으로 제공 가능
트래픽이 몰려도 CDN이 어느 정도 버텨줌
다만 CDN도 고려할 게 있음.
비용이 듦.
만료 시간을 잘 정해야 함.
파일을 바꿨는데 CDN에 옛날 파일이 남아 있을 수 있음.
그럴 때는 무효화하거나 image.png?v=2 같은 버전 방식을 써야 함.
정리하면 이거임.
동적 요청은 웹 서버로
정적 파일은 CDN으로
자주 쓰는 데이터는 캐시로
원본 데이터는 DB로
이렇게 역할을 나누는 거임.
보내줘도 됨.
근데 이미지, CSS, JS 같은 정적 파일까지 웹 서버가 다 보내면 웹 서버 부하가 커짐.
CDN을 쓰면 이런 파일은 사용자 가까운 CDN 서버가 대신 보내줘서 더 빠르고 안정적임.
CDN 업체는 여러 지역에 서버를 깔아둠.
한국, 일본, 미국, 유럽 같은 여러 위치에 정적 파일 캐시 서버가 있는 거임.
사용자는 원본 서버까지 멀리 가지 않고, 가까운 CDN 서버에서 파일을 받음.
가능함.
그래서 TTL, 무효화, 버전 관리가 필요함.
이미지를 새로 바꿨는데 CDN에 예전 이미지가 남아 있으면 사용자는 이전 버전을 볼 수 있음.
CSS랑 JavaScript도 정적 파일이기 때문임.
매번 웹 서버에서 새로 계산해서 주는 게 아니라, 파일 그대로 보내면 됨.
그래서 CDN에 올려두면 브라우저가 더 빠르게 받아서 화면 로딩 속도가 좋아짐.
ㅇㅇ 그럴 수 있음.
그래서 배포할 때 파일명에 해시를 붙이거나, style.css?v=2 같은 버전 파라미터를 쓰기도 함.
또 CDN 무효화 API를 호출해서 예전 파일을 삭제할 수도 있음.
무상태 웹 계층이 왜 중요함?
웹 서버를 여러 대로 늘릴 때 중요한 개념이 무상태임.
상태라는 건 사용자 세션 같은 정보임.
예를 들어 로그인한 사용자 A의 세션 정보가 서버 1에만 저장돼 있다고 해보자.
그러면 사용자 A의 다음 요청도 반드시 서버 1로 가야 함.
요청이 서버 2로 가면 서버 2는 사용자 A가 누군지 모름.
이러면 로드밸런서가 사용자를 항상 같은 서버로 보내야 함.
이걸 고정 세션이라고 함.
근데 이 방식은 불편함.
서버 추가하기도 어렵고, 제거하기도 어렵고, 장애 처리도 복잡함.
그래서 좋은 구조는 웹 서버에서 상태 정보를 빼는 거임.
세션 정보나 사용자 상태는 웹 서버 안에 저장하지 말고, Redis, NoSQL, DB 같은 공유 저장소에 둠.
그러면 어떤 웹 서버로 요청이 가도 됨.
웹 서버는 필요할 때 공유 저장소에서 상태 정보를 가져오면 됨.
이게 무상태 웹 계층임.
장점은 큼.
서버 추가가 쉬움
서버 제거가 쉬움
장애 대응이 쉬움
자동 확장이 쉬움
즉, 웹 서버는 최대한 가볍고 교체 가능하게 만들어야 함.
처음에는 편함.
근데 서버가 사용자를 기억하면 그 사용자의 요청은 계속 같은 서버로 가야 함.
서버를 늘리거나 줄이기 어려워지고, 장애가 나면 복구도 복잡해짐.
로그인 상태를 Redis, DB, NoSQL 같은 공유 저장소에 둠.
웹 서버는 요청이 올 때 토큰이나 세션 ID를 보고 공유 저장소에서 사용자 정보를 가져옴.
그래서 어떤 웹 서버가 요청을 받아도 같은 사용자 정보를 확인할 수 있음.
로드밸런서가 자유롭게 요청을 분산할 수 있음.
서버 하나가 죽어도 다른 서버가 바로 처리할 수 있음.
또 트래픽이 늘면 서버를 추가하고, 줄면 제거하기 쉬움.
고정 세션은 사용자를 특정 서버에 묶어두는 방식임.
단기적으로는 편하지만, 서버 추가/제거/장애 처리에서 부담이 생김.
대규모로 갈수록 웹 서버는 무상태로 만들고 상태는 외부 저장소에 두는 게 훨씬 유리함.
상대적으로 훨씬 쉬워짐.
웹 서버가 상태를 들고 있지 않으니까 새 서버를 추가해도 바로 요청을 받을 수 있음.
물론 Redis나 NoSQL 자체의 안정성, 성능, 복제도 같이 설계해야 함.
데이터 센터를 여러 개 두는 이유
서비스가 더 커지면 한 지역 데이터센터만으로 부족해짐.
전 세계 사용자가 들어오면 가까운 데이터센터로 보내는 게 빠름.
미국 동부 사용자는 US-East로,
미국 서부 사용자는 US-West로 보내는 식임.
이걸 지리적 라우팅, GeoDNS라고 함.
DNS가 사용자의 위치를 보고 가까운 데이터센터 IP를 알려주는 방식임.
이렇게 하면 응답 속도가 빨라짐.
또 한 데이터센터가 장애가 나도 다른 데이터센터로 트래픽을 돌릴 수 있음.
예를 들어 US-West가 죽으면 모든 트래픽을 US-East로 보냄.
그러면 서비스 전체가 죽는 걸 막을 수 있음.
근데 멀티 데이터센터도 쉬운 건 아님.
해결해야 할 게 많음.
첫째, 트래픽을 어디로 보낼지 정해야 함.
둘째, 데이터센터 간 데이터 동기화가 필요함.
셋째, 여러 지역에서 테스트해야 함.
넷째, 모든 데이터센터에 같은 버전이 배포되도록 자동화가 필요함.
즉, 멀티 데이터센터는 속도와 안정성은 좋아지지만 운영 난이도도 올라감.
서버 여러 대는 보통 같은 지역이나 같은 데이터센터 안에서 확장하는 느낌임.
데이터센터 여러 개는 아예 물리적으로 떨어진 지역에 시스템을 나눠두는 거임.
예를 들어 미국 동부와 미국 서부, 한국과 일본에 각각 센터를 두는 식임.
물리적 거리가 짧으면 네트워크 왕복 시간이 줄어듦.
데이터가 이동해야 하는 거리와 중간 경로가 줄어들기 때문임.
그래서 가까운 데이터센터를 쓰면 응답 시간이 줄어드는 경우가 많음.
가능함.
GeoDNS는 사용자의 위치나 네트워크 정보를 보고 적절한 데이터센터 IP를 응답할 수 있음.
그래서 미국 동부 사용자는 US-East로, 서부 사용자는 US-West로 보낼 수 있음.
설계가 잘 되어 있으면 가능함.
장애가 난 센터로 가던 트래픽을 정상 센터로 우회시키면 됨.
다만 데이터 동기화, 용량 여유, 장애 감지, 라우팅 전환이 준비되어 있어야 함.
대부분은 그래야 함.
한 센터에만 데이터가 있으면 다른 센터로 우회했을 때 필요한 데이터를 못 찾을 수 있음.
그래서 데이터 복제, 동기화, 백업 전략이 중요함.
메시지 큐는 왜 필요함?
서비스가 커질수록 모든 작업을 즉시 처리하면 안 됨.
예를 들어 사용자가 사진을 업로드했는데, 서버가 그 자리에서 바로 크롭, 리사이즈, 필터 처리까지 다 하면 응답이 느려짐.
사용자는 기다려야 하고, 웹 서버는 계속 붙잡힘.
이럴 때 메시지 큐를 씀.
메시지 큐는 작업을 잠깐 맡아두는 줄이라고 보면 됨.
웹 서버는 오래 걸리는 작업을 직접 처리하지 않고 큐에 넣음.
“이 사진 리사이즈 해줘.”
그러면 작업 서버가 큐에서 작업을 꺼내서 처리함.
이 구조에는 생산자와 소비자가 있음.
생산자는 메시지를 넣는 쪽임.
소비자는 메시지를 꺼내서 처리하는 쪽임.
웹 서버가 생산자고, 작업 서버가 소비자라고 보면 됨.
장점은 이거임.
웹 서버가 오래 걸리는 작업에서 해방됨.
작업 서버가 잠깐 죽어도 메시지는 큐에 남아 있음.
큐가 길어지면 작업 서버를 더 늘리면 됨.
큐가 비어 있으면 작업 서버를 줄이면 됨.
즉, 메시지 큐는 시스템을 느슨하게 연결해줌.
서로 직접 붙어 있지 않으니까 한쪽이 잠깐 느려지거나 죽어도 전체가 바로 무너지지 않음.
바로 처리하면 사용자가 오래 기다릴 수 있음.
특히 이미지 보정, 영상 처리, 이메일 발송, 알림 발송 같은 작업은 시간이 걸림.
이런 건 큐에 넣고 뒤에서 처리하면 웹 서버가 빨리 응답할 수 있음.
보통 “작업 접수 완료”를 먼저 받고, 나중에 결과를 받음.
예를 들어 “이미지 변환 중입니다”라고 보여주고, 완료되면 알림이나 상태 변경으로 알려주는 방식임.
즉, 즉시 결과가 필요 없는 작업에 특히 잘 맞음.
큐가 durability를 보장하도록 구성되어 있으면 안전함.
소비자가 죽어도 메시지는 큐에 남아 있다가, 소비자가 다시 살아나면 처리할 수 있음.
물론 메시지 중복 처리나 실패 재시도 전략도 같이 설계해야 함.
처리 지연이 생김.
사용자는 결과를 늦게 받게 됨.
이럴 때는 작업 서버를 더 늘리거나, 작업 우선순위를 나누거나, 큐를 분리해서 처리량을 올려야 함.
웹 서버가 작업 서버를 직접 호출하지 않아도 됨.
그냥 큐에 메시지만 넣으면 됨.
작업 서버가 잠깐 죽어도 웹 서버는 큐에 넣고 끝낼 수 있으니까, 서로 강하게 묶이지 않는 구조가 됨.
로그, 메트릭, 자동화는 왜 필요함?
서버가 몇 대 없을 때는 그냥 직접 들어가서 보면 됨.
근데 서버가 수십 대, 수백 대가 되면 사람이 일일이 볼 수 없음.
그래서 로그, 메트릭, 모니터링, 자동화가 필요함.
로그는 문제가 생겼을 때 원인을 찾기 위한 기록임.
어떤 에러가 났는지
어느 서버에서 났는지
어떤 요청에서 터졌는지
이런 걸 확인할 수 있음.
메트릭은 숫자로 보는 시스템 상태임.
CPU 사용량
메모리 사용량
디스크 I/O
DB 응답 시간
캐시 히트율
일일 사용자 수
매출
재방문율
이런 걸 수집해서 현재 서비스가 정상인지 확인함.
자동화는 반복 작업을 사람 대신 처리하게 만드는 거임.
빌드, 테스트, 배포, 모니터링, 알림 등을 자동화함.
그래야 서비스가 커져도 운영이 가능함.
사람이 매번 수동으로 배포하면 언젠가 실수남.
자동화는 그 실수를 줄여줌.
메트릭과 모니터링으로 봄.
CPU, 메모리, 에러율, 응답 시간, DB 지연, 큐 적체량 같은 수치를 계속 수집함.
이 수치가 비정상적으로 변하면 알림을 받아서 문제를 확인함.
원인을 찾기 어려움.
어떤 요청에서 터졌는지, 어떤 사용자에게 발생했는지, 어느 서버에서 발생했는지 알 수 없음.
로그는 장애 상황에서 범인을 찾는 CCTV 같은 역할임.
아님.
CPU와 메모리는 서버 상태 일부만 보여줌.
응답 시간, 에러율, DB 성능, 캐시 히트율, 큐 적체량, 사용자 행동 지표까지 같이 봐야 서비스 상태를 제대로 알 수 있음.
서버는 멀쩡한데 매출이나 가입자가 급락할 수도 있음.
반대로 매출은 잘 나오는데 시스템이 곧 터질 만큼 과부하일 수도 있음.
그래서 기술 지표와 비즈니스 지표를 같이 봐야 전체 상황을 이해할 수 있음.
사람이 수동으로 하면 실수할 가능성이 큼.
서버 하나 빼먹거나, 잘못된 파일을 올리거나, 테스트를 건너뛸 수 있음.
자동화된 배포는 정해진 절차대로 빌드, 테스트, 배포를 반복하니까 실수를 줄여줌.
데이터베이스 샤딩은 뭐임?
데이터가 더 많아지면 DB도 한 대로 감당하기 힘들어짐.
이때 DB를 키우는 방법도 두 가지임.
스케일 업은 더 좋은 DB 서버를 쓰는 거임.
CPU, RAM, 디스크를 늘림.
근데 이것도 한계가 있음.
비싸고, 무한히 커질 수도 없고, 한 대가 죽으면 위험함.
그래서 수평 확장을 함.
DB 수평 확장을 보통 샤딩이라고 함.
샤딩은 큰 데이터베이스를 여러 조각으로 나누는 거임.
각 조각을 샤드라고 부름.
예를 들어 사용자 데이터를 4개의 샤드에 나눈다고 해보자.
user_id % 4 결과에 따라 저장 위치를 정함.
user_id가 0, 4, 8, 12면 샤드 0
user_id가 1, 5, 9, 13이면 샤드 1
user_id가 2, 6, 10, 14면 샤드 2
user_id가 3, 7, 11, 15면 샤드 3
이렇게 하면 데이터가 여러 DB 서버에 나뉘어 저장됨.
장점은 확장성이 좋아진다는 거임.
데이터가 늘어나면 샤드를 늘릴 수 있음.
근데 샤딩도 만능은 아님.
샤딩 키를 잘못 고르면 특정 샤드에만 데이터나 요청이 몰림.
이걸 핫스팟 문제라고 함.
예를 들어 유명인 계정 몇 개가 한 샤드에 몰리면 그 샤드만 터질 수 있음.
또 데이터가 너무 많아져서 샤드를 다시 나눠야 할 수도 있음.
이걸 재샤딩이라고 함.
그리고 여러 샤드에 흩어진 데이터를 조인하기도 어려움.
그래서 비정규화를 하거나, 애플리케이션 단에서 따로 처리해야 할 수도 있음.
정리하면 이거임.
샤딩은 DB를 여러 조각으로 나눠 확장하는 기술임.
근데 샤딩 키를 잘못 잡으면 더 큰 지옥이 열림.
처음에는 가능함.
근데 더 큰 서버로 바꾸는 건 비용이 비싸고, 언젠가는 하드웨어 한계에 부딪힘.
또 한 대에 계속 의존하면 장애 위험도 커짐. 그래서 일정 규모 이상에서는 샤딩을 고민하게 됨.
맞음.
그래서 샤딩 키가 중요함.
예를 들어 user_id 기준으로 나누면, user_id만 알면 어느 샤드에 있는지 계산해서 바로 찾아갈 수 있음.
데이터를 여러 샤드에 고르게 나누기 위해서임.
나머지 값이 0, 1, 2, 3으로 나뉘니까 4개 샤드에 분산 저장할 수 있음.
단순하고 이해하기 쉬운 방식임. 다만 샤드 수가 바뀔 때 재배치 문제가 생길 수 있음.
데이터나 요청이 고르게 분산되지 않기 때문임.
예를 들어 특정 유명인 데이터가 한 샤드에 몰리면 그 샤드만 읽기 요청이 폭발함.
이런 걸 핫스팟 문제라고 함.
ㅇㅇ 맞음.
여러 샤드에 데이터가 흩어져 있으면 DB 안에서 한 번에 조인하기 어렵거나 비싸짐.
그래서 샤딩 후에는 비정규화, 중복 저장, 애플리케이션 레벨 조인 같은 방법을 고려해야 함.
전체 구조를 한 번에 보면
처음에는 단순함.
사용자 → DNS → 웹 서버 → DB
근데 서비스가 커지면 계속 역할을 나누게 됨.
최종적으로는 이런 구조가 됨.
사용자는 DNS를 통해 주소를 찾음.
정적 파일은 CDN에서 받음.
동적 요청은 로드밸런서로 감.
로드밸런서는 여러 웹 서버에 요청을 분산함.
웹 서버는 무상태로 동작함.
세션 같은 상태 정보는 NoSQL이나 Redis 같은 공유 저장소에 둠.
자주 쓰는 데이터는 캐시에 둠.
데이터 원본은 DB에 둠.
DB는 주/부 복제로 읽기와 쓰기를 나눔.
데이터가 더 커지면 샤딩으로 나눔.
오래 걸리는 작업은 메시지 큐에 넣고 작업 서버가 비동기로 처리함.
로그, 메트릭, 모니터링, 자동화로 운영 상태를 관리함.
전 세계 서비스가 되면 여러 데이터센터를 두고 GeoDNS로 가까운 곳으로 보냄.
이게 대규모 시스템 설계의 기본 흐름임.
대충 이렇게 보면 됨.
정적 파일 요청이면 DNS를 거쳐 CDN에서 이미지, CSS, JS를 받음.
동적 API 요청이면 DNS로 주소를 찾고, 로드밸런서로 가고, 로드밸런서가 웹 서버로 보내고, 웹 서버가 캐시나 DB를 조회해서 응답함.
URL 구조와 설정이 결정함.
예를 들어 이미지 주소를 CDN 도메인으로 주면 브라우저가 CDN으로 요청함.
반대로 api.mysite.com 같은 API 주소는 로드밸런서와 웹 서버로 연결되게 설정할 수 있음.
CDN은 주로 정적 파일을 사용자 가까운 지역에서 빠르게 전달함.
캐시는 보통 서버 내부에서 자주 쓰는 데이터나 쿼리 결과를 빠르게 꺼내기 위해 씀.
쉽게 말하면 CDN은 파일 전달 속도 개선, 캐시는 데이터 조회 속도 개선에 가깝다 보면 됨.
웹 서버는 오래 걸리는 작업을 직접 하지 않고 큐에 넣음.
작업 서버는 큐에서 작업을 꺼내서 나중에 처리함.
그래서 사용자의 요청 흐름과 백그라운드 작업 흐름이 분리됨.
맞음.
컴포넌트가 늘면 복잡도도 올라감.
그래서 필요할 때 단계적으로 도입해야 함. 다만 규모가 커지면 역할을 분리하지 않는 구조가 더 큰 장애를 만들 수 있음.
진짜 핵심만 요약하면
1주차 전체 내용을 핵심 원칙 중심으로 요약함.
처음에는 서버 한 대로 시작해도 됨.
근데 사용자가 늘면 서버가 해야 할 일이 너무 많아짐.
그래서 역할을 나눔.
웹 서버는 요청 처리.
DB는 데이터 저장.
로드밸런서는 트래픽 분산.
캐시는 자주 쓰는 데이터 빠르게 제공.
CDN은 이미지, CSS, JS 같은 정적 파일 빠르게 제공.
메시지 큐는 오래 걸리는 작업을 비동기로 처리.
NoSQL은 상태 정보나 비정형 데이터처럼 특정 요구에 맞게 사용.
샤딩은 DB 데이터를 여러 조각으로 나눠 확장.
로그와 메트릭은 문제를 찾고 상태를 보는 장치.
자동화는 커진 시스템을 사람이 실수 없이 굴리기 위한 장치.
결국 대규모 시스템 설계는 어려운 말로 포장돼 있지만, 본질은 단순함.
한 놈한테 다 시키지 말고,
역할별로 나누고,
자주 쓰는 건 가까이 두고,
오래 걸리는 건 뒤로 빼고,
장애가 나도 다른 놈이 대신하게 만들고,
필요하면 서버를 쉽게 늘릴 수 있게 만드는 거임.
한 서버가 모든 일을 하면 병목도 한 곳에 생기고, 장애도 한 곳에서 터짐.
역할을 나누면 각 부분을 따로 확장하고 따로 관리할 수 있음.
웹 서버, DB, 캐시, CDN, 큐가 나뉘는 이유가 이거임.
ㅇㅇ 맞음.
캐시는 자주 쓰는 데이터를 서버 가까이에 두는 거고, CDN은 자주 쓰는 정적 파일을 사용자 가까이에 두는 거임.
둘 다 핵심은 “멀리 가지 말고 가까운 데서 빨리 가져오자”임.
맞음.
사용자가 굳이 기다릴 필요 없는 작업은 큐에 넣고 나중에 처리함.
그래서 웹 서버는 빠르게 응답하고, 무거운 작업은 작업 서버가 비동기로 처리함.
단일 장애 지점을 줄여야 함.
서버를 여러 대 두고, 로드밸런서를 이중화하고, DB를 복제하고, 캐시나 큐도 장애 대응이 가능하게 만들어야 함.
핵심은 “하나가 죽어도 다른 놈이 대신하게 만드는 구조”임.