본문 바로가기
카테고리 없음

대규모 시스템 설계 기초 2~4장

by 계범 2025. 3. 30.

2장 개략적인 규모 추정

시스템의 설계에 앞서,시스템의 용량이나 성능 요구사항을 개략적으로 추정할 때 사용되는 것들에 대해 설명해주는 장이다.

 

2의 제곱수 또는 10의 제곱수를 기반으로 아래와 같은 용어가 있다.

고가용성 : 시스템이 오랜 시간 동안 지속적으로 중단 없이 운영될 수 있는 능력을 지칭하는 용어

QPS : query per second로 초당 쿼리 수

저장소 요구량 : 실 데이터 사용량

 

시스템 설계 전

QPS, 최대 QPS, 저장소 요구량, 캐시 요구량, 서버 수 등을 추정하는 것이다.

 

해당 상황에서의 cpu, memory를 확인하고

서비스의 resource 규모를 파악하면 현재 어느정도까지 커버할 수 있는지 대략적으로 파악할 수 있을 것 같다.

 

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

3장은 제목 그대로 시스템 설계 면접을 할 시에 어떻게 하면 좋을지 알려주는 장이다.

면접 뿐만 아니라, 실제 서비스를 설계해 나갈때도 이렇게 하면 좋겠다는 생각이 들었다.

 

1단계 : 문제 이해 및 설계 범위 확정

어떤 기능을 만들어야하는가?

제품 사용자 수는?

회사의 규모는 얼마나 빨리 커지리라 예상하는가?

회사가 주료 사용하는 기술 스택은?

 

2단계 : 개략적인 설계안 제시 및 동의 구하기

위에서 들은 내용을 기반으로 최초 청사진을 제시하고 동의를 얻는 과정이다.

내가 생각한 추정이 맞는지, 미처 고려하지 못한 엣지 케이스들은 있는지 등을 확인하는 시간이다.

 

3단계 : 상세 설계

2단계에서 들은 내용을 바탕으로 상세 설계를 진행한다.

 

예를 들어, 채팅 시스템이라면 지연시간을 줄이고 사용자의 온/오프라인 상태를 어떻게 표시할 것인가를 듣고 싶어할 것이다.

집중적으로 디테일하게 더 설계해야하는 부분을 처리한다.

 

4단계 : 마무리

후속질문이나 스스로 추가 논의를 진행하는 데 쓴다.

 

면접관이 시스템 병목구간, 혹은 더 개선 가능한 지점을 찾아내라는 주문을 할 수 있다.

개선할 점이 없다는 답변은 하지 않도록 하자.

언제나 개선할 점은 있다.

 

해당 장에선 해야할것과 하지말아야할 것들을 정리해놨다.


스스로 내린 가정이 옳다 믿고 진행하지말고 질문을 통해 확인하라는 말과
나의 사고 흐름을 이해할 수 있게 하란 말이 가장 인상 깊었다.

 

4장 처리율 제한 장치의 설계

처리율 제한 장치는 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치다.

 

예를 들어, api의 요청 횟수가 정의된 임계치(threshold)를 넘어서면 추가로 도달한 모든 호출은 처리가 중단된다.

  • 사용자는 초당 2회 이상 새글을 올릴 수 없다.
  • 같은 ip 주소로는 하루에 10개 이상의 계정을 생성할 수 없다.
  • 같은 디바이스로는 주당 5회 이상 리워드를 요청할 수 없다.

 

처리율 제한 장치를 두면은 아래와 같은 장점이 있다.

  • dos 공격 방지
  • 비용절감
  • 서버 과부하 방지

해당 장치를 둘때는 사전에 파악해야하는 내용들이 있다.

  • 설정된 처리율을 정확하게 제한해야하는지? ( 경성. 연성 )
  • 해당 장치는 낮은 응답시간을 보장해야한다. ( 해당 장치로 인해 기존 요청 응답시간이 지연되어선 안됨 )
  • 가능한 한 적은 메모리 사용
  • 단일 서버인지? 분산 서버인지?
  • 예외처리
  • 높은 결함 감내성 ( 제한 장치에 장애가 생기더라도 시스템에 영향을 주어선 안됨 )

 

처리율 제한 알고리즘 종류는 5가지를 설명했다.

  • 토큰 버킷
  • 누출 버킷
  • 고정 윈도 카운터
  • 이동 윈도 로그
  • 이동 윈도 카운터

 

분산환경에서 처리율 제한 장치를 구현하다보면 다음 문제들을 만난다.

경쟁조건

경쟁조건의 예시)

redis에서 카운터 값을 읽어서 카운터를 증가시키고, 해당 카운터를 기반으로 처리율 제한 장치를 구현해놨다.

 

현재 카운터는 3이라고 했을 때,

각각의 요청을 처리하는 스레드가 동시에 레디스에서 값을 읽었고, (2개의 스레드가 읽은 값은 3)
둘다 카운터를 +1로 올리려고한다면

총5가 되어야할 것 같지만, 둘다 4를 업데이트하려고학때문에 다른값이 되어버린다.

해당상황에서 가장 널리 알려진 해결책은 락이지만, 락은 시스템 성능을 상당히 떨어트린다.

 

다른 방법으로 소개한 것은 루아 스크립트와 정렬 집합이라 불리는 레디스 자료구조를 쓰는 것이다. (정렬 집합은 도움되지 않음..;)

 

루아 스크립트란, 원자적으로 실행되는 스크립트이다.

레디스 내에서 해당 스크립트가 실행되는 동안은 다른 명령어가 끼어들 수 없다.

명시적인 락 획득과 해제같은게 없기 때문에 더 빠르다. ( 실제론, 당연히 해당 스크립트가 실행되는 동안 기다리게된다 )

 

책에선 sorted set(정렬집합)이 경쟁 조건 이슈를 해결하는 방법이라고 설명되어있지만,

실제적으로는 해당 자료구조가 해결해주는건 아니고 redis 트랜잭션을 통해 해결했다.. (결국 락을 사용했다는 뜻)

 

https://stripe.com/blog/rate-limiters

https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/

 

더보기

🔍 차이점 요약

 

항목 Redis 트랜잭션 (MULTI/EXEC) Lua 스크립트 (EVAL)
실행 방식 명령 큐에 저장 후 EXEC로 실행 전체가 하나의 명령처럼 실행
끼어들기 가능성 watch를 쓰지 않으면,큐에 저장 중엔 취약.
외부에서 같은 키를 변경할 수 있기 때문.
절대 끼어들 수 없음 (완전한 원자성)
실패 처리 중간 실패는 나머지 계속 진행됨 중간 실패 시 전체 실패
롤백 지원 ❌ 없음 ❌ 없음
조건문/로직 처리 ❌ 불가 ✅ 가능 (if, loop, 변수 등)
복잡한 제어 흐름 ❌ 불가 ✅ 가능
성능 빠름 약간 더 느릴 수 있음 (단, 여전히 ms 수준)

✅ 어떤 걸 언제 써야 하나?

단순 명령 여러 개를 원자적으로 실행하고 싶을 때 MULTI/EXEC
동시성 제어 + 조건 분기 + 로직 판단이 필요한 경우 ✅ Lua Script
중간 실패가 발생하면 전체 실패시키고 싶을 때 ✅ Lua Script
재시도 로직 없이 확실한 처리 보장 원할 때 ✅ Lua Script
키 변경 감지 후 실행하고 싶을 때 WATCH + MULTI/EXEC

동기화

처리율 제한장치가 여러개라고 했을 때 해결책 중 하나는 고정 세션이다.

고정 세션은 같은 클라이언트로부터의 요청은 항상 같은 처리율 제한 장치로 보낼 수 있도록 하는 것인데,

규모면으로 확장 가능하지도 않고 유연하지도 않기 때문이다.

 

다른 방법으로는 레디스와 같은 중앙 집중형 데이터 저장소를 쓰는 것이다.

 

처리율 제한을 회피하는 방법

클라이언트 측 캐시를 사용하여 api 호출 횟수를 줄인다.

처리율 제한의 임계치를 이해하고, 짧은 시간 동안 너무 많은 메세지를 보내지 않도록 한다.

예외나 에러를 처리하는 코드를 도입하여 클라이언트가 예외적 상황으로부터 우아하게 복구될 수 있도록 한다.

재시도 로직을 구현할 때는 충분한 백오프 시간을 둔다.

 

현재 우리 서비스의 구현방식

1)일부 sms 발송 서비스에서 처리율 제한 장치를 구현해둠.

서비스 내에서 사용자 별 키값을 기반으로, 레디스를 통해 구현.

(서비스까진 요청을 받음!)

레디스의 트랜잭션을 이용하고 있었어서, 레디스 루아 스크립트로 변경해야겠다.

 

2) third party 시스템 내 구현 항목 적용

nhn 클라우드 서비스를 이용중인데

카카오톡 알림톡 서비스의 경우 헤더에 특정 값을 넣으면, 중복 발송 제한이 걸려있음.

X-NC-API-IDEMPOTENCY-KEY

https://docs.nhncloud.com/ko/Notification/KakaoTalk%20Bizmessage/ko/alimtalk-api-guide-v1.5/

 

서비스에 요청오기전 처리는 없는것으로 보이는데 추가 확인을 해봐야겠다

 

댓글