Architecture

책<가상 면접 사례로 배우는 대규모 시스템 설계 기초> (읽는 중...)

팅리엔 2025. 4. 6. 12:58

 

 

가상 면접 사례로 배우는 대규모 시스템 설계 기초 : 알라딘

16가지 실제 시스템 설계 면접 문제와 상세한 답안을 제시한다. 시스템 동작 원리를 시각적으로 보여 주는 188개의 도해로 설명하면서 4단계 접근법으로 면접 문제를 풀 수 있도록 돕는다. 이 책

www.aladin.co.kr

 

 

 

1장. 사용자 수에 따른 규모 확장성

  • 어떤 데이터베이스를 선택할 것인가?
    • 관계형 vs 비관계형
    • NoSQL : key-value, graph, document, column
    • 비관계형이 더 적절할 수도 있는 상황: 
      • 아주 낮은 응답 지연시간latency이 요구됨
      • 다루는 데이터가 비정형unstructured임
      • 데이터(json, yaml, xml 등)를 직렬화, 역직렬화 할 수 있기만 하면 됨
      • 아주 많은 양의 데이터를 저장해야 함
  • 수직적 규모 확장scale up
    • 고사양 자원 추가(더 좋은 cpu, 더 많은 ram 등)
    • 단순함
    • 트래픽 양이 적을 때
    • 장애에 대한 자동복구failover 방안이나 다중화redundancy 방안을 제시하지 않는다.
  • 수평적 규모 확장vertical scailing
    • 서버를 추가하여 성능 개선
    • 로드밸런서 
      • 웹서버는 클라이언트의 접속을 직접 처리하지 않는다.
      • 보안은 위해 서버 간 통신에는 사설 ip 주소를 사용한다. 
    • 데이터베이스 다중화
      • master-slave 관계
      • 원본은 master에, 사본은 slave에 저장
      • 쓰기 연산은 master에서만 지원, slave는 읽기 연산만 지원
      • 데이터를 지역적으로 떨어진 여러 장소에 다중화시켜 놓을 수 있다.
      • + multi-masters, circular replication
    • 캐시
      • 값 비싼 연산 결과, 자주 참조되는 데이터를 메모리에 두고, 뒤 이은 요청이 보다 빨리 처리될 수 있도록 한다.
      • 데이터 갱신은 자주 일어나지 않지만 참조는 비번하게 일어날 때
      • + 생각할 점
        • 캐시 만료 정책은 어떻게 할 것인가?: 너무 짧거나, 너무 길지 않게
        • 일관성consistency을 어떻게 유지할 것인가?: 원본 갱신 연산과 캐시 갱신 연산이 단일 트랜잭션으로 처리되지 않으면 일관성이 깨질 수 있다.
        • 장애에 어떻게 대처할 것인가?: 캐시 서버 다중화
        • 캐시 메모리 크기는 어떻게 할 것인가?: 너무 작으면 eviction이 자주 일어나 성능이 떨어진다. 과할당overprivision이 한 방법.
        • 데이터 방출eviction 정책은 어떻게 할 것인가?: 경우에 맞게. LRU(least recently used)가 가장 널리 쓰임
        • 캐시 전략은 어떻게 할 것인가?: 경우에 맞게
    • 콘텐츠 전송 네트워크CDN
      • 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크
      • 이미지, 비디오, CSS, JavaScript 파일 등
      • 콘텐츠가 CDN에 없으면 원본 서버에서 가져오고, 가져온 콘텐츠를 CDN에 저장한다.
      • + 생각할 점
        • 비용: 자주 사용되지 않는 콘텐츠는 캐싱 이득이 크지 않으므로 CDN에서 뺀다.
        • 적절한 만료 시한 설정
        • 장애 대처 방안
        • 콘텐츠 무효화 방법: 아직 만료되지 않은 콘텐츠더라도 CDN 사업자 제공 API 사용, 콘텐츠 다른 버전을 서비스하도록 object versioning을 사용하면 CDN에서 제거할 수 있다.
      • + 동적 콘텐츠 캐싱 (request path, query string, cookie, request header 등 정보에 기반하여 HTML 페이지 캐싱)
    • 무상태stateless 웹 계층
      • 웹 계층을 수평적으로 확장하려면 상태 정보(사용자 세션 데이터 등)를 웹 계층에서 제거해야 한다.
      • 상태 정보를 공유 저장소에 보관하고, 필요할 때 가져온다. 
    • 데이터 센터
      • 장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내된다. geo-routing
      • 지리적 라우팅에서의 geoDNS는 사용자의 위치에 따라 도메인 이름을 어떤 ip 주소로 변환할지 결정할 수 있도록 해주는 DNS 서비스다.
      • + 생각할 점
        • 트래픽 우회: 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야한다.
        • 데이터 동기화: 데이터를 여러 데이터 센터에 걸쳐 다중화 한다.
        • 테스트와 배포: 여러 위치에서 서비스를 테스트해봐야 한다. 
    • 메시지 큐
      • 메시지의 무손실 보장
      • 비동기 통신 지원
    • 로그, 메트릭, 자동화
      • 로그: 로그를 단일 서비스로 모아주는 도구를 활용하면 편하다.
      • 메트릭: 사업 현황에 관한 정보, 시스템의 현재 상태 정보
        • 호스트 단위 메트릭: cpu, 메모리, 디스크 i/o에 관한 메트릭
        • 종합aggreageted 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능
        • 핵심 비즈니스 메트릭: daily active user, 수익, 재방문
      • 자동화: ci 도구
        • 빌드 속도 개선하기
          • 병렬화: gradle, gitlab 등 job 병렬 실행 (test, build, lint 등)
          • 변경된 모듈의 테스트만 실행
          • 개발 단계마다 실행할 테스트 분리(개발단계: 최소한의 테스트, merge전: 전체 테스트)
          • 캐시 활용: 변경 없는 task는 재사용(gradle은 incremental build를 한다.)
    • 데이터베이스의 규모 확장
      • 수평적 확장
        • 샤딩sharding
        • 데이터베이스를 shard라는 작은 단위로 분할한다.
        • 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.
        • 샤딩 키(partition key) 정하기
          • 데이터가 어떻게 분산될지 정한다.
          • 하나 이상의 칼럼으로 구성
          • 데이터가 고르게 분산될 수 있도록 하는 게 가장 중요하다. 
        • + 생각할 점
          • 데이터의 재샤딩resharding: 데이터가 많아져서 하나의 샤드로는 감당하기 어려울 때, 샤드 간 데이터 분포가 고르지 못해서 어떤 샤드의 공간 소모가 빨리 진행될 때 (샤드 소진shard exhaustion). 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치해야 한다.
          • 유명인사celebrity 문제(핫스팟 키hotspot key 문제): 특정 샤드에 질의 집중
          • 조인과 비정규화join and de-normalization: 일단 여러 샤드로 쪼개고 나면 여러 샤드에 걸친 데이터를 조인하기가 힘들다. 한 가지 방법은 비정규화. 

 

 

 

2장. 개략적인 규모 추정

  • 1바이트 = 8비트
  • ASCII 문자 하나 = 1바이트
  • KB < MB < GB < TB < PB

통상적인 컴퓨터에서 연산들의 응답지연 값

  • 결론
    • 메모리는 빠르지만 디스크는 아직도 느리다.
    • 디스크 탐색seek은 가능한 한 피하라.
    • 단순한 압축 알고리즘은 빠르다.
    • 데이터를 인터넷으로 전송하기 전에 가능하면 압축하라.
    • 데이터 센터는 보통 여러 region에 분산되어 있고, 센터들 간에 데이터를 주고받는 데는 시간이 걸린다.
  • 고가용성high availability: 시스템이 지속적으로 중단 없이 운영될 수 있는 능력
    • 대부분 99~100% 사이 값
    • 99.9% 연간 장애시간 3.65일, 99.999% 연간 장애시간 5.26분
  • 예시: 트위터의 QPS와 저장소 요구량 추정
    • 가정: MAU 3억명, 50% 사용자가 매일 사용, 평균적으로 매일 2건의 트윗 업뎃, 미디어 포함 트윗은 10% 정도, 데이터는 5년간 보관
    • 추정: 
      • QPS 추정
        • DAU = 3억 * 50% = 1.5억
        • QPS = 1.5억 * 2 트윗 / 24시간 / 3600초 = 약 3,500
        • 최대 QPS = 2 * QPS = 약 7,000
      • 저장소 요구량
        • 평균 트윗 크기: tweet_id 64바이트(ASCII 64글자), 텍스트 140바이트(ASCII 140글자), 미디어 1MB
        • 미디어 저장소 요구량: 1.5억 * 2 * 10% * 1MB = 30TB/일
        • 5년간 미디어 저장소 요구량: 30TB * 365 * 5 = 약 55PB
    • 면접 팁
      • 근사치로 계산하라. 정확한 숫자가 필요한 게 아니다.
      • 가정들은 적어두라. 나중에 다시 살펴보게.
      • 단위를 붙여라. 안 붙이면 스스로 헷갈린다.
      • 많이 출제되는 규모 추정 문제는 QPS, Peak QPS, 저장소 요구량, 캐시 요구량, 서버 수

 

 

 

3. 시스템 설계 면접 공략법

  • 실제 세계에서 쓰이는 제품을 당신이 한 시간 안에 설계하길 기대하는 게 아니다. 
    협력에 적합한 사람인지, 압박이 심한 상황을 잘 헤쳐나갈 수 있는지, 모호한 문제를 해결할 능력이 있는지, 피드백을 건설적으로 받아들일 수 있는지, tradeoff들 사이에서 적절한 결정을 할 수 있는지 확인하는 자리이다.
  • 1단계: 문제 이해 및 설계 범위 확정
    • 생각 없이 바로 답을 내지 말자. 면접은 퀴즈쇼가 아니다.  요구사항을 완전히 이해하고 가정들을 분명히 해야한다. 
    • 올바른 질문 하기, 적절한 가정 하기, 시스템 구축에 필요한 정보 모으기
    • 예를 들어 다음 같은 질문들
      • 구체적으로 어떤 기능을 만들어야 하나?
      • 제품 사용자 수는 얼마나 되나?
      • 회사의 규모는 얼마나 빨리 커지리라 예상하나?
      • 회사가 주로 사용하는 기술 스택은 무엇인가? 설계를 단순화하기 위해 활용할 수 있는 기존 서비스는 어떤 것들이 있는가?
    • 예시: 뉴스 피드 설계
      • 질문하기: 가장 중요한 기능은? 정렬 기준, 포스트마다 다른 가중치가 부여되어야 하는지? 최대 몇 명의 사용자와 친구가 될 수 있는지? 트래픽 규모는? 이미지나 비디오 업로드?
  • 2단계: 개략적인 설계안 제시 및 동의 구하기
    • 최초 정사진 제시하고 의견 구하기. 면접관을 팀원인 것처럼 대하라.
    • 핵심 컴포넌트를 다이어그램으로 그린다. 클라이언트, API, 웹 서버, 데이터 저장소, 캐시, CSN, 메시지 큐 등이 포함될 수 있다.
    • 개략적 추정이 필요한지 면접관에게 미리 물어본다. 계산 과정은 소리 내어 설명하라. 
    • 가능하다면 시스템의 구체적 사용 사례도 몇 가지 살펴본다. 에지 케이스를 발견할 수도 있다.
    • 예시: 뉴스 피드 설계
      • 기능 분리
        • 피드 발행: 사용자가 포스트를 올리면 친구 뉴스 피드에 뜬다.
        • 피드 생성: 어떤 사용자의 뉴스 피드는 해당 사용자 친구들의 포스트를 시간 역순으로 정렬해 만든다.
        • feed publishing: 저장, 전송, 알림 서비스 => feed building: 생성 서비스
  • 3단계: 상세 설계
    • 설계 대상 컴포넌트 사이의 우선순위를 정한다. 
    • 면접관은 당신이 특정 컴포넌트의 세부사항을 깊이 있게 설명해보길 원한다.
    • 사소한 세부사항보다 시스템 설계 능력이 있다는 것을 입증하는 게 중요하다. 
    • 예시: 뉴스 피드 설계
      • feed publishing 
        • 로드밸런서
        • 웹서버: 인증, 처리율 제한 
        • 포스팅 저장 서비스 - 포스팅 캐시 - 포스팅 데이터베이스
        • 포스팅 전송 서비스
          • 1. 친구 id 목록 추천: 그래프 데이터베이스
          • 2. 친구 데이터 추출: 사용자 정보 캐시 - 사용자 정보 데이터베이스
          • 3. 포스팅 전송: 메시지 큐 - 포스팅 전송 작업 서버 - 뉴스 피드 캐시
        • 포스팅 알림 서비스
      • feed building
        • CDN
        • 로드밸런서
        • 웹서버: 인증, 처리율 제한
        • 뉴스 피드 서비스
          • 1. 뉴스 피드 캐시
          • 2. 포스팅 캐시 - 포스팅 데이터베이스
          • 3. 사용자 정보 캐시 - 사용자 정보 데이터베이스
    • 4단계: 마무리
      • 좀 더 개선 가능한 지점을 찾아내기. 개선할 점은 언제나 있다.
      • 당신의 설계를 다시 한 번 요약하기.
      • 오류가 발생한다면 무슨 일이 생기는지 따져보기.
      • 운영 이슈
      • 미래에 닥칠 규모 확장
  • 요약
    • 질문을 통해 확인하라.
    • 문제의 요구사항을 이해하라.
    • 정답은 없다는 것을 명심하라.
    • 면접관과 소통하라. 침묵하지 마라. 생각을 소리내어 말하라.
    • 가능하다면 여러 해법을 함께 제시하라.
    • 개략적 설계에 면접관이 동의하면, 각 컴포넌트의 세부사항을 설명하라. 가장 중요한 컴포넌트부터.
    • 면접관의 아이디어를 이끌어내라. 팀원처럼 협력하라. 힌트를 청하라. 의견을 일찍, 자주 구하라.

 

 

 

4장. 처리율 제한 장치rate limiter의 설계

  • 클라이언트가 보내는 트래픽 처리율rate을 제한하기 위한 장치
  • 특정 기간 내에 전송되는 클라이언트의 요청 횟수 제한하기
  • 예시: 사용자는 초당 2회 이상 새 글을 올릴 수 없다, 같은 IP 주소로는 하루에 10개 이상의 계정을 생성할 수 없다, 같은 디바이스로는 주당 5회 이상 리워드를 요청할 수 없다. 
  • 1단계: 문제 이해 및 설계 범위 확정
    • 질문 예시:
      • 어떤 걸 제한해야 하는가? 클라이언트 측 vs 서버 측? => 서버측 API 제한
      • 어떤 기준으로 제한? IP 주소? 사용자 IP? => 다양한 형태의 제어 규칙을 정의할 수 있어야 함
      • 시스템 규모? 스타트업 vs 큰 기업? => 대규모 요청을 처리할 수 있어야 함
      • 분산 환경? => 분산 환경
      • 독립된 서비스 vs 애플리케이션 코드에 포함? => 본인 결정
      • 사용자 요청이 제한된 경우 사용자에게 알려야 함? => 알려야 함
    • 요구사항 정리:
      • 설정된 처리율을 초과하는 요청은 정확하게 제한한다.
      • 낮은 응답시간
      • 가능한 적은 메모리 사용
      • 분산형 처리율 제한: 하나의 처리율 제한 장치를 여러 서버나 프로세스에서 공유할 수 있어야 한다.
      • 예외 처리: 요청이 제한되었을 경우 사용자에게 분명하게 보여주어야 한다.
      • 높은 결함 감내성fault tolerance: 제한 장치에 장애가 생기더라도 전체 시스템에 영향을 주어선 안 된다.
  • 2단계: 개략적 설계안 제시 및 동의 구하기
    • 처리율 제한 장치는 어디에 둘 것인가?: 클라이언트 측 처리율 제한은 서버 부하를 줄이고, UX를 향상할 수 있지만 우회가 쉬어서 쉽게 뚫린다.
      • 처리율 제한 미들웨어를 두기: 보통 API gateway 컴포넌트에 구현됨(처리율 제한 + ssl 종단, 사용자 인증, ip whitelist 관리 등). 클라우드 업체가 제공하는 거 사용해도 됨. 직접 구현하려면 시간 걸림.
    • HTTP 429(Too Many Requests) 응답하기
    • Redis 등에 상태 저장: 빠른 저장소가 필요하다. Redis는 빠르고, TTL(Time To Live)를 설정할 수 있고, atomic 연산(INCR 카운터 증가, EXPIRE 만료 등)을 제공한다. 
    • 동작 원리
      • 클라이언트가 요청 보냄 => 처리율 제한 미들웨어가 레디스의 지정 버킷에서 카운터를 가져와 한도 도달 확인 => 한도 도달 안했으면 요청은 API 서버로 전달됨 => 미들웨어는 카운터 값을 증가시킨 후 다시 레디스에 저장
    • 처리율 제한 알고리즘
      • 토큰 버킷 token bucket
      • 누출 버킷 leaky bucket
      • 고정 윈도 카운터 fixed window counter
      • 이동 윈도 로그 sliding window log
      • 이동 윈도 카운터 sliding window counter
    • 토큰 버킷 알고리즘
      • 버킷은 지정된 용량을 갖는 컨테이너. 버킷에는 일정 속도로 토큰이 쌓인다. 요청 1번 당 토큰 1개를 소비한다. 토킷이 없는 경우 해당 요청은 버려진다.
      • 2가지 인자: 버킷 크기(버킷 최대 10개 저장), 토큰 공급율 refill rate(초당 5토큰 생성)
      • 일반적으로 API 엔드포인트마다 별도의 버킷을 둔다. 
      • ip 주소별로 처리율 제한을 둔다면 ip 주소마다 버킷을 하나씩 할당해야 한다.
      • 시스템의 처리율을 초당 10,000개로 제한하고 싶다면 모든 요청이 하나의 버킷을 공유하도록 해야한다.
      • 장점
        • 구현이 쉽다. 
        • 짧은 시간에 집중되는 트래픽(burst of traffic)도 처리 가능하다. 5개 토큰이 있는데, 5개 요청을 한꺼번에 허용할 수 있다는 말. 요청량을 제한하면서도 사용자에게 좋은 ux경험을 제공한다.
      • 단점
        • 두 개 인자를 설정하는 게 까다롭다.
    • 누출 버킷 알고리즘
      • 토큰 버킷 알고리즘과 비슷한데 요청 처리율이 고정되어 있다. 
      • 보통 FIFO 큐로 구현한다.
      • 요청이 도착하면 큐가 가득 차 있는지 보고, 빈자리에 있으면 큐에 추가, 가득 차 있으면 새 요청을 버린다. 지정된 시간마다 큐에서 요청을 꺼내어 처리한다.
      • 2가지 인자: 버킷 크기(큐 최대 10개 저장), 처리율outflow rate(초당 5 요청 처리)
      • 장점
        • 큐의 크기가 제한되어 있어 메모리 사용이 효율적이다.
        • 고정된 처리율을 갖고 있기 때문에 안정적 출력이 필요한 경우 적합하다.
      • 단점
        • 단시간에 많은 트래픽이 몰리는 경우, 제때 처리하지 못하면 최신 요청들은 버려진다.
        • 두 개 인자를 설정하는 게 까다롭다.
    • 고정 윈도 카운터 알고리즘
      • 타임라인timeline을 고정된 간격의 윈도window로 나누고, 각 윈도마다 카운터counter를 붙인다.
      • 요청이 접수될 때마다 카운터의 값이 1씩 증가한다. 카운터의 값이 사전에 설정된 임계치에 도달하면 새로운 요청은 새 윈도가 열릴 때까지 버려진다.
      • 장점
        • 메모리 효율이 좋다.
        • 이해하기 쉽다.
      • 단점
        • 윈도의 경계 부근에 순간적으로 많은 트래픽이 집중될 경우 윈도에 할당된 양보다 더 많은 요청이 처리될 수 있다.
  • 3단계: 상세 설계
    • 제한 규칙은 보통 설정 파일 형태로 디스크에 저장한다.
    • 작업 프로세스는 수시로 규칙을 디스크에서 읽어 캐시에 저장한다. 
    • 처리율 제한 미들웨어는 제한 규칙, 카운터, 마지막 요청의 타임스탬프를 레디스 캐시에서 가져온다.
    • 경우에 따라서는 제한 걸린 메시지를 나중에 처리하기 위해 큐에 보관할 수 있다.
    • HTTP 응답 헤더
      • X-Ratelimit-Remaining: 윈도 내에 남은 처리 가능 요청 수
      • X-Ratelimit-Limit: 매 윈도마다 클라이언트가 전송할 수 있는 요청 수
      • X-Ratelimit-Retry-After: 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는지 알림
    • 분산 환경에서의 이슈
      • 경쟁 조건race condition
        • 락Lock
        • 루아 스크립트Lua script: 락 없이 모든 로직이 원자적으로 실행됨
          local counter = tonumber(redis.call("GET", KEYS[1]))
          if counter < 5 then
              return redis.call("DECR", KEYS[1])
          else
              return -1
          end
        • 정렬 집합sorted set: 정렬 상태를 redis가 알아서 보장해준다.
      • 동기화 이슈
        • 처리율 제한 장치 서버를 여러 대 둘 때
        • 고정 세션sticky session을 사용해서 같은 클라이언트의 요청은 항상 같은 장치로 보내기: 확장 불가능, 유연하지 않음
        • Redis와 같은 중앙 집중형 데이터 저장소 사용
      • 성능 최적화
        • edge server로 지역성 개선
        • 장치 간 데이터 동기화 할 때 최종 일관성 모델eventual consistency model 사용
  • + hard or soft 처리율 제한 : 연성soft처리율제한에선 요청 개수는 잠시동안은 임계치를 넘어설 수 있다. warn 로그 남기기, 기준 초과시 응답 일부러 지연시키기 등.
  • + 다양한 계층에서의 처리율 제한 (OSI 계층)
  • + 처리율 제한을 회피하는 방법
    • 클라이언트 측에서 캐시를 사용해 API 호출 횟수를 줄인다.
    • 짧은 시간 동안 너무 많은 메시지를 보내지 않도록 한다.
    • 재시도 로직을 구현할 때는 충분한 back-off 시간을 둔다.