7장 분산 시스템을 위한 유일 ID 생성기 설계

분산 환경에서 관계형 데이터베이스의 기본키 설정인 auto_increment 를 사용하기 어렵다.

데이터베이스 서버 한 대로는 감당하기 어렵고, 여러 데이터베이스 서버를 쓸 경우 delay 를 낮추기 어렵기 때문이다.


문제 이해 및 설계 범위

  • ID 는 유일할 것
  • ID 는 숫자로 구성
  • ID 는 64비트로 표현
  • ID 는 발급 날짜 순으로 정렬 가능 해야 함
  • 초당 10,000 개의 ID 생성 해야 함

개략적 설계안 제시 및 동의 구하기

유일성을 보장하는 ID 생성 방법

  • 다중 마스터 복제 (multi-master replication)
  • UUID (Universally Unique Identifier)
  • 티켓 서버 (ticket server)
  • 트위터 스노플레이크 (twiter snowflake) 접근법

다중 마스터 복제

데이터베이스의 auto_increment 를 활용하는 기법이다. 그러나 1씩 증가하는 것이 아닌 n(데이터 베이스 서버 수) 만큼
증가 하는 것이다.

데이터베이스 수를 늘리면 초당 생산 가능 ID를 늘릴 수 있다.
그러나 다음과 같은 단점이 있다.

  1. 여러 데이터 센터에 걸쳐 규모 늘리기 어려움
  2. ID 유일성 보장하나 그 값이 시간의 흐름에 맞추는 것을 보장하긴 어렵다.
  3. 서버 추가/삭제 시 잘 동작하도록 만들기 어렵다.

UUID

유일성이 보장되는 ID를 만드는 간단한 방법이다.

UUID 는 컴퓨터 시스템에 저장되는 정보를 식별하기 위한 128비트 수다.

충돌 가능성이 낮다. 또한 서버 간 조율 없이 독립적으로 생성 가능하다.


장점

  1. 만드는 방법이 단순하고 서버 사이 조율도 필요 없어 동기화 이슈가 없다.
  2. 각 서버가 ID를 만드는 구조라 규모 확장이 쉽다.

단점

  1. 128비트라서 길다.
  2. 시간순 정렬이 안 된다.
  3. 숫자가 아닌 다른 값이 포함될 수 있다.

티켓 서버

플리커는 분산 키본 키 (distributed primary key) 를 만들어 내기 위해 이 방법을 차용하였다.

하나의 티켓 서버에 여러 웹 서버가 붙는 방식으로 동작한다.
핵심은 auto_increment 기능을 갖춘 데이터베이스 서버 (티켓 서버)를 중앙 집중형으로 사용하는 것이다.



장점

  1. 유일성이 보장되고, 숫자로만 구성된 ID를 쉽게 만들 수 있다.
  2. 구현하기 쉽고, 중소 규모 애플리케이션에 적합하다.

단점

  1. 티켓 서버가 SPOF 가 된다. 만일 티켓 서버를 여러 대 만들게 되면 동기화 이슈가 발생할 것이다.


트위터 스노플레이크 접근법

ID 생성하기 전 생성해야 하는 ID 구조를 section 으로 분할해 보겠다.
img

  • 사인 (sign) 비트: 1비트 할당. 음/양수를 구별하는 데 쓰일 수 있음
  • 타임 스탬프: 41비트 할당. 기원 시각 (epoch) 이후로 몇밀리초가 경과했는지 나타낸다.
  • 데이터 센터 ID: 5비트 할당. 25=32개 데이터 센터를 지원한다.
  • 서버 ID: 5비트 할당. 데이터 센터 당 32개 서버 사용 가능
  • 일련 번호: 12비트 할당. 각 서버에서 ID 생성할 때 1씩 증가시킨다. 1밀리초 경과 시 0으로 초기화 된다.

상세 설계

데이터 센터 ID 와 서버 ID 는 시스템 시작 시 결정되며, 운영 중에는 바뀌지 않는다. ID 충돌이 일어나기 때문이다.

타임 스탬프나 일련 번호는 ID 생성기 돌고 있는 중에 만들어지는 값이다.

타임 스탬프

시간이 흐를 수록 큰 값을 가지므로 ID 는 시간 순으로 정렬할 수 있다.

41비트로 표현할 수 있는 최댓값은 241-1=2199023255551밀리초이다.

대략 69년에 해당한다. 기원 시각을 현재에 가깝게 맞춰 오버 플로 시점을 늦추었다.

따라서 69년 지날 경우 기원 시각을 변경하거나 ID 체계를 변경하면 된다.


마무리

추가 논의 할 수 있는 부분

  • 시계 동기화 (clock synchronization): 각 서버가 같은 시계를 사용한다고 생각했지만, 그러지 않을 수 있다.

    NTP(Network Time Protocol)로 해결할 수 있다.
  • 각 section 동기화: 동시성(concurrency)이 낮고 수명이 긴 애플리케이션이라면 일련번호 섹션을 줄이고 타임 스탬프를 늘리
    는 방식을 사용할 수도 있다.
  • 고가용성 (high availability): ID 생성기는 필수 불가결(mission critical) 컴포넌트 이므로 높은 가용성을 제공해야 한다.




8장 URL 단축기 설계

문제 설계 범위 설정

  1. URL 단축: 긴 URL 을 짧게 줄인다.
  2. URL redirection: 줄인 URL 로 HTTP 요청이 오면 원래 URL 로 연결
  3. 높은 가용성과 규모 확장성, 장애 감내 필요

개략적 추정

  • 쓰기 연산: 매일 1억개 단축 URL 생성
  • 초당 쓰기 연산: 1억/24/3600 = 1160
  • 읽기 연산: 읽기 연산과 쓰기 연산의 비율을 10:1이라고 가정 (읽기 연산 11,600회 발생)
  • URL 단축 서비스를 10년 운영한다 가정 시 1억x365x10 = 3650억 레코드 보관
  • 축약 전 URL 평균 길이는 100이라 가정
  • 10년 동안 36.5TB의 저장소가 필요

개략적 설계안 제시 및 동의 구하기

API 엔드포인트

클라이언트는 서버가 제공하는 API 엔드포인트를 이용하여 서버와 통신한다. 엔드포인트를 REST 스타일로 설계할 건데

URL 단축기는 두 개의 엔드포인트가 필요하다.

  1. URL 단축 엔드포인트: 클라이언트는 단축 URL 을 생성하고자 할 때 URL 을 인자로 실어 POST 요청 한다.


    POST/api/v1/data/shorten
  • 인자: {longUrl:longURLstring}
  • 반환: 단축 URL


    의 형태를 띤다.
  1. URL 리디렉션용 엔드포인트: 단축 URL 에 HTTP 요청이 오면 원래 URL 로 반환


    GET/api/v1/shortUrl
  • 반환: HTTP 리디렉션 목적지의 원래 URL

URL 리디렉션

img
단축 URL 을 받은 서버는 원래 URL 로 바꿔 301 응답 Location 헤더에 넣어 반환한다.

301과 302 응답의 차이가 있다

  • 301 Permanently Moved: 해당 URL 에 대한 HTTP 요청의 처리 책임이 영구적으로 Location 헤더에 반환된 URL 로 이전되었다는 응답이다.

    브라우저는 이 응답을 캐시한다. 추후 같은 URL 요청시 캐시된 원래 URL 로 요청을 보낸다.
  • 302 Found: 일시적으로 Location 헤더가 지정하는 URL 에 의해 처리한다는 응답이다.

    따라서 항상 URL 단축 서버에 먼저 보내지고 원래 URL 로 리디렉션 되야 한다.


서버 부하를 줄이고 싶다면 301을 사용하는 것이 좋다. 트래픽 분석을 하고 싶으면 302를 쓰는 것이

클릭 발생률, 발생 위치 추적 등에 좋다.

URL 리디렉션은 해시 테이블로 구현하는 것이 가장 직관적이다.

  • 원래 URL = hashTable.get(단축 URL)
  • 301 or 302 응답 Location 헤더에 원래 URL 넣은 후 전송

URL 단축

긴 URL 을 해시 값으로 대응 시킬 때의 단축 URL 형태가 www.tinyURL.com/{hashValue} 라고 가정한다면

다음 요구사항을 충족해야 한다.

  • 입력된 긴 URL 이 다른 값이면 해시 값도 달라야 한다.
  • 계산된 해시 값은 원래 입력으로 주어졌던 긴 URL 로 복원 가능해야 한다.

상세 설계

데이터 모델

해시 테이블에 저장하는 것은 메모리가 유한하고 비싸기에 실제 시스템에서는 지양한다.

대신, 관계형 데이터베이스에 <단축 URL, 원래 URL>의 순서쌍을 저장하는 것이 좋다.

해시 값 길이

단축 URL 값을 hashValue 라고 가정한다. hashValue 는 [0-9, a-z, A-Z]의 문자들로 62개를 사용할 수 있다.

hashValue 길이를 정하기 위해서는 62n >= 3650억 인 n의 최소 값을 찾아야 한다.
n=7 이면 3.5조를 만들 수 있으므로 hashValue 는 7로 가정한다.

해시 함수 구현으로는 '해시 후 충돌 감소'와 'base-62 변환' 방법이 있다.


해시 후 충돌 해소

CRC32, MD5, SHA-1로 줄일 수 있지만 여전히 7보다 길다.

해시 함수 해시 결과 (16진수)
CRC32 5cb54054
MD5 5a62509a84df9ee03fe1230b9df8b94e
SHA-1 0eeae7916c06853901d9ccbefbfcaf4de57ed85b



첫 번째 해결 방법은 앞의 7 글자만 이용하는 것이다. 그러나 충돌 확률이 높아지는 단점이 있다.

따라서 충돌 시에는 longURL 뒤에 사전에 정한 문자열을 추가하여 다시 해시 값에 붙인다.



충돌은 해소하지만 단축 URL 을 쓸 때, 한 번 이상의 질의로 인해 오버헤드가 크다.

블룸필터를 사용하면 성능을 높일 수 있다.

블룸 필터는 어떤 집합에 특정 원소가 있는지 검사하는 기술로, 확률론에 기초했고 공간 효율에 좋다.


base-62 변환

진법 변환 (base conversion)은 URL 단축기 구현할 때 흔히 사용하는 접근법이다.

62진법을 쓰는 이유는 hashValue에 사용하는 문자 개수가 62개라서 이다.



10진수 11157을 62진수로 변경했을 때, 2TX62이다.

비교
해시 후 충돌 해소 전략 base-62 변환
단축 URL 길이 고정 단축 URL 길이 가변적, ID 값 커지면 같이 길어짐
유일성 보장되는 ID 생성기 필요 x 유일성 보장 ID 생성기 필요
충돌 할 수 있기 때문에 해소 전략 필요 ID 유일성 보장 후 적용 가능 전략이라 충돌 존재 x
ID 로부터 단축 URL을 계산하는 방식이 x
=> 다음에 쓸 URL 알아내는 것 불가능
ID 가 1씩 증가하는 값이라 가정하면, 단축 URL 가늠 가능하기 때문에 보안상 문제 될 수 있음


URL 단축기 상세 설계

URL 단축기는 논리적으로 단순하고 기능적으로 언제나 동작하는 상태로 유지하는 것이 핵심이다.

  1. 입력으로 긴 URL 받음
  2. 데이터베이스에 해당 URL 있는지 검사
  3. 있으면 단축 URL 가져와 클라이언트에 반환
  4. 없으면 유일 ID 생성 (기본키)
  5. 62진법 변환 적용하여 단축 URL 만든다.
  6. ID, 단축 URL, 원래 URL로 새 데이터베이스 레코드 만둔 후 단축 URL 을 클라이언트에 전달
    ID 생성기는 단축 URL 을 만들 때 사용 되고, 전역적 유일성 (globally unique)이 보장돼야 한다.

URL 리디렉션 상세 설계

  1. 사용자가 단축 URL 클릭
  2. 로드밸런서가 발생한 요청을 웹서버에 전달
  3. 캐시에 있는 경우, 원래 URL 클라이언트에 반환
  4. 캐시에 없는 경우, 데이터베이스에서 꺼낸다. (데이터 베이스에 없다면 잘못된 단축 URL을 입력한 경우)
  5. URL을 캐시에 넣은 후 클라이언트에 반환

마무리

  • 처리율 제한장치 (rate limiter): 엄청 많은 양의 URL 요청이 들어올 경우를 감안한다.

    처리율 제한장치를 두면, IP 주소를 비롯한 필터링 규칙 등을 이용해 요청을 거를 수 있다.
  • 웹 서버의 규모 확장: 웹 계층은 무상태 계층이므로 웹 서버를 자유로이 증설, 삭제 가능하다.
  • 데이터베이스의 규모 확장: 데이터베이스를 다중화하거나 샤딩하여 규모 확장성을 달성 할 수 있다.
  • 데이터 분석 솔루션 (analytics): URL 단축기에 데이터 분석 솔루션을 통합해 두면 클릭 수나 시간대 클릭 통계 정보 등을 알 수 있다.
  • 가용성, 데이터 일관성, 안정성: 대규모 시스템 운영을 위한 필수 요소이다.



참조

  • Alex Xu, (2021). 가상 면접 사례로 배우는 대규모 시스템 설계 기초, 인사이트

5장 안정 해시 설계

수평적 규모 확장성을 위해는 요청 또는 데이터를 서버에 균등하게 나누는 것이 중요하다.



해시 키 재배치(Refresh) 문제

serverIndex = hash(key) % N(서버의 갯수)

해시 해시 % 4 (서버 인덱스)
key0 18358617 1
key1 26143584 0
key2 18131146 2
key3 35863496 0
key4 34085809 1
key5 27581703 3
key6 38164978 2
key7 22530351 3
총 4대의 서버를 사용한다고 가정했을 때 다음과 같은 서버 인덱스로 분산하게 된다.

이 방법은 서버 풀 크기가 고정되어 있을 때, 데이터 분포가 균등할 때 잘 동작한다.

그러나 서버 풀의 크기가 4에서 3으로 변경된다고 했을 때는 키 분포가 달라진다.

만약 server1이 죽으면 캐시 클라이언트가 데이터가 없는 엉뚱한 서버에 접속하게 된다.

이는 대규모 캐시 미스가 발생하게 될 것이다. 안정 해시는 이 문제를 해결할 수 있다.

안정 해시

안정 해시(Consistent hash)는 해시 테이블 크기가 조정될 때 평균적으로 k/n개의 키만 재배치
하는 해시기술이다. (k=키의 갯수, n=슬롯의 갯수)


반면, 전통적 해시 테이블은 슬롯의 수가 바뀌면 대부분의 키를 재배치한다.



s0~s3은 해시 서버이고, k0~k3은 해시 키다.
해시 함수는 "해시 키 재배치 문제"에 언급된 함수와 다르고, 나머지 연산을 차용하지 않는다고 가정한다. 캐시 키 또한 해시 링 어디에든 위치할 수 있다.
서버 조회는 시계 방향으로 이루어지는데, key0 경우, server0에 저장된다.
(key1->server1, key2->server2, key3->server3)

  • k0과 s0 사이에 s4를 추가한다고 가정할 때, k0만 s4에 재배치 하게 된다.
  • s1이 제거됐다고 가정했을 때, k1만 s2에 재배치 하게 된다.

기본 구현법의 두가지 문제

안정 해시 알고리즘은 서버와 키를 균등 분포 해시 함수를 사용하여 해시 링에 배치한다.

그리곤 키의 위치에서 시계 방향으로 탐색하여 만난 최초의 서버에 배치된다.


이는 '파티션 크기를 균등하게 유지하기 어렵다'는 문제점이 있다.

위의 그림에서 s1이 제거된다 했을때 s2의 파티션이 다른 파티션보다 매우 커지게 됨을 확인할 수 있다.



또한, '키의 균등 분포를 달성하기 어렵다.'는 문제점이 있다.


위의 그림에서 보면 s2에 키가 보관되는 것을 확인할 수 있다.

가상 노드, (replica)

가상 노드는 실제 노드 또는 서버를 가리키는 노드로, 하나의 서버는 여러 개의 가상 노드를 가질 수 있다.

img

가상 노드의 개수를 늘릴 수록 키의 분포는 균등해진다. 그러나 개수를 늘릴수록 데이터는 많아진다는 것을 명심하자.


재배치할 키 설정

server4 가 추가 되었을 때, s3 ~ s4 사이의 키들이 영향을 받게 된다.
img


마찬가지로 server 가 삭제됐을 때도, 해당 서버-1~해당 서버 (반시계 방향)의 키들이 영향을 받게 될 것이다.



요약

  • 서버가 추가, 삭제될 때 재배치 되는 키의 수가 최소화 된다.
  • 데이터가 균등하게 분포하므로 수평적 규모 확장성을 달성하기 쉽다.
  • 핫스팟 키 문제를 줄인다.




6장 키-값 저장소 설계

key-value store는 비 관계형 데이터베이스이다.


저장되는 값은 고유 식별자를 키로 가져야 하며, 이런 연결관계를 key-value쌍이라고 지칭한다.


성능상의 이유로 키는 짧을 수록 좋다.
value는 리스트, 객체 등 무엇이 오든 상관하지 않는다.


다이나모, memached, 레디스 등이 있다.

단일 서버 키-값 저장소

가장 직관적인 방법은 해시 테이블로 저장한다. 해시 테이블은 빠른 속도를 보장하지만 모든 데이터를 메모리 안에
저장하는 것이 불가능할 수 있다.
해결책으로는




-> 데이터 압축(compression)

-> 자주 쓰는 데이터만 메모리에 두고 디스크에 저장
이 있다.


그러나 한 대의 서버만으로 부족한 때가 오기 때문에 분산 키-값 저장소가 필요하다.

분산 키-값 저장소(distributed key-value story)

분산 해시 테이블이라고 불린다. 분산 시스템을 설계할 때는 CAP 정리(Consistency, Availability, Partition Tolerance theorem)를 이해하고 있어야 한다.

CAP 정리

CAP 정리는 데이터 일관성(Consistency), 가용성(Availability), 파티션 감내(Partition tolerance)라는 세 가지 요구사항을 동시에 만족하지 못한다는 정리다.

  • 데이터 일관성: 클라이언트는 어떤 노드에 접속하든 같은 데이터를 본다.
  • 가용성: 일부 노드에 장애 발생하더라도 항상 응답 가능해야 한다.
  • 파티션 감내: 파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미한다.

    파티션 감내는 네트워크 파티션이 생겨도 시스템은 동작해야 한다는 뜻이다.

CAP 정리는 두 가지 조건을 충족하려면 하나는 반드시 희생되어야 한다는 것을 의미한다.
  • CP 시스템: 가용성을 희생한다.
  • AP 시스템: 데이터 일관성을 희생한다.
  • CA 시스템: 존재하지 않는다. 분산 시스템은 반드시 파티션 문제를 감내할 수 있도록 설계!

  1. CP 시스템의 경우: 데이터 불일치를 피하기 위해 n1, n2에 대한 쓰기 연산을 중단해야 한다.

-> 가용성이 깨진다.

-> 은행권 시스템이 주로 차용
2. AP 시스템의 경우: 예전 데이터를 반환할 위험을 감수하고 계속 읽기 연산을 허용한다.
n1, n2 역시
쓰기 연산을 허용할 것이고 파티션 문제가 해결된 뒤에 새 데이터 n3을 전송한다.

시스템 컴포넌트

널리 사용되는 세 가지 키-값 저장소 (다이나모, 카산드라, 빅테이블) 의 사례를 참고한다.

데이터 파티션

대규모 애플리케이션의 경우 데이터를 저장하는 방식의 가장 단순한 해결책은 작은 파티션으로 분할하고 여러대의 서버에 저장하는 것이다.
데이터 파티션을 나눌 때는 다음의 두 문제를 따져야 한다.

  • 데이터를 여러 서버테 고르게 분산했는가
  • 노드가 추가되거나 삭제될 때 데이터의 이동을 최소화할 수 있는가

안정 해시는 두 문제를 해결할 수 있다.
또한, 안정 해시를 사용하여 데이터 파티션을 하게되면 다음과 같은 장점이 있다.

  • 규모 확장 자동화(automatic scaling): 시스템 부하에 따라 서버가 자동으로 추가, 삭제 된다.
  • 다양성(heterogeneity): 각 서버 용량에 맞게 가상 노드 수를 조정할 수 있다. 즉, 고성능 서버는 가상 노드를 더 많이 가질 수 있다.

데이터 다중화

가용성과 안정성을 확보하기 위해 데이터를 N(튜닝한 값)개 서버에 비동기적으로 다중화(replica)할 필요가 있다.


그런데 가상 노드를 사용하면 선택한 N개의 노드와 대응할 실제 물리 서버의 개수가 N보다 작아질 수 있다.


이 문제를 피하려면 물리 서버를 중복 선택하지 않도록 해야한다.

같은 데이터 센터에 속한 노드는 정전, 네트워크 이슈, 자연재해 등의 문제를 동시에 겪을 수 있다.

따라서 안정성을 담보하기 위해 데이터의 사본은 다른 센터의 서버에 보관하고 센터들은 고속 네트워크로 연결한다.


데이터 일관성

여러 노드에 다중화된 데이터는 적절히 동기화가 되어야 한다. 정족수 합의(Quorum Consensus) 프로토콜을 사용하면

읽기/쓰기 연산 모두에 일관성을 보장할 수 있다.

<정의>

N=사본 개수
W=쓰기 연산에 대한 정족수. 쓰기 연산이 성공한 것으로 간주되려면 적어도 W개의 서버로 부터 쓰기 연산이 성공했다는 응답을 받아야 함
R=읽기 연산에 대한 정족수. 읽기 연산이 성공한 것으로 간주되려면 적어도 R개의 서버로 부터 응답을 받아야 함


img

N=3인 경우에 대한 그림을 예로 설명하자면,
W=1은 쓰기 연산이 성공했다고 판단하기에 coordinator는 최소 한대 서버가 쓰기 성공 응답을 보냈다는 뜻이다.


coordinator는 클라이언트와 노드 사이에서 proxy 역할을 한다.


W, R, N을 정하는 것은 응답 지연과 데이터 일관성 사이의 타협점을 찾는 전형적인 과정이다.
W, R의 값이 1보다 클 경우 데이터 일관성 수준은 높아지나 응답 속도는 저하될 것이다.

  • R=1, W=N: 빠른 읽기 연산에 최적화된 시스템
  • W=1, R=N: 빠른 쓰기 연산에 최적화된 시스템
  • W+R>N: 강한 일관성이 보장됨 (보통 N=3, W=R=2)
  • W+R<=N: 강한 일관성이 보장되지 않음


일관성 모델
  • strong consistency: 모든 읽기 연산은 가장 최근의 갱신 결과를 반환
  • weak consistency: 읽기 연산은 가장 최근에 갱신된 결과를 반환하지 못할 수도 있다.
  • eventual consistency: 약한 일관성의 한 형태로, 갱신 결과가 모든 사본에 동기화되는 모델이다.

강한 일관성을 위해 일반적으로는 모든 사본에 현재 쓰기 연산 결과가 반영될 때까지 R/W를 금지하는 것이다. 이 방법은 고가용성 시스템에는 적합하지 않는데 새로운 요청이 중단되기 때문이다.
다이나모나 카산드라 같은 저장소는 결과적 일관성 모델을 택한다.

이럴 경우 쓰기 연산이 병렬적으로 발생시 시스템에 저장도니 값의 일관성이 깨질 수 있다. 이는 클라이언트가 데이터 버전정보를 활용하여
해결할 수 있다.


비일관성 해소 기법: 데이터 버저닝

데이터 다중화시 가용성은 높아지나, 사본 간 일관성이 깨질 가능성도 높아진다.


버저닝과 벡터 시계를 통해 해결할 수 있다.


버저닝은 데이터를 변경할 때 새로운 버전을 만드는 것이다. 따라서 각 버전의 데이터는 변경 불가능(immutable) 하다.



  • 서버1에서 "name"의 값 "john"->"jhonSanFran"으로 변경한다.
  • 서버2에서 "name"의 값 "john"->"jhonNewYork"으로 변경한다.
  • 두 연산은 동시에 이루어진다.
  • 충돌 값을 V1, V2라고 한다.


변경이 이루어 지면 원래 값은 무시할 수 있다. 두 버전의 충돌을 해소하기 위해 버저닝 시스템이 필요하다.

벡터 시계는 두 버전의 충돌을 해소하기 위해 보편적으로 사용되는 해결 방법이다.

[서버, 버전]의 순서쌍을 데이터에 매달아서 사용된다.




백터 시계는 버전의 앞 뒤를 쉽게 판단할 수 있다.

어떤 버전 X와 Y사이 충돌이 있는지 알아보려면 Y 벡터 시계 구성 요소 가운데 X의 벡터 시계 동일 서버 구성 요소를 비교하면 된다.


그러나 벡터 시계는 두 가지 단점이 있다.

  1. 충돌 감지 및 해소 로직이 클라이언트에 들어가기 때문에 클라이언트 구현이 복잡해 진다.
  2. [서버:버전]의 순서쌍 개수가 빨리 늘어난다.

    따라서 임계치(threshold)를 설정하고 오래된 순서쌍을 벡터 시계에서 제거해야 한다.
    ~~그러나, 버전 간의 선후 관계를 정확히 할수 없어 충돌 해소 과정의 효율성이 낮아진다는 단점이 있으나 실제 서비스에서 아마존에 의하면 그런 문제는 발생하지 않았다고 한다.~~

장애 감지

보통 두대 이상의 서버거 장애를 보고해야 실제로 장애가 발생했다고 간주한다.


멀티캐스팅 채널을 구축하여 서버 장애를 손쉽게 감지할 수 있다. 하지만 서버가 많을 땐 비효율적이다.


가십 프로토콜 같은 분산형 장애 감지(decentralized failure detection) 솔루션이 효율적이다.

  • 각 노드는 멤버십 목록을 유지한다. (멤버 ID, 박동 카운터)의 목록이다.
  • 각 노드는 주기적으로 박동 카운터를 증가시킨다.
  • 각 노드는 무작위로 선정한 노드에게 주기적으로 자기 박동 카운터 목록을 보낸다.
  • 받은 노드는 멤버십 목록을 최신 값으로 갱신한다.
  • 어떤 멤버의 값이 갱신되지 않으면 해당 멤버는 offline(장애)상태인 것으로 간주한다.


일시적 장애 처리

가십 프로토콜로 장애를 감지한 시스템은 가용성을 보장하기 위한 조치를 취해야 한다.

  • strict quorum 을 쓴다면 읽기와 쓰기 연산을 금지해야 한다.
  • sloppy quorum 을 쓴다면 strict 보다 완화하여 가용성을 높인다.

sloppy quorum 은 쓰기 연산을 수행할 W개의 서버와 읽기 연산을 수행할 R개의 서버를 해시 링에서 고른다.
단, 장애 상태의 서버는 무시한다.

장애 서버의 경우 다른 서버가 임시로 맡아 처리한다. 변경 사항의 경우 장애 서버의 복구 시에 일괄 반영하며

데이터 일관성을 보존한다.
임시로 맡는 서버의 경우 쓰기 연산 처리에 대한 hint 를 남겨둔다.

이런 장애 처리 방안을 단서 후 임시 위탁(hinted handoff) 기법이라 부른다.


영구 장애 처리

영구적인 노드 장애 상태 처리 방안은 반-엔트로피(anti-entropy) 프로토콜을 구현하여 사본들을 동기화 해야한다.
반-엔트로피 프로토콜은 사본을 비교하여 최신으로 갱신하는 과정을 포함한다. 비교를 할 때는 상태를 탐지하고 데이터 양을 줄이기 위해 머클트리 를 사용한다.

머클트리를 이용한 영구 장애 처리의 예시(그림 6-13~6-16)를 보면, 루트 노드의 해시 값 비교로 시작한다.


머클 트리를 사용 시, 동기화 해야 하는 데이터의 양은 실제 차이 크기에 비례하며 두 서버의 보관한 데이터와는 무관하다.


하지만 실제 시스템의 경우 버킷 하나의 크기는 상당하는 것을 명심해야 한다.


데이터 센터 장애 처리

  • 클라이언트는 get(key), put(key, value) 통신한다.
  • coordinator는 클라이언트에게 키-값 저장소에 대한 프락시 역할을 한다.
  • 노드는 안정 해시의 해시 링 위에 분포한다.
  • 노드를 자동으로 추가, 삭제 할 수 있도록, 시스템은 완전히 decentralized.
  • 데이터는 여러 노드에 다중화 된다.
  • 모든 노드에게 책임이 있으므로 SPOF 는 존재하지 않는다.


쓰기 경로

카산드라에서 쓰기 요청이 특정 노드에게 전달되는 과정

  1. 쓰기 요청이 커밋 로그에 기록된다.
  2. 데이터가 메모리 캐시에 기록된다.
  3. 메모리 캐시가 가득 차거나, 임계치에 도다하면 데이터는 디스크의 SSTable에 기록된다.

읽기 경로

데이터가 메모리 캐시에 있는지 찾아본다.

있는 경우, 반환한다.

없는 경우, 디스크에서 가져와야 한다. 이 때, 어느 SSTable에 있는지 효율적으로 찾아보기 위해 블룸 필터를 사용한다.

  1. 데이터가 메모리에 있는지 조회한다.
  2. 없을 경우, 블룸 필터를 검사한다.
  3. 어떤 SSTable 에 키가 보관되어 있는지 알아낸다.
  4. SSTable 에서 데이터를 가져온다.
  5. 해당 데이터를 클라이언트에 반환한다.



참조

  • Alex Xu, (2021). 가상 면접 사례로 배우는 대규모 시스템 설계 기초, 인사이트

2장 개략적인 규모 추정

개략적인 규모 추정은 2의 제곱수latency값, 가용성에 관계돈 수치를 잘 이해하는 것이 중요하다.

2의 제곱수

근사치 축약형 2의 x 제곱
천(thousand) 1KB 210
백만(milion) 1MB 220
10억(bilion) 1GB 230
1조(trillion) 1TB 240
1000조(quadrilliion) 1PB 250

모든 프로그래머가 알아야 하는 latency 값

연산명 시간
L1 캐시 참조 0.5ns
분기 예측 오류 5ns
L2 캐시 참조 7ns
뮤텍스 락/언락 100ns
주 메모리 참조 100ns
Zippy로 1KB 압축 10,000ns = 10μs
1Gbps 네트워크로 2KB 전송 20,000ns = 20μs
메모리에서 1MB 순차적으로 read 250,000ns = 250μs
같은 데이터 센터 내에서의 메시지 왕복 지연시간 500,000ns = 500μs
디스크 탐색 10,000,000ns = 10ms
네트워크에서 1MB 순차적으로 read 10,000,000ns = 10ms
디스크에서 1MB 순차적으로 read 30,000,000ns = 30ms
한 패킷의 CA로부터 네덜란드까지의 왕복 latency 150,000,000ns = 150ms

결론

  • 메모리는 빠르지만 디스크는 느리다.
  • 디스크 탐색(seek)은 피하자.
  • 단순 압축 알고리즘은 빠르다
  • 인터넷 전송하기 전 압축할 것
  • 데이터 센터는 여러 지역에 분산되어 있고 센터들 간에 데이터를 주고받는 데는 시간이 걸린다.

요약

  • 근사치를 활용하여 계산하자.
  • 가정을 적어두라.
  • 단위를 붙여라.



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

네트워크 시스템에서 rate limiter는 클라이언트 혹은 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치다.
HTTP의 경우 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한한다.
서버가 특정 임계치까지 클라이언트 요청을 허용하는 것이다.

처리율 제한 장치는 다음과 같은 특징이 있다.

  • DoS 공격으로 인한 resource starvation 방지한다.
  • 비용 절감
  • 서버 과부하를 막는다.

처리율 제한 장치는 클라이언트 요청을 쉽게 위변조 할수 있으므로 서버 측에 두는 것이 좋다.
혹은 처리율 제한 미들웨어를 만들어 통제하는 방법도 있다.

처리율 제한 알고리즘

토큰 버킷 알고리즘

폭 넓게 이용되는 알고리즘이다. 간단하고 세간의 이해도가 높으며 인터넷 기업들이 보편적으로 사용하고 있다.

  • 토큰 버킷은 지정된 용량을 갖는 컨테이너다. 버킷에는 사전 설정된 양의 토큰이 채워진다.
  • 토큰 공급기(refiller)는 버킷을 추가하고, refiller에 버킷이 가득 차면 overflow.
  • 각 요청은 처리될 떄마다 토큰이 소비된다.
  • 토큰이 있는 경우, 토큰 하나를 꺼낸 후 요청을 시스템에 전달한다.
  • 토큰이 없는 경우, 해당 요청은 dropped

img

장점

  • 구현이 쉽다.
  • 메모리 사용 측면에서도 효율적이다.
  • burst of traffic 처리 가능. (단, 버킷에 남은 토큰이 있을 경우)

단점

  • 버킷 크기*와 *토큰 공급률(refill rate) 2개의 파라미터를 가지고 있는데, 적절하게 튜닝하는 것이 까다롭다.

누출 버킷 알고리즘 (leaky bucket)

토큰 버킷 알고리즘과 비슷하지만 요청 처리율(refill rate)이 고정되어있다는 점이 다르다.
보통 FIFO 큐로 구현한다.

  • 요청이 오면, 큐가 차있는지 확인한다.
  • 빈 자리가 있을 경우에 큐에 새 요청을 추가한다.
  • 큐가 가득 차 있는 경우에는 새 요청을 버린다.
  • 지정된 시간마다 큐에서 요청을 꺼낸다.

누출 버킷 알고리즘은 버킷 크기(큐 사이즈)와 처리율(rate filler) 2개의 파라미터를 사용한다.


버킷 크기(큐) 에는 처리할 항목들이,
처리율에는 지정된 시간 당 몇 개의 항목을 처리할 지 정하며 초 단위로 표현한다.



img

장점

  • 큐의 크기가 제한되어 있어서 메모리 사용량 측면에서 효율적
  • fixed rate를 가지고 있어서 안정적 출력이 필요한 경우가 적합하다.

단점

  • 위의 그림처럼, 단시간 트래픽이 몰릴 경우, 오랜 요청이 쌓여 최신 요청이 버려진다.
  • 토큰 버킷과 마찬가지로 2개의 파라미터를 올바르게 튜닝하기 어렵다.


고정 윈도 카운터 알고리즘 (Fixed Window Counter)

고정 윈도 카운터 알고리즘은 타임라인fixed window로 나누고 각 윈도우마다 카운터를 붙인다.
요청이 접수될 때마다 카운터는 1씩 증가하며 임계치(threshold)에 도달하면 윈도우가 새로 열릴떄 까지 버려진다.

img

해당 알고리즘의 문제윈도 경계 부근에 트래픽이 집중될 경우 할당량보다 더 많은 요청이 처리될 수 있다.

장점

  • 메모리 효율에 좋다.
  • 이해하기 쉽다.
  • 윈도가 닫히는 시점에 카운터를 초기화 하는 방식으로 트래픽 패턴 처리에 용이

단점

  • 윈도 경계 부근에서 트래픽이 몰릴 경우, 시스템 처리 한도보다 요청을 많이 처리하게 된다.


이동 윈도 로깅 알고리즘 (Sliding Window Logging)

고정 윈도 카운터 알고리즘의 단점을 해결할 수 있는 알고리즘이다.

  • 요청의 타임 스탬프를 추적한다.
  • 요청이 오면, 만료된 타임 스탬프는 제거한다.
  • 새 요청의 타임 스탬프를 로그에 추가한다.
  • 로그의 크기 $\leq$ 허용치 : 요청을 시스템에 전달한다.
  • 로그의 크기 > 허용치 : 처리 거부

장점

  • 처리율 제한 메커니즘이 정교하다.

단점

  • 거부된 요청의 타임 스탬프도 갖고 있어 다량의 메모리를 사용한다.


이동 윈도 카운터 알고리즘 (Sliding Window Counter)

고정 윈도 카운터 + 이동 윈도 로깅 알고리즘을 결합한 것이다.

장점

  • 이전 시간대의 평균 처리율에 따라 현재 윈도 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 대응 가능
  • 메모리 효율이 좋다.


단점

  • 직전 시간대의 요청이 균등하다는 가정 하에 계산하기 때문에 느슨하다.
    그러나 버려진 요청은 극히 드물기 때문에 심각한 문제는 아니다.


개략적인 아키텍처

처리율 제한 알고리즘의 아이디어는 단순하다. 요청을 추적할 수 있는 카운터를 추적 대상별로 둔다.
그리고 카운터 값을 넘어 갔을 때 도착한 요청은 거부되는 것이다.


카운터는 캐시에 저장하는 것이 바람직하다. 만료 정책을 지원하기 떄문이다.


처리율 제한 규칙

보통 configuration 파일 형태로 디스크에 저장된다.

처리율한도 초과 트래픽의 처리

HTTP 응답 헤더에는 요청이 처리율 제한에 걸리고 있는가(throttle), 어떻게 감지하는가, 처리율 제한 걸리기 까지 보낼 수 있는 요청의 수 등을 알 수 있다.


분산 환경에서의 처리율 제한 장치의 구현

경쟁 조건 (Race Condition)

병행성이 심한 환경에서는 경쟁 조건이 나타날 수 있다.
서로 다른 요청을 처리하는 스레드에서 병렬로 counter를 증가할 경우이다.


두 병렬 스레드는 요청을 처리할 때, counter를 공유하지 않으므로 증가하지 않고 같은 값을 나타낼 수 있다.


이는 락(Lock)으로 해결할 수 있다. 그러나, 시스템 성능을 저하시키기 때문에 루아 스크립트정렬 집합(레디스 자료구조)
를 사용하여 해결할 수 있다.



동기화 이휴

처리율 제한 장치를 여러대 둘 때 동기화를 잘 해야할 것이다. 고정 세션으로 해결할 수 있지만 확장성에 좋지 않기 때문이다. 따라서 레디스나 중앙 집중형 데이터 저장소를 쓰는 것이 좋다.


성능 최적화

  • 데이터 센터 외의 edge server를 두어 지연시간을 줄인다.
  • 제한 장치 간에 데이터 동기화 시 최종 일관성 모델(Eventual Consistency Model)을 사용한다.



모니터링

처리율 제한 알고리즘이 효과적인지, 처리율 제한 규칙이 효과적인지 파악한다.


요약

  • 토큰 버킷, 누출 버킷, 고정 윈도 카운터, 이동 윈도 로그, 이동 윈도 카운터 알고리즘
  • limit을 fixed하게 하는가, flexible하게 하는가에 대한 hard, soft 처리율 제한
  • 처리율 제한을 회피하는 방법 :
    • 클라이언트 캐시를 사용하여 API 호출 횟수를 줄인다.
    • limit을 파악한다.
    • 에러 처리를 통해 예외를 복구시킬 수 있도록 한다.
    • retry 로직을 구현할 때는 충분한 back-off 시간을 둔다.



링크

단일 서버

웹 앱, 데이터베이스, 캐시 등이 서버 한 대에서 실행된다.

  1. 사용자는 도메인 이름으로 DNS를 통해 IP주소로 변환한다.
  2. DNS 조회로 IP주소가 반환된다.
  3. IP주소로 HTTP 요청을 전달한다.
  4. HTML이나 JSON 형태로 응답한다.

데이터 베이스

사용자가 늘게 되면 서버를 여러대 두어야 한다. 하나는 웹/모바일 트래픽 처리용, 하나는 데티어베이스용이다.

어떤 데이터베이스를 사용할 것인가?

관계형 데이터베이스

- MySQL, 오라클, PostgreSQL 등   
- 자료를 테이블과 열, 칼럼으로 표현   
- 조인할 수 있음   

비 관계형 데이터베이스

- Cassandra, HBase, Amazon DynamoDB 등   
- 키-값 저장소, 그래프 저장소, 칼럼 저장소, 문서 저장소로 나뉨   
- 조인 지원 안 함   
- 낮은 응답 latency   
- 데이터가 비정형이라서 관계형 데이터가 아님   
- 아주 많은 양의 데이터 저장 

수직적 규모 확장 vs 수평적 규모 확장

수직적 규모 확장(scale up)은 고사양 자원을 추가하는 행위를 말한다. 트래픽 양이 적을때 좋고 단순하다.
그러나 한계가 있으며 자동 복구(failover)나 다중화(redundancy) 방안이 없다. 서버에 장애가 발생할 시에 웹 사이트와 앱은 중단된다.

반면에, 수평적 규모 확장(scale out)은 서버를 추가하여 성능을 개선한다. 대규모 애플리케이션 지원시에는 이 방법이 좋다.

사용자가 웹 서버로 바로 연결 시에 서버가 다운되면 엡사이트에 접속할 수 없다. 또한 사용자가 집중될 때 응답 속도가 느려지거나 접속이 불가할 수 있을때, 로드밸런서를 도입하는 것이 최선이다.

로드밸런서

트래픽 부하를 고르게 분산하는 역할을 한다. http 통신에서 DNS을 통해 IP주소를 직접 접근하지 않고 로드밸런서를 중간에 놓는다. 웹 서버는 클라이언트의 접속을 직접 처리하지 않는다. 사용자는 로드밸런서를 통해 공개 IP 주소로 접근하고, 서버 간 통신에는 사설 IP 주소가 이용된다.

부하 분산 집합에 웹 서버를 추가하면 자동 복구(failover)문제와 가용성이 향상된다.

데이터베이스 다중화

서버 사이에 주(master)-부(slave) 관계를 설정하고 쓰기연산은 마스터에서 지원한다. 이를 복사하여 부 데이터베이스는 읽기 연산을 할 수 있다.

데이터베이스를 다중화하게 되면 성능이 좋아진다.

  1. 성능 : 병렬 처리 할 수 있는 쿼리가 많아지기 때문이다.
  2. 안정성(realiability): 데이터베이스 중 일부가 파괴되어도 데이터는 보존될 것이다.
  3. 가용성(availability): 하나의 데이터베이스가 장애가 생겨도 계속 서비스할 수 있다.

데이터베이스 서버 가운데 하나가 다운되도 즉시 대체될 수 있게 설계되어 있다.

캐시

응답 시간을 개선할 수 있는 저장소이다. 값이 비싼 연산 결과나 자주 참조되는 데이터를 메모리에 두고 빨리 처리할 수 있게 한다. 데이터가 잠시 보관되는 곳으로 성능이 개선되고 데이터베이스의 부하를 줄이고, 캐시 계층의 규모를 독립적으로 확장시킬 수 있다.

캐시 사용시 유의할 점

  1. 어떤 상황에 쓸 것인지?: 데이터 갱신율이 적고 참조가 자주 일어나는 데이터
  2. 어떤 데이터를 쓸지?: 휘발성 메모리이므로 중요 데이터는 지속적인 저장소에 넣어야 한다.
  3. 어떻게 만료할지?: 정책을 미리 고민할 필요가 있다.
  4. 일관성(Consistency): 단일 트랜잭션으로 처리하지 않을 경우 일관성이 깨질 수 있다. 시스템 확장시 일관성 유지가 문제가 된다.
  5. 장애 대처: 캐시 서버를 한 대만 둘 경우 SPOF가 될 수 있다. 따라서 여러 지역에 캐시 서버를 분산시켜야 한다.
  6. 캐시 메모리 크기: 너무 작으면 eviction으로 캐시 성능 저하가 된다. 캐시 메모리 과할당(overprovision)으로 해결할 수 있다.
  7. eviction 정책은 무엇인가?: 캐시 용량이 찰 경우 데이터를 내보내야 하는데 LRU, LFU, FIFO 등이 있다.

CDN

CDN은 정적 콘텐츠를 전송하는 데 쓰인다. 지리적으로 분산된 서버 네트워크이다.

CDN 사용 시 고려해야 할 사항

  1. 비용: CDN은 3rd party에 의해 운영되어 데이터 전송 양에 따라 요금을 부과한다.
  2. 만료 시한 설정: time-sensitive 콘텐츠를 판단하여 만료 시점을 설정한다.
  3. CDN 장애 대처 방안; CDN이 죽었을 경우 웹 사이트와 애플리케이션의 대처 방안을 세운다.
  4. 콘텐츠 무효화(invalidation): CDN 서비스에서 제공하는 API, 오브젝트 버저닝을 통해 바꾼다.

무상태(Stateless) 계층

웹 계층을 수평적으로 확장한다.상태 정보를 저장소에 보관하게 된다.

상태 정보 의존적인 아키텍처

상태 정보를 보관하는 서버는 클라이언트 정보를 요청 사이에 공유하도록 하지만, 무상태 서버는 그런 정보가 없다. 그렇기에 HTTP 요청 받을 시 어떤 웹 서버로 전송될 수 있고, 공유 저장소에서 정보를 가져와서 확인할 수 있다. 단순하고 안정적이며 규모확장이 좋은 장점이 있다.

그림 1-14에서 세션 데이터를 지속성 데이터 보관서에 저장하게 설계되었다.가용성을 높여 전 세계에서 웹 사이트를 쾌적하게 사용하기 위해서는 데이터 센터의 역할이 중요하다.

데이터 센터

장애가 없는 상태에서 사용자는 웹 사이트에 접근 시 geo-routing을 통해 가까운 데이터 센터에서 서버의 라우팅이 된다.

다중 데이터센터 아키텍처 설계시 고려해야 할 사항

  1. 트래픽 우회:올바른 데이터 센터로 트래픽을 보내야 한다.
  2. 데이터 동기화: 데이터 센터마다 별도의 데이터베이스를 사용하면 장애가 자동 복구(failover)되어 트래픽이 다른 데이터베이스로 우회더라도 해당 데이터 센터에 데이터가 없을 수 있다.
  3. deployment: 웹 사이트 또는 애플리케이션을 여러 위치에서 테스트 해보는 것이 중요하다.메시지 큐
    1. 로그: 에러 로그를 모니터링 하는 것이 중요하다. 오류를 쉽게 찾을 수 있기 때문이다. 서버 단위로 모니터링할 수 있고 3rd party에서 제공하는 단일 서비스로 모아주는 도구를 활용하는 방안도 있다.
    2. 메트릭: 사업 현황에 관한 정보를 얻을 수 있고 시스템의 현재 상태를 쉽게 파악할 수 있다.
    • 호스트 단위 메트릭: CPU, Memory, Disk I/O에 관한 메트릭
    • 핵심 비지니스 메트릭: 일별 능동 사용자, 수익, 재방문
    • 자동화: 생산성을 높이기 위해 활용해야 한다. 이 외에도 빌드, 테스트, 배포 등 절차를 자동화할 수 있어 개발 생산성을 향상시킬 수 있다.
  4. 로그, 메트릭 그리고 자동화
  5. 메시지의 무손실(durability)을 보장하는 비동기 통신 지원 컴포넌트이다.메시지의 버퍼역할을 하며, 비동기적으로 전송한다.
    생산자/발행자가 메시지를 만들어 메시지 큐에 발행(publish)하면, 큐에 연결된 소비자/구독자가 수행한다.
    서버간의 결합을 느슨하게 하고 규모 확장성이 보장되는 곳에 구성하기 좋다.
  6. 시스템의 규모를 확장하기 위해서는 컴포넌트를 분리하여 독립적으로 확장할 수 있어야 한다. 메시지 큐가 해결 방안일 수 있다.

데이터베이스의 규모 확장

수직적 확장

스케일 업이라고 부른다. 고성능의 자원(CPU, RAM 등)을 증설하는 방법이다.

-> 사용자가 늘면 감당하기 어렵다.
-> SPOF 위험성
-> 비용

수평적 확장

샤딩(sharding)이라고 부른다. 더 많은 서버를 추가하여 성능을 향상시킨다.
모든 shard는 같은 스키마를 쓰지만 샤드에 보관되는 데이터는 중복이 없다.
샤딩 전략을 구현할 때 샤딩 키를 정하는 것이 가장 중점이다. 파티션 키라고도 불린다.
데이터가 어떻게 분산될 지 정하는 칼럼으로 구성되므로 샤딩 키를 정할 때는 데이터를 고르게 분할할 수 있도록 해야 한다.

샤딩을 도입할 경우 직면할 문제도 있다.

  1. 데이터의 재 샤딩(resharding): (1) 데이터가 많아졌을 때,
                                (2) 샤드 간 데이터 분포가 균등하지 않아 공간 소모가 빠를 때 (shard exhaustion)   
                                   샤드 키를 계산하는 함수를 변경하고 데이터를 재배치 해야 한다. **안정 해시**로 해결할 수 있다.   
  2. Celebrity 문제: 핫스팟 키 문제라고도 한다. 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제다.
                 각 유명인사에 샤드 하나를 더 할당 하거나 쪼개는 방식으로 해결한다.   
  3. join and de-normalization: 샤드를 쪼개면 데이터를 조인하기 어려워 진다.
                            이는 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행할 수 있도록 해결한다.

요약

  • Stateless 계층으로
  • 모든 계층에 다중화
  • 가능한 많은 데이터를 캐시할 것
  • 데이터 센터를 많이 분산시킬 것
  • CDN을 통해 서비스 제공할 것
  • 데이터 계층은 샤딩을 통해 규모 확장할 것
  • 각 계층은 독립적 서비스로 분할할 것
  • 시스템을 지속적으로 모니터링하고, 자동화 도구를 활용할 것

+ Recent posts