사용자가 10배 늘었다. 일단 서버부터 사면 되나요?

코헤·4일 전

요즘은 AI 덕분에 개나소나 개발이 가능해져서 그런가, 면접 질문마다 꼭 대규모 시스템에 대한 이해를 한 스푼씩 곁들인다.
물론 나는 개나 소 정도 되어서 잘 대답하지 못하는 것 같아 이 참에 정리해보고자 글을 써본다.
그리고 후르츠가 자꾸 CS 주입해서 정리해본다

그중에서도 내가 유독 자주 받아본 질문이 있다.

“사용자가 10배 증가한다면 어떻게 대응하시겠어요?”

처음에는 나도 뻔하게 Scale Out, Load Balancer, Redis, DB Replication, Kafka 같은 키워드를 열심히 꺼냈다. (아무래도 면접관도 잘 모르는 경우도 종종있으니까 그 중에 하나 걸려봤음 좋겠다)

그리고 마음속으로는 거의 이런 상태였다.

서버 10배 복제 가보자고.

하지만.. 사용자가 10배 늘었다고 했지, 서비스가 터졌다고는 안 했다....


사용자 10배 증가 ≠ 서버 10배 구매

현재 사용자가 1만 명인데 10만 명으로 늘었다고 생각해 볼 때 엄청난 일처럼 보인다. (축하합니다. 우리 서비스 떡상했습니다.)

이제 AWS에게 월급을 바칠 시간입니다.

그런데 새로 가입한 사용자들이 한 달에 한 번 접속한다면?

기존 사용자는 매일 접속했지만 신규 사용자는 가입만 하고 사라졌다면?

사용자는 10배 증가했지만 실제 요청량은 별로 늘지 않았을 수도 있다.
반대로 사용자는 2배밖에 늘지 않았는데 전 국민이 오후 8시에 동시에 티켓을 예매한다면 서버는 이렇게 될 수도 있다.

살려 주세요.


ex) 무신사의 무진장 이벤트 기간에 죽어가는 개발자 1인이 있다

결국 중요한 것은 사용자 수 자체가 아니다.

우리가 확인해야 하는 것은 실제로 시스템에 들어오는 부하다. (트래픽이라고 하면 틀린말인가?.? 잘 모르겠다)

  • 초당 요청은 얼마나 늘었는가?
  • 특정 시간에 요청이 몰리는가?
  • 읽기 요청이 증가했는가?
  • 쓰기 요청이 증가했는가?
  • 동시에 접속하는 사용자는 몇 명인가?
  • 요청 하나를 처리하는 비용은 얼마나 드는가?

“사용자가 10배 늘었습니다.”

라는 말을 시스템의 언어로 번역하면 다음 질문이 된다.

어쩌라고요?.?(그래서 언제, 어떤 요청이, 얼마나 많이 들어오는데요?)


철칙 1 : 아무것도 하지 않는다.

사실 내가 만들어가는 서비스의 대부분은 사용자가 10배 는다고해서 응답에 문제가 없고 에러도 없다. CPU도 여유롭다. DB도 평화롭다. 서비스 이용자도 아무 불만이 없다.
이 경우에는 사용자가 10배 늘어 신기하다는 기분으로 집에 가야한다.

물론 진짜로 모니터를 끄고 집에 가라는 뜻은 아니다.

현재 시스템이 증가한 트래픽을 충분히 처리하고 있다면, 이유 없이 Redis를 붙이고 Kafka를 띄우고 DB를 샤딩할 필요는 없다는 뜻이다.

개발자에게 새로운 기술은 매우 매력적이다. 약간 은탄환 같은 느낌이고 새로운 기술을 도입하면 왠지 시스템이 강해질 것 같다. AI로 인한 FOMO로 내 일자리 잃을 것 같은 기분에서 1발자국 약간 멀어질 수도 있다.

하지만 만일 이력서 드리븐 개발로 Redis 도입!(redis로 영속성을 챙기고 싶지 않아?!) Kafka 도입! (rabbitmq는 가오가 안살아) MSA 전환! (일단 쪼개보자 ! 근데 유저가 매 요청마다 인가 받았는지 확인해야 할 것 같은데? 무한히 인증 서버를 찔러보자) 를 했다 치자

그리고 3개월 뒤에 재밌어진다.. 우리는 열심히 관리를 하고 있을 것이다.
기술을 추가하면 성능만 늘어나는 것이 아니다. 관리해야 할 서버도 늘어나고, 장애 지점도 늘어나고, 모니터링 대상도 늘어난다.

기존에는 애플리케이션과 DB만 보면 됐는데 Redis를 추가하면 이제 이런 질문도 생긴다.

  • Redis가 죽으면?
  • Redis 데이터가 사라지면?
  • 캐시와 DB 데이터가 다르면?
  • Redis 메모리가 가득 차면?

문제를 해결하기 위해 기술을 추가했더니 새로운 문제가 DLC로 따라온다.
따라서 현재 시스템이 서비스 목표를 만족한다면 구조를 유지하는 것도 설계다.

아무것도 하지 않는 것이 아니라, 문제가 없다는 것을 확인하고, 불필요한 복잡도를 추가하지 않는 것이다.


그래서 ‘괜찮다’는 건 어떻게 아는데?

“서버 멀쩡해 보이는데요?” 감으로 판단할 수는 없다. 내 컴퓨터에서는 잘 되는데요의 서버 버전을 만들어서는 안 된다.

같이 일하는 동료가 이렇게 볼 수도 있다 (물론 다 같이 모르는 상황이 많아서 안그러기도 함)

인간도 정신머리가 ㄱㅊ은지 판단하는 50만원짜리 풀베터리 검사가 있듯 서버에게도 서비스가 정상인지 판단하려면 우리가 지켜야 할 기준이 있어야 한다.

예를 들어 다음과 같은 기준이다.

  • 대부분의 요청은 500ms 안에 응답해야 한다.
  • 결제 요청은 거의 실패하지 않아야 한다.
  • 한 달 동안 서비스가 일정 시간 이상 사용 가능해야 한다.
  • 중요한 데이터가 사라지면 안 된다.

이런 서비스 품질 목표를 SLO(Service Level Objective)라고 한다.

서비스마다 중요한 가치는 다르다. SNS의 인기 게시글 순위가 3초 늦게 바뀌는 것은 별문제가 아닐 수 있다.

하지만 결제 결과가 3초 늦게 반영되면서 사용자가 결제 버튼을 다섯 번 누른다면? 카드 명세서가 갑자기 풍성해질 수 있다.

서비스마다 중요하게 봐야 하는 것도 다르다. 어떤 서비스는 응답 속도가 중요하다.
어떤 서비스는 데이터 정확도가 중요하다. 어떤 서비스는 잠깐 느려지는 것보다 절대 중단되지 않는 것이 중요하다.

따라서 사용자가 10배 늘었다고 무조건 같은 대응을 할 수 없다. 먼저 물어야 한다.

우리 서비스에서 반드시 지켜야 하는 것은 무엇인가?


문제가 생겼다. 이제 DB 엉덩이부터 때려도 되나요?

사용자가 늘어난 뒤 실제로 응답 시간이 느려졌다. 드디어 문제가 발생했고 내 연봉의 가치를 실현할 때가 되었다.

그렇다면 DB부터 의심하면 될까? 솔직히 DB일 확률은 꽤 높을 수도 있다. 하지만 사용자의 요청은 DB까지 순간 이동하지 않는다...

대략 이런 경로를 지나간다.

사용자

↓

로드 밸런서

↓

애플리케이션 서버

↓

DB 커넥션 풀

↓

데이터베이스

↓

외부 API

↓

다시 사용자

이 중 어디에서 느려졌는지 모른다.
로드 밸런서가 처리할 수 있는 연결 수를 넘었을 수도 있다.
애플리케이션 서버의 CPU가 비명을 지르고 있을 수도 있다.
DB 커넥션을 모두 사용해서 새 요청들이 줄을 서고 있을 수도 있다.
외부 결제 API가 느린데 우리 서버가 억울하게 욕먹고 있을 수도 있다.

그런데 우리는 종종 이렇게 한다.

응답이 느리다.

↓

DB인가?

↓

일단 인덱스 생성.

병목을 찾지 않고 해결책부터 선택하면 운 좋게 맞을 수도 있지만, 안 맞으면 시스템만 더 복잡해진다. 하지만 물론 내 경우 99% 정도는 DB 탓이라서 급하면 인덱스부터 추가하자..
모 회사의 경우 table 생성을 하면 무조건 full index 를 넣는 짓거리를 하곤 한다. 개발엔 정답이 없다는 브랜치 글도 있으니 한 번 살펴보자


CPU 사용률 30%인데 왜 서버가 느린데요?

CPU 사용률을 확인해보자 아래는 우리집 뽀삐다.

30%로 주 40~52시간씩 일하는 현대인보다 복지가 좋다. 그런데 응답 시간은 계속 느리다. 이럴 때는 요청들이 어디선가 기다리고 있을 수도 있다.

예를 들어 서버가 동시에 요청 10개를 처리할 수 있다고 하자.

그런데 갑자기 요청 100개가 들어왔다. 일단 할 수 있는 10개는 처리해보지만 나머지 90개는 번호표 뽑고 기다린다.

요청 증가

↓

처리 가능한 작업 수 초과

↓

대기열 입장

↓

제 앞에 몇 팀 있어요?

↓

응답 시간 증가

이거 플로우 차트로 만들 수 있거나 이쁘게 만들 수 있지 않나? 난 돈없는 취준생이라서 이렇게 쓴다. 나를 정기구독하는 회사가 생긴다면 이쁘게 만들어보도록 하겠다.

실제 로직은 100ms 만에 끝나더라도 요청이 3초 동안 기다렸다면 사용자가 느끼는 응답 시간은 3.1초다.

CPU 하나만 보고 시스템이 여유롭다고 판단할 수 없는 이유다.

다른 곳에서도 요청은 기다릴 수 있다.

  • 애플리케이션 Thread Pool
  • DB Connection Pool
  • 메시지 Queue
  • DB Lock
  • 네트워크

그래서 시스템을 볼 때는 CPU 하나만 쳐다보는 것이 아니라 요청량, 응답 시간, 오류, 자원 포화 상태를 함께 봐야 한다.
어디서 볼 수 있는지는 나도 모른다 나는 ktor에30개의기여를달성하였으며,후르츠리스트로널리알려져있다내가포트원에합격한것은후르츠리스트의상징이다CS를증명해냈다 의 상징인 사람에게 모니터링을 배워서 AI에게 모든 정보를 한 곳에 때려넣어달라고 부탁해뒀다.


if) DB가 범인이었다

일단 요청 흐름을 확인해보자 역시나 DB 쿼리에서 시간이 오래 걸리고 있었다.

자, 이제 DB 용량을 늘리면 될까? 아니다. DB를 돈으로 때리기 전에 질문할 것이 있다.

쿼리가 지금 제정신인가?

사실 내가 제일 제정신이 아닌데 쿼리 정신머리부터 확인하기는 어렵긴하지만 몇가지 잦게 발생하는 예시가 있다.

예를 들어 게시글 20개만 보여주면 되는데 게시글 100만 개를 전부 조회한 뒤 애플리케이션에서 20개만 자르고 있을 수도 있다.(이 내용은 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식이 책이 낫밷했다 추천은 안한다)
조건에 맞는 데이터 몇 개를 찾기 위해 테이블 전체를 읽고 있을 수도 있다.
반복문 안에서 쿼리를 계속 실행하고 있을 수도 있다.
가끔은 log를 찍겠답시고 한 서비스에서 4~5번 DB 조회하는 일이 있을수도 있다.

왜 이런일이 있었냐고 의문을 갖지 말도록하자 우리의 서비스는 드디어 고공행진을 하며 사용자수가 10배 늘었으니까

그래서 먼저 확인한다.

DB가 느리다.

↓

쿼리는 필요한 데이터만 조회하는가?

↓

실행 계획은 어떻게 되어 있는가?

↓

인덱스를 제대로 사용하는가?

↓

같은 데이터를 반복 조회하는가?

↓

정말 실시간 조회가 필요한가?

기획을 조금 변경하는 것으로 문제가 해결될 수도 있다. 개발자는 기획자보다 도메인에 대해서 느알느알과 잘알잘알을 해야한다.

모든 데이터를 한 번에 보여주지 않고 페이지를 나눌 수는 없을까?
매 요청마다 정확한 인기 순위를 다시 계산해야 할까?
어제 생성된 통계까지 매번 실시간으로 집계해야 할까?

개발자는 종종 기술 안에서만 해결책을 찾는다. 하지만 가끔은 요구사항을 조금 바꾸는 것이 서버 열 대보다 저렴하다.

서버 열 대 보다 내 연봉이 더 저렴할텐데 왜 사람을 안 뽑는지도 의문이다


else if ) 아직도 DB가 힘들다면

쿼리도 개선했다. 인덱스도 확인했다. 불필요한 요청도 줄였다. 그런데 DB가 여전히 힘들다.

이제 나약한 DB를 위해서 상황에 맞는 방법을 생각할 수 있다.

읽기가 너무 많다면 조회 부하를 다른 DB로 분산할 수 있다. 반복 조회되는 데이터라면 캐시를 사용할 수 있다. DB 한 대의 성능이 부족하다면 Scale Up을 고려할 수 있다. 데이터와 요청을 여러 DB로 나누는 방법도 생각할 수 있다.

하지만 여기서도 해결책은 병목에 따라 다르다.

읽기 요청이 문제인데(select) 쓰기 분산부터(update, delete 등) 고민할 이유는 없다.

특정 데이터에 쓰기가 몰리는데 Read Replica를 열 대 추가해도 문제는 해결되지 않는다.

DB Connection이 부족한 상황에서 애플리케이션 서버만 계속 늘리면 어떻게 될까?
서버마다 Connection Pool이 생긴다.
DB로 들어가는 Connection은 더 많아진다.

DB: 한 명씩 오세요. (제발)

즉 서버를 늘리는 것이 오히려 DB 부하를 키울 수도 있다.


긴급 처방과 치료는 다르다

서비스가 지금 터지고 있다. 사용자는 계속 들어온다

좋은 회사의 경우 장애 알림과 슬랙이 엉엉 울 수 있다.

하지만 내가 원하는 회사는 장애가 났을 때 전화와 내 심장이 울 수 있다면 된다..

이때는 스윗하게 달래기위해서 우선 장애를 완화해야 한다. 서버를 추가할 수도 있다. 트래픽을 제한할 수도 있다.

일부 기능을 잠시 비활성화할 수도 있다.

하지만 서비스가 정상으로 돌아왔다고 끝난 것은 아니다.

1단계

일단 산다.

서버 증설
트래픽 제한
기능 제한

↓

2단계

왜 아팠는지 찾는다.

병목 분석
쿼리 개선
인덱스
캐싱
구조 개선

서버 한 대를 추가해서 장애가 사라졌다면 급한 불은 껐을 수 있다. 하지만 비효율적인 쿼리가 그대로라면 사용자가 또 늘었을 때 같은 문제가 반복된다. (과연 우리 서비스가 다시 흥해서 사용자가 다시 몰릴 수 있을까..)
긴급 처방과 근본 원인 해결은 다르다.


그래서 사용자가 10배 늘어나면요?

하여튼 우리는 뭔가 많이 배웠다. 처음 질문으로 돌아가 보자.

사용자가 10배 증가한다면 어떻게 대응하시겠어요?

이 질문에 대한 답변은 이제 "서버를 늘리고 Redis를 붙이고 DB를 분산하겠습니다." 가 아니라 "일단 문제가 생겼나요?" 가 되었을 것이다.

문제가 생겼는지를 확인하려면 사용자 증가가 실제 트래픽에 어떤 영향을 주었는지 확인한다.
현재 시스템이 서비스 목표를 만족하고 있다면 불필요하게 구조를 변경하지 않는다. 문제가 발생했다면 요청의 전체 흐름을 보면서 병목을 찾는다.

그리고 병목의 원인에 맞게 쿼리 개선, 인덱스, 캐시, 서버 증설, 데이터 분산 등을 선택한다.

정리하면 이렇다.

사용자가 10배 늘었다.

↓

진짜 부하도 10배 늘었나?

↓

서비스 목표를 만족하는가?

├─ 네
│
│  축하합니다.
│  괜히 건드리지 맙시다.
│
└─ 아니오
   ↓
   어디가 느린가?
   ↓
   병목을 찾는다.
   ↓
   원인에 맞는 기술을 선택한다.

서비스가 커진다고 무조건 더 복잡한 기술이 필요한 것은 아니라고 생각은 한다.. 기술 이름을 많이 말하는 것보다 먼저 필요한 것은 지금 정말 문제가 있는지 확인하고, 문제가 있다면 정확히 어디가 문제인지 찾는 것이다.

그리고 요청 흐름을 따라가 보니 정말 DB가 범인이었다고 해 보자.
그렇다면 다음 질문이 생긴다. DB는 데이터를 어떻게 찾고 있길래 느린 걸까? MySQL은 왜 인덱스에 B+Tree를 사용할까?

그리고 인덱스로 DB에서 적게 읽는 것과 Redis를 이용해 DB를 아예 읽지 않는 것은 어떻게 이어질까?

다음 글에서는 DB를 혼내기 전에 B+Tree부터 만나 보려고 한다.

profile
하이하이

2개의 댓글

comment-user-thumbnail
3일 전

다음 시리즈 언제 나와여

1개의 답글