우연히 "대규모 시스템 설계 기초"라는 책을 읽게 되었다. 재밌다, 나 같은 뉴비들은 솔직히 "설계"라는 말만 들어도 피를 토하고 싶어진다. 첫 장에서 서비스가 무중단으로 지속되도록 설계 하는 방법에 대해 대략적이지만 자세하게 적혀있었다. 이 책을 읽어 가면서 내가 지금까지 만들었던 백앤드 어플리케이션이 어떻게 설계되어서 사용되는지 알아보려한다. 그 내용을 한번에 담은 그림은 다음과 같다.
지금 까지 무의식적으로 개발했던 모든 요소들이 모두 들어가 있다. aws를 사용하다보니 그냥 "아 이거 있어야 한다고 함~" 하면서 적용한 것들 (로드벨런서, CDN..) 도 있는데 이번 기회에 잘 조지고 가야겠다.
그냥 dau 100명 정도? 생각하고 만든 당신의 서비스가 갑자기 대박을 터트리면서 dau 100만 명을 찍는 서비스가 되었다고 치자. 짜릿하네 기존 100명 짜리 서버가 100만 명을 수용할 수 있을까? 뭐.. 방법이야 여럿 있지만 일반적으로 서버를 "확장"하려 할 것이다. 그럼 어떠한 방식으로 확장할 것인가?
- 서버 10대 더 사서 병렬 배치하기
- 지금 쓰는 서버에 램, 하드, cpu 등을 10배 이상의 성능이 나오는 걸로 바꾸기
와 같은 방법이 있겠다. 1 은 수평적 규모 확장이며, 2는 수직적 규모 확장이다.
수직적 확장은 당연하겠지만 한계가 있다. cpu나 램을 어디까지 꽂을 건데? 무한히 꽂을 수 없기 때문이다. 또한 수직적 확장법은 자동 복구나 다중화 방안을 제시하지 않는다. 서버가 한대 뿐인데... 이거 퍼지면 어떻게 될까?
반대로 수평적 규모 확장은 여러대의 서버를 사용하기 때문에 만약에 한대가 퍼지더라도 다른 서버로 일단은 서비스를 돌릴 수는 있다. 하지만 만약 한대가 퍼지면 다른 한대가 2대의 일을 해야하기 때문에 일시적으로 응답이 지연되거나 그럴 수는 있다. 당연히 서비스가 중단되는것 보다는 백배 낮다.
그럼 위 그림의 요소에 큰 덩어리들에 대해 하나씩 알아보자.
로드밸런서는 말그대로 "부하 분산자"라고 보면 된다. 한번에 들어오는 요청을 여러대의 서버로 고르게 분산 시키는 역할을 한다.
자 이제 우리는 여러 대의 서버가 있다. 그럼 이제 우짬? 위에서 말한 한대의 서버의 문제를 커버하기 위해 우리는 로드밸런서를 사용한다.
로드밸런서는 평소에는 전달 받은 요청을 여러 서버로 골고루 분배한다. 하지만 만약 서버 한대가 고장난 것을 감지하면 고장나지 않았던 서버로 모든 요청을 집어 넣는다. 그럼 우리가 원하는 수평적 규모 확장을 가져오게 되는 것이다.
요청을 어떻게 넣게 될까? 일반적으로 우리의 서비스는 publicIP를 통해 최초 로드밸런서로 요청이 들어온다. 그리고 그 로드밸런서에 붙어 있는 여러 서버의 privateIP로 요청을 분배해 준다.
이 부분이 재밌었다. 아 원래 데이터베이스는 하나만 있으면 되는거 아님? 이라 생각 했다. 하지만 위키피디아에서는 이렇게 말하더라.
많은 데이터베이스 관리 시스템이 "다중화"를 지원한다. 보통은 서버 사이에 주(master)-부(slave) 관계를 설정하고 데이터 원본은 주 서버에, 사본은 부 서버에 저장하는 방식이다
이러한 관계에서는 쓰기 연산은 마스터에만, 읽기 연산은 종놈들에게 시키는 것이다. 마스터에 새로운 데이터가 들어가면 종놈들은 이를 복사해 두고 읽기 요청이 들어왔을 때 종놈들로만 데이터를 내보내 주는 것이다.
생각해보니 대부분의 서비스는 "아마도" 쓰기 보다는 읽는 작업이 훨~~씬 많을 것이다. 그렇다 보니 데이터베이스의 부하도 고민해야 한다. 그럴때 사용하는 방식이 이렇고 주종 관계의 데이터 베이스들이다.
웹에서 쓰기 요청이 들어오면 로드밸런서가 쓰기 전용 서버로 데이터를 보내고 그 서버가 마스터 디비에만 쓰게 하고, 일기 요청은 모든 서버에 들어갈 수 있으며 종놈 데이터베이스에서 정보를 가져올 수 있게 설계 하면 된다.
데이터베이스에 매번 조회를 하는 것은 비용적인 측면이나 효율적인 측면이나 다 썩 좋지는 않다. 또는 서버에서 매번 그 데이터를 활용해서 가공하고 다른 api를 호출하고 등등을 거친다면 데이터 한번 조회하는데 발생하는 비용이 너무 클 수 있다. 자주 들어 봤겠지만 우린 캐시를 사용해야 한다.
캐시 계층은 데이터가 잠시 보관되는 곳이다. 캐시는 램 같은 고속 기억장치를 사용하기 때문에 응답 속도나 비용 측면에서 데이터베이스를 직접 사용하는 것보다 좋다. 자주 조회하는 정보들을 미리 캐시에 저장해 놓고 있다가, 요청이 들어왔을 때 캐시에 있는 정보(이를 캐시히트라고 한다.)라면 서버나 데이터베이스를 거치지 않고 캐시의 정보를 그대로 내준다.
그럼 캐시를 사용할 때 어떤 점을 고려해야하냐
- 어떤 데이터를 저장 할 것인가.
- 언제까지 저장 할 것인가.
- 장애 대처는 어떻게 할 것인가.
캐시는 램 같은 휘발성 메모리다. 따라서 영속적으로 저장해야 하는 데이터는 당연히 여기에 저장해서는 안된다.
또한 램이기 때문에 하드 같이 넓고 넓은 저장 공간을 기대할 수 없다. 따라서 데이터를 일정 시간만 저장해야한다. 그런데 만약 저장 시간이 너무 짧으면 캐시미스가 자주 발생해 데이터베이스를 다시 조회해야 한다. 반대로 너무 길면 캐시의 용량이 빠르게 꽉차기 때문에 이는 실제 서비스를 운영하면서 잘 조절해야 한다.
CDN은 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크다. 이미지, 비디오, CSS, javascript 파일 등을 캐시할 수 있다.
쉽게 말해서 유저에게 가까운 서버에 전송이 오래 걸리는 이미지나 영상 같은걸 캐시해놓는 것을 말한다. 원본은 미국에 있는 유튜브 영상을 한국에서도 빨리 볼려면 한국 서버에도 저장해 놓으면 되지 않겠나! 그걸 해주는 것이라고 생각하면 편하다.
하지만 이런 CDN도 사용시 고려해야 할 사항이 있다.
일단 비용적인 측면이다 일반적으로 제3 사업자를 통해 운영하게 된다. 대표적으로 aws가 있다. 따라서 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않다.
그리고 적절하게 만료 시켜줘야 한다. 캐시는 원래 서버를 거치지 않고 저장해 두었던 정보를 일정 시간 내에 다시 보여준다. 그럼 시의성이 중요한 콘텐츠의 경우 만료 시점을 잘 정해야 하겠지? 항상 최신 정보를 보여줘야 하는 콘텐츠일 경우 CDN 자체가 적절하지 않거나 만료 시한을 짧게 정해줘야 한다. 물론 너무 짧으면 캐시를 하는 이유가 퇴색된다.
또한 장애에 대한 대처도 잘해야한다. 만약 CDN이 죽었다고해서 서비스자체가 죽어버리지 않게 설계 해야한다. 따라서 CDN이 응답하지 않는 경우 직접 원본 서버에서 콘텐츠를 가져올 수 있도록 구성해야 한다.
메시지 큐는 쉽게 말해서 여러 덩어리들이 비동기 통신을 할 수 있게 만들어 주는 큐다. 이 큐에는 직접 적으로 연결되어 있지 않는 여러 컴포넌트간 메시지를 전달하는 용도로 사용된다. 여기서 메시지는 또 쉽게 말하면 이벤트다.
"유저가 이거 달라고 하는데 캐시에 없으니까 서버가 좀 찾아줘"
라는 메시지가 큐에 들어가면 큐의 끝단에 도착했을 때 이 큐를 구독하고 있는 구독자들이 자신에게 해당되는 이벤트를 가져가서 처리하게 된다. 이 메시지들은 소비자가 꺼낼 때까지 안전히 보관된다는 특성을 가지고 있다. 즉 받는 쪽에서 터진 상태라면 큐에 보관하고 있는다.
기본적으로 이 메시지를 생산하는 쪽을 생산자 또는 발행자라 부르며 이 메시지를 구독하는 쪽을 소비자 또는 구독자 라고 부른다.
메시지 큐를 사용하면 서비스 또는 서버 간 결합이 느슨해진다는 장점이 있어서 규모 확장성이 보장되는 안정적 어플리케이션을 구성하기 좋다.
그럼 어쩔 때 사용할 수 있을까. 아래와 같은 상황이 있다고 치자.
- 동영상을 변환하는 작업을 하는 웹사이트가 있다.
- 나는 2시간 짜리 영상을 변환하게 했다.
- 변환에는 10분이 걸린다. 나는 이 영상이 변환이 끝날 때까지 그냥 화면을 보고 있어야 한다.
사실 10분 동안 기다리게 할 필요가 없잖나? 그냥 "당신의 동영상의 변환 요청이 잘 받아 젔고 완료되면 이메일로 알려줄께!" 라고 하면, 또 새로운 영상을 병렬적으로 요청할 수 있으니까. 서비스 측면에서도 이게 더 좋지 않은가.
이러한 요구 사항을 만족 시킬 수 있는게 어떻게 보면 메시지큐를 이용하는 것 이다. 그럼 아래 처럼 구성해 볼 수 있겠다.
- 동영상 변환 요청을 받는다.
- 동영상 변환 비즈니스 로직이 있는 서버가 구독하고 있는 메시지 큐에 요청을 발행 한다.
- 요청이 잘 집어 넣어지면 유저에게 응답을 보내준다.
- 동영상 변환 비즈니스 로직은 이벤트를 큐를 통해 소비하게 되면 변환 작업을 시작한다. 완료되면 이메일로 알려준다.
유저 입장에서는 10분 동안 다른 작업을 하거나 추가 영상 변환 작업을 큐에 넣을 수 있다. 변환 비즈니스 서버는 자신의 규모에 맞게 큐에서 이벤트를 꺼내 작업을 처리하기에도 용의하다.
휴 일단 오늘은 이 큰 덩어리들에 대한 이야기를 해봤다. 무의식적으로 사용하기 보단 이런애들이 모두 모여서 백앤드 서비스가 돌아간다는 점이 재밌었다.
사이드프로젝트를 할 때 이 요소들을 모두 잘 넣어서 진행해 봐야겠다.