하루 830건을 위해 분당 10만 건을 준비했습니다 (회고)
하루 830건을 보내면서 분당 10만 건을 준비했습니다. 과하게 만든 것보다 과하게 말한 것이 문제였어요.
[그때 내린 판단]
공지 알림 서비스의 FCM 발송 구조를 다시 짰습니다. 서블릿 스레드가 Firebase 응답을 기다리며 묶이는 문제가 있었고, 비동기 콜백으로 바꾼 뒤 k6로 부하 테스트를 돌렸어요.
200 VU로 분당 10만 건 시나리오를 만들어 검증했습니다. P95가 5.24초에서 2.53초로 줄었고, 최대 응답도 8.33초에서 3.51초가 됐어요.
한동안 이걸 자랑스럽게 말하고 다녔습니다. 그러다 실제 트래픽을 계산해봤어요.
최근 90일 발송량이 약 75,000건입니다. 하루 평균 830건이에요.
분당 10만 건을 준비했는데 하루에 830건을 보내고 있었습니다. 두 숫자를 나란히 놓고 보니 말문이 막혔어요.
[지금 다시 보면]
두 숫자는 애초에 같은 축이 아니었습니다
먼저 정리할 게 있어요. 하루 830건은 평균입니다. 공지 알림은 평균으로 오지 않아요.
새 공지가 하나 올라오면 그 게시판을 구독한 사람 전원에게 동시에 나갑니다. 수백 건이 한 번에 터져요. 하루 830건이라는 숫자는 이 순간적인 몰림을 완전히 가립니다.
그러니 “하루 830건인데 분당 10만 건은 과하다”는 지적은 절반만 맞아요. 비교해야 할 것은 일 평균이 아니라 피크입니다.
문제는 제가 이 반박을 그때는 못 했다는 거예요. 부하 테스트를 돌릴 때 저는 피크를 계산하고 200 VU를 정한 게 아니었습니다. 그냥 큰 숫자를 넣어본 거였어요.
준비 자체는 틀리지 않았습니다
비동기 전환은 필요한 작업이었어요. 실제로 서블릿 스레드가 묶이고 있었고, 스레드 덤프에서 확인한 문제였습니다. 트래픽이 적어도 구조가 잘못됐으면 고치는 게 맞아요.
Outbox 패턴도 마찬가지입니다. 서버를 강제 종료했을 때 발송 대상이 통째로 사라지는 건 트래픽 규모와 무관한 결함이에요. 하루 830건이어도 그 830건이 사라지면 안 됩니다.
즉 만든 것들은 대부분 정당했어요. 문제는 다른 데 있었습니다.
틀린 건 판단이 아니라 설명이었습니다
“분당 10만 건을 처리했다”고 말한 게 문제였어요.
이 문장은 두 가지를 흐립니다. 첫째, 프로덕션 실트래픽이 아니라 부하 테스트 시나리오 검증값입니다. 둘째, 200 VU라는 숫자에 근거가 없었어요.
정확히 말하면 이렇게 됐어야 합니다.
k6 200 VU로 분당 10만 건 규모의 부하를 주입했을 때, P95가 5.24초에서 2.53초로 줄었습니다. 실제 서비스 피크는 이보다 훨씬 작지만, 공지 하나에 수백 건이 동시에 나가는 구조라 여유를 두고 검증했습니다.
문장이 길어졌지만 이게 사실이에요. 짧게 말하려다 과장이 됐습니다.
[무엇이 달랐어야 했나]
숫자를 정할 때 근거를 같이 적었어야 했습니다
200 VU를 왜 200으로 정했는지 저는 지금도 설명하지 못해요. 그때 기록을 안 남겼기 때문입니다.
만약 “구독자가 가장 많은 게시판이 N명이고, 공지가 몰리는 시간대에 최대 M개가 동시에 올라오므로 N × M을 목표로 잡았다”처럼 한 줄만 적어뒀다면 지금 답할 수 있었을 거예요. 계산이 틀렸더라도 근거 있는 판단이었다고 말할 수 있습니다.
지금은 근거가 없으니 결과 수치만 남았어요. 수치는 있는데 이유가 없는 상태가 제일 취약합니다.
오버엔지니어링인지 아닌지는 항목별로 갈립니다
전부를 한 덩어리로 놓고 “과했나”를 물으면 답이 안 나와요. 나눠보면 이렇습니다.
| 작업 | 지금 판단 |
|---|---|
| 비동기 전환 | 필요했습니다. 실제 병목을 확인하고 고쳤어요 |
| Outbox 패턴 | 필요했습니다. 규모와 무관한 유실 문제입니다 |
| Redis 조회수 집계 | 필요했습니다. DB 락 경합이 실재했어요 |
| 200 VU 부하 테스트 | 규모는 과했고, 근거도 없었습니다 |
| ShedLock 적용 | 현재 단일 인스턴스라 아직은 불필요합니다 |
마지막 두 개가 과했다고 생각해요. 나머지는 트래픽이 지금의 100분의 1이어도 했어야 하는 작업입니다.
[남은 질문]
어디까지가 학습이고 어디부터가 낭비인가를 아직 못 정했습니다.
솔직히 말하면 저는 그 부하 테스트를 하면서 배운 게 많았어요. k6를 처음 써봤고, VU와 처리량의 관계를 몸으로 알게 됐고, 스레드 풀이 어떻게 마르는지 봤습니다. 실무에서는 필요 없었을지 몰라도 저한테는 남는 게 있었어요.
학생 프로젝트에서 이런 과잉은 어느 정도 허용되는 영역이라고 생각합니다. 실패해도 손해가 크지 않고, 배우는 게 목적이기도 하니까요.
다만 그걸 성과로 포장하는 순간 문제가 됩니다. “배우려고 규모를 키워봤다”와 “이 규모를 처리했다”는 완전히 다른 말이에요. 저는 후자로 말해왔고, 그게 이 회고를 쓰게 된 이유입니다.
앞으로는 두 가지를 지키려고 해요.
첫째, 숫자를 정할 때 근거를 한 줄이라도 남깁니다. 둘째, 검증값과 실측값을 말할 때 구분합니다.
과하게 만든 것보다 과하게 말한 것이 더 위험하다는 걸 늦게 알았습니다.