본문 바로가기
아키텍처 | 설계

콘웨이의 법칙과 시스템 설계 / 대규모 마이크로 서비스 - 마이크로 서비스 아키텍처 11장

by 자유코딩 2020. 6. 8.
시스템 구조는 설계하는 조직의 커뮤니케이션 구조를 닮게 된다.
모든 시스템은 그 조직의 의사소통 구조와 동일하게 만들어진다.
조직의 구조가 시스템 아키텍처 설계에 영향을 준다.

책에서는 팀의 분리 상태를 들어서 설명하고 있다.

연관성이 높은 조직 A와 B가 밀접하게 소통하지 못하고 원격으로 이메일만 조금 주고 받는다면 어떨까

이렇게 개발을 할때 정말 좋지 않은 상황은 그대로 커뮤니케이션 되지 않은채 서비스가 방치되는 것이다.

 

컨텍스트를 잘 나누고 관련 있는 조직끼리 잘 소통하게 해야한다.

 

예를 들어서 고객의 주문 서비스와 상담 서비스를 아예 다른 팀에서 개발 한다고 해보자.

두 조직은 분리되어 있다.

커뮤니케이션 없이 코드베이스도 완전히 분리되게 된다.

이렇게 개발하다 만약 상담 서비스 개발하던 곳에서 인원 변동이 생겼다고 해보자.

주문 서비스 쪽에서는 상담 서비스를 떠안고 개발을 시작한다.

 

이 경우 분리된 두 조직처럼 서비스도 완전히 분리된 모습을 띄게 된다.

 

책에서는 유명한 명제도 하나 소개한다.

 

1개의 컴파일러를 만들기 위해서 4개 팀이 조직되면 4단계로 빌드하는 컴파일러가 나온다.

 

콘웨이의 법칙에서 중요한 부분은 이렇다.

시스템을 조직 구조와 다르게 설계하면 위험하다.

 

대규모 마이크로 서비스

1. 장애는 어디서든 발생한다.

- 대규모 상황에서는 정말 좋은 하드웨어로 구성해도 고장을 피할 수 없다.

- 어떤 장애든 결국 발생한다.

- 서버는 고장날 수 있고 고장 날 것이다.

 

2. 얼마나 많아야 너무 많은 건가

- MSA의 응답속도가 얼마나 빨라야 할까

책에서는 "초당 200개 동시 접속을 처리 할 때 응답시간의 90%가 2초 미만을 유지 할 것으로 예상된다."

이런 식으로 말 할 수 있는 기준을 가져야 한다고 한다.

초당 200개 동시접속일때 2초가 넘으면 느린 서버이다.

 

- 가용성

서비스가 얼마나 다운될 지 예상하는가

연중무휴 서비스로 간주 할 수 있는지를 말한다.

 

- 데이터의 내구성

얼마나 많은 데이터 손실이 허용 가능한가.

사용자 세션 로그 보관 기간은 어느정도로 하는가.

예를 들어서 일반적인 로그는 1년 또는 공간 절약을 위해서 그 이하로 보관 할 수 있다.

하지만 금융 거래기록은 수년동안 보관해야 할 수도 있다.

 

 

3.기능분해

- 회복력 있는 시스템에서 중요한 것중 하나는 기능을 안전하게 분해하는 것이다.

주문 서비스가 안된다고 해서 카트도 안 나온다던가 하면 안된다.

MSA 1개가 다운 되었다고 전체 웹페이지를 표시 할 수 없다면 회복력이 떨어지는 시스템이다.

각 서비스는 장애가 생길 수 있다.

서비스의 장애로 인한 충격을 이해하고 기능을 분해하는 방법을 생각해야 한다.

 

4. 아키텍처 안전조치

- 나쁜 서비스 1개가 시스템 전체를 망치면 안된다.

가장 나쁜 장애 유형은 매우 느린 응답이다.

차라리 동작하지 않는다면 문제를 더 빨리 찾는다.

 

느리다면 동작을 끊기 전까지 계속 기다려야 한다.

캐시되지 않고. 인덱스도 없이 수많은 데이터를 검색하는 서비스가 중간에 있다면 굉장히 느리게 만들 것이다.

 

해결책 - http 커넥션 타임아웃 설정

 - 커넥션 풀 분리 격벽 구현

 - 정상 동작하지 않는 시스템은 호출하지 않는다.

    (회로 차단기 구현)

 

시스템 행에 걸린 상태

행(hang) 아무런 반응을 하지 않는 상태로써 시스템 운영이 불가능 한 상태

 

 

느린 서비스가 중간에 있으면 아래와 같은 문제가 생긴다.

1. 느린서비스 호출

2. 느린 서비스의 응답을 기다림

3. 응답을 기다리는 동안 계속 요청 받음

4. 수많은 스레드 생성

5. 시스템 다운.

 

 

- 구글과 넷플릭스의 장애대응

 카오스 몽키 - 특정시간에 임의의 머신을 끈다.

 레이턴시 몽키 - 머신간의 느린 네트워크 접속 상황을 시뮬레이션 한다.

 

타임아웃

- 모든 프로세스의 외부 회출에는 타임아웃을 항상 넣어라.

- 타임아웃 발생 시간을 로깅하고 어떤일이 발생했는지 살펴보면서 타임아웃을 변경해라.

- 호출 실패 판단이 너무 오래 걸리면 전체 시스템이 느려진다.

 

회로 차단기

- 타임아웃이나 5xx 에러시 전송 트래픽을 중지시킨다.

- 정상적으로 복구 되었을때 재시작한다.

 

회로 차단기가 끊어졌을때 선택지

1. 요청을 큐에 넣고 나중에 처리한다. - 비동기 작업일 경우 적절하다.

2. 동기 호출의 일부일 경우에는 - 바로 실패시키는게 낫다.

 

격벽

- 장애를 격리하기 위한 방법

각각의 하위 시스템의 커넥션마다 분리된 커넥션 풀로 시작하는 것이 좋다.

- 회로 차단기 사용을 고려하라.

 

하위 시스템의 동기식 호출에 회로 차단기는 꼭 쓰는게 좋다.

- 넷플릭스의 히스트릭스, istio

 

멱등성

연산이 연속적으로 여러번 되어도 결과가 달라지지 않는 것

적립 포인트를 증가시키는 아래 두개의 payload를 보자.

{
    amout: 100,
    account: 1
}
{
    amount:100,
    account: 1,
    forPurchase:12,
}

한 구매당 1개의 적립만 할 수 있는 경우라면 아래의 payload 가 api 멱등성을 유지 할 수있다.

 

 

확장

- 시스템을 확장하는 이유

    - 장애에 더 잘 대응하기 위해

    - 성능을 위해서

 

확장 방법

1. 머신에 더 큰 cpu 탑재하기 - 수직 확장 = 비용이 크다.

    - 큰 서버 1대는 서버 2대보다 비용이 크다.

    - cpu가 크다고 모든 소프트웨어가 그걸 활용하도록 작성되지는 않았다.

 

2. 작업부하 나누기

 - 예를 들어서 금융 기록 서비스, 주문 서비스, 고객 정보 서비스가 있다고 해보자.

이 모든 서비스가 한 군데 있으면 좋지 않다.

중요한 금융 기록 서비스가 높은 부하를 받지 않도록 다른 서비스를 분리한다.

 

위험 분산

- 회복성을 위해 확장하는 방법중 1가지는 계란을 한 바구니에 두지 않는 것이다.

한 호스트에 여러 서비스를 배포하지 않는다.

 - 장애가 모든 서비스에 영향을 줄 수 있다.

 

장애를 줄이기 위한 분리의 일반적인 형태

 - 모든 서비스를 데이터센터의 단일 랙에서 실행하지 않는다.

 - 서비스를 여러 데이터 센터로 분산한다.

예를 들어서 aws 는 별개의 클라우드로 간주되는 리전으로 나뉘어 있다.

 

부하 분산

호출을 하나 이상의 인스턴스에 분배한다.

MSA의 소비자는 자신이 통신하는 서비스의 인스턴스가 1개인지 100개인지 모른다.

보안 강화를 위해서 VLAN 에서 부하분산하기

VLAN( Virtual Local Area Network )

VLAN 외부의 커뮤니케이션은 https 로 한다.

내부에서는 http로 한다.

 

이렇게 인스턴스의 부하는 분산시킬 수 있다.

그렇다면 데이터베이스는 어떨까

데이터베이스를 1개를 쓴다면 데이터베이스는 단일 장애지점이 된다.

 

아키텍처 구축에 대한 제언

-  10배 성장까지 고려해서 설계한다.

- 100배 성장 전까지 재작성을 계획한다.

 

재설계는 모놀리틱을 쪼개는 것을 말할 수도 있다.

재설계는 새로운 DB 저장소를 선택하는 일이 될수도 있다.

동기식 요청을 이벤트 시스템으로 전환하거나 배포플랫폼, 스택을 교체하는 일이 될수도 있다.

 

처음부터 대규모 확장을 대비해서 구축하는 것은 좋지 않다.

신속한 실험이 필요하다.

어떤 기능을 만들어야 하는지 이해해야 한다.

 

초반에 대규모 확장을 고려한다면 절대 오지 않을 부하를 대비해서 엄청난 시간을 쓰게 된다.

 

데이터베이스 확장

- 읽기용 확장

     - 캐싱을 활용한다.

     - 읽기 복제 ( 레플리카 ) 활용

- 쓰기용 확장

    - 샤딩을 활용한다.

 

공유 데이터베이스 인프라

 - 데이터베이스가 여러개의 스키마를 갖는 것은 필요한 머신이 줄어서 좋다.

 - 하지만 명백한 단일 장애 지점이 된다.

 

데이터베이스 인프라가 다운되면 한번에 다수의 MSA 에 영향을 주고 재앙을 초래한다.

 

CQRS

Command Query Responsibility Pattern

- 명령과 질의에 대한 책임 분리

- 이벤트 소싱으로 알려진 패턴

- 명령과 질의를 처리하는데 사용된 모델은 분리된다.

- 명령을 이벤트로 처리하고 프로세싱하도록 할 수 있다.

 

캐싱

 - 일반적인 성능 최적화 방법

 - 연속되는 요청은 재연산 없이 저장되어 있는 값을 쓸 수 있다.

분산시스템에서는 캐싱을 클라이언트와 서버측에 둘지 고민한다.

 

클라이언트 측 캐싱

 - 캐싱 결과를 클라이언트가 저장

 

프록시 캐싱

 - 클라이언트와 서버 사이에 프록시 배치

예시) AWS CDN

 

서버측 캐싱

레디스, 멤캐시드, 인메모리 캐시 시스템을 활용한다.

 

클라이언트 캐싱은 네트워크 호출을 줄인다.

프록시 캐싱은 클라이언트와 서버에 독립적이다.

기존 시스템에 추가하는 가장 간단한 방법

 - 예시 ) 리버스 프록시

서버측 캐싱 - 메모리 디비 활용

 

쓰기용 캐싱

 - 로컬 캐시에 쓰기를 먼저하고 데이터베이스에는 나중에 쓴다

 

회복성을 위한 캐싱

- 서비스를 쓸 수 없는 상태라면 캐싱된 이전 데이터를 쓸 수 있다.

 

원본 감추기

- 캐시를 원본보다 앞에 위치시킨다.

- 캐시미스 발생시 캐시에서 빠르게 응답한다.

- 이렇게 하면 캐시의 장애가 원본으로 전파되지 않는다.

 

너무 많은 곳에 캐싱하지 마라.

 - 데이터의 신선도가 좋지 않다.

 - 어디서 어디로 캐시되는지 명확하게 이해하고 사용하라.

 

자동 확장

 - AWS 같은 서비스에서는 Auto Scaling 을 제공한다.

 - 쿠버네티스를 써도 자동 확장을 할 수 있다.

 - 시간대별 서비스 사용량을 잘 보고 auto scaling 하자.

 

분산 시스템에서 균형을 맞춰야하는 3가지

CAP

1. C  일관성 - 항상 일관된 응답을 줘야한다. 멱등성이 있어야 한다.

2. A 가용성 - 모든 요청이 응답을 받을 수 있어야 한다.

3. P 분할 용인 - 시스템 부분 간의 통신은 가끔 실패한다. - 다루는 능력이 있어야 한다.

 

3가지를 모두 가질 수 없는 경우

2가지를 선택한다.

 

이렇게 나뉠 수 있다.

CA - 분할용인을 희생한 경우

시스템에 분할용인이 없으면 네트워크상에서 수행될 수 없다.

 - CA 는 분산 시스템에서는 존재 할 수 없는 형태이다.

 - 분산 시스템에서 시스템 통신은 당연히 실패 할 수 있다.

 - 그리고 그것을 다루는 능력은 당연히 있어야 한다.

 

 

CP - 가용성을 희생한 경우

주문 서비스 인스턴스도 여러개 있고

주문 데이터베이스도 여러개라고 해보자.

이때 1개 서비스와 데이터베이스에 장애가 났다.

이걸 그대로 가용성 있게 처리하면 디비 2개가 서로 일관성이 안 맞게 된다.

바로 에러를 리턴하고 처리하면 가용성은 희생하지만 일관성을 지킬 수 있다.

 - 다수의 노드의 일관성을 제대로 유지하는 것은 어렵다.

 - 필요하더라도 직접 발명하지 말고 이런 기능을 제공하는 서비스를 써라

 - 아래 AP 시스템을 구축하는데 열중하던지 적절한 기성품을 써라.

 

 

AP - 일관성을 희생한 경우

주문 서비스 1 과 데이터베이스 1이 장애가 생겼을때

에러 메세지를 리턴하지 않고 요청을 처리한다.

이렇게 하면 기능은 동작한다. 하지만 데이터 일관성을 잃게 된다.

나중에 두 노드를 동기화 시켜줘야 한다.

분할된 상태가 길어지면 동기화가 어려워진다.

 

- 이런 시스템을 최종적 일관성이 있다고 한다. 미래에 특정시점에 노드의 데이터는 없데이트 된다.

하지만 한번에 수행되지는 않는다. 이전 데이터를 볼 가능성이 있다.

 

분산시스템은 장애를 예상해야 한다.

현실에서는 AP 시스템으로 개발되는 경우가 많다.

 

넷플릭스의 유레카

 - 스프링에서 서비스 인스턴스에 대해 부하분산, 조회 기능을 제공한다.

 

문서화

 - swagger

 - HAL

 

 

 

 

 

댓글