개념
이 모듈의 본론
레슨 1부터 6까지는 전부 상태를 만드는 방법 이었다. 조건을 정의하고, 도구를 고르고, 적용을 확인하고, Limited Service를 재현했다.
이 레슨이 본론이다. 그래서 무엇을 볼 것인가.
네트워크 테스트는 '느려지는지'가 아니라 '느려졌을 때 제품이 어떻게 행동하는지' 를 본다.
조건을 걸어놓고 "아, 느리네요" 하고 끝내는 테스트를 아주 많이 본다. 그건 도구 동작 확인이지 제품 테스트가 아니다.
저하 상태에서 확인할 8가지
① 타임아웃 정책 몇 초 뒤 어떤 메시지를 보여주는가. 무한 로딩은 없는가
② 재시도 동작 자동 재시도 횟수·간격이 통제되는가. 수동 수단이 있는가
③ 중복 처리 응답 지연 중 연타 시 중복 결제·중복 등록이 나는가
④ 부분 로딩 일부만 로드된 화면에서 레이아웃이 무너지지 않는가
⑤ 오프라인 처리 캐시 화면·안내 배너가 뜨고 입력값이 보존되는가
⑥ 데이터 정합성 업로드 중 끊긴 뒤 서버와 클라이언트 상태가 일치하는가
⑦ 회선 전환 Wi-Fi ↔ 셀룰러 전환 시 세션이 유지되는가
⑧ 자원 소모 재시도 폭주로 발열·배터리·데이터 소모가 과도한가여덟 개 중 ③과 ⑥이 돈과 직결된다. 중복 결제와 주문 유실이 여기서 나온다. 나머지는 사용자 경험 문제지만, 이 둘은 사고다.
① 타임아웃 정책 — 예산으로 생각한다
타임아웃은 하나가 아니다
"타임아웃 30초"라고만 말하는 팀이 많은데, 실제로는 여러 단계가 있다.
DNS 해석 도메인 → IP
+
TCP 연결 connect timeout
+
TLS 핸드셰이크
+
요청 전송 ~ 첫 바이트 수신 read timeout
+
본문 전체 수신
─────────────────────────
= 전체(total) timeout각 단계 타임아웃의 합이 전체 타임아웃보다 크면, 전체 값은 의미가 없어진다. 반대로 연결 타임아웃만 짧게 잡고 읽기 타임아웃을 안 걸면 응답이 영원히 안 와도 계속 기다린다.
QA가 확인할 것:
- connect / read / total 타임아웃이 각각 설정되어 있는가
- 그 합이 사용자가 기다릴 수 있는 시간 안에 들어오는가
- 화면마다 다른 값을 쓰는가 (전부 같은 값이면 설계 부재를 의심)
화면별 예산 잡기
사용자가 대기 중인 화면 (로그인, 결제 확인)
→ 10~15초. 그 이상은 사용자가 먼저 포기한다
목록·상세 조회
→ 15~20초. 부분 로딩으로 먼저 보여줄 수 있으면 더 짧게
백그라운드 동기화
→ 30~60초. 사용자가 안 보고 있으므로 길어도 된다
파일 업로드
→ 전체 타임아웃보다 '진행률 정체' 로 판단하는 편이 낫다
(예: 30초간 전송 바이트가 안 늘면 실패 처리)"타임아웃이 60초인데 화면에는 스피너만 있다"는 조합이 최악이다. 값 자체보다 그 시간 동안 사용자에게 무엇을 보여주는가 가 더 중요하다.
② 재시도 — 설계 없이 하면 장애를 키운다
나쁜 재시도가 서버를 죽인다
서버가 잠깐 불안정해졌다. 클라이언트 수만 대가 동시에, 즉시, 무한히 재시도한다. 서버는 회복할 틈이 없다. 이게 재시도 폭주(retry storm) 다.
[나쁜 재시도]
실패 → 즉시 재시도 → 실패 → 즉시 재시도 → ...
모든 클라이언트가 같은 순간에 몰린다
[좋은 재시도]
실패 → 1초 대기 → 실패 → 2초 → 실패 → 4초 → 포기
+ 각 대기에 무작위 편차를 섞는다 (지터)
+ 클라이언트마다 다른 순간에 재시도하게 흩어진다지수 백오프와 지터
delay = min(cap, base × 2^attempt) × random()
base 첫 대기 (예: 1초)
2^n 시도할수록 배로 늘린다
cap 상한 (예: 30초). 무한히 늘어나지 않게
random() 지터 — 이게 없으면 모두가 같은 순간에 재시도한다지터가 없는 지수 백오프는 반쪽짜리다. 간격은 벌어지지만 여전히 모든 클라이언트가 같은 타이밍에 몰린다.
QA가 검증할 재시도 항목
- 최대 재시도 횟수 가 있는가 (보통 3~5회). 무한 재시도는 결함이다
- 간격이 늘어나는가 (고정 간격이면 백오프가 없는 것)
- 간격에 무작위성이 있는가 (여러 번 측정해 값이 다른지 본다)
- 4xx는 재시도하지 않는가 — 400·401·403·404는 다시 보내도 같은 결과다
- 429·5xx만 재시도하는가
-
Retry-After헤더를 존중하는가 - 재시도가 여러 계층에서 중복되지 않는가 (SDK + 앱 코드가 각각 3회면 실제로는 9회)
마지막 항목이 놓치기 쉽다. HTTP 클라이언트 라이브러리가 이미 재시도를 하고 있는데 앱 코드가 또 감싸면 곱해진다. 재시도는 한 계층에서만 한다.
어떻게 측정하는가
레슨 4의 프록시가 여기서 가장 유용하다.
1. 특정 API를 항상 503으로 반환하게 설정
2. 앱에서 그 기능을 실행
3. 프록시 세션 목록에서 같은 요청이 몇 번 왔는지 센다
4. 각 요청의 도착 시각 차이를 계산한다
→ 1초, 2초, 4초처럼 늘어나는가?
→ 매번 정확히 같은 값인가? (지터 없음)요청 도착 시각을 적어 간격을 계산하는 것 이 재시도 정책 검증의 실체다. 코드를 안 봐도 동작으로 확인할 수 있다.
③ 중복 처리 — 가장 비싼 결함
왜 저하 상태에서 중복이 나는가
사용자: [주문하기] 클릭
→ 응답이 5초째 안 온다
→ "안 눌렸나?" 하고 다시 클릭
→ 또 안 온다 → 한 번 더
서버: 주문 3건 생성요청은 갔는데 응답이 안 온 상태 가 핵심이다. 사용자도 앱도 성공 여부를 모른다. 여기에 자동 재시도까지 겹치면 더 늘어난다.
방어 수단
| 수단 | 어디서 | 확인 방법 |
|---|---|---|
| 버튼 비활성화 | 클라이언트 | 연타해도 요청이 1건인가 (프록시로 센다) |
| 멱등키 (Idempotency-Key) | 클라이언트 → 서버 | 같은 키로 재요청 시 1건만 생성되는가 |
| 서버 중복 검사 | 서버 | 짧은 시간 내 동일 요청 거부 |
클라이언트 버튼 비활성화만으로는 부족하다. 자동 재시도는 버튼과 무관하게 나가고, 네트워크 레벨 재전송도 있다. 상태를 바꾸는 요청 (결제·주문·등록)에는 멱등키가 있어야 한다.
멱등키 검증 절차
1. 레슨 4의 프록시로 요청을 가로채 Idempotency-Key 헤더가 있는지 확인
2. 있다면: 같은 요청을 그대로 재전송(replay)한다
→ 서버가 새로 만드는가, 기존 결과를 돌려주는가?
3. 없다면: 그 자체가 결함 후보다
4. 키가 재시도마다 새로 생성되지는 않는지 확인
→ 재시도인데 키가 바뀌면 멱등성이 깨진다4번이 실무에서 자주 발견되는 버그다. 멱등키를 넣긴 했는데 재시도할 때마다 새 키를 만들어서 아무 효과가 없는 구현.
④⑤ 부분 로딩과 오프라인 처리
부분 로딩
저하 상태에서는 일부만 도착한 화면 이 오래 유지된다. 정상 속도에서는 찰나라 눈에 안 띄던 문제가 드러난다.
- 이미지가 안 온 자리에 자리표시자(placeholder)가 있는가, 아니면 레이아웃이 밀리는가
- 목록 일부만 로드됐을 때 스크롤이 튀지 않는가
- 값이 없는 필드가
null이나undefined로 화면에 노출되지 않는가 - 일부 실패한 섹션만 별도로 재시도할 수 있는가
세 번째 항목은 저하 상태에서만 잡히는 전형적 결함이다.
오프라인 처리
레슨 6의 레시피 C·D와 겹친다. 핵심은 둘이다.
- 캐시된 내용이라도 보여주는가, 아니면 빈 화면인가
- 입력값이 보존되는가
⑥ 데이터 정합성 — 조용히 어긋난다
가장 늦게 발견되는 문제
업로드 중에 연결이 끊겼다. 클라이언트는 실패로 처리했는데 서버는 받았다. 또는 반대. 화면과 서버가 다른 상태를 믿고 있는 상태 다.
이건 저하 테스트에서 의도적으로 만들지 않으면 절대 안 나온다.
[정합성 시험 절차]
1. 업로드·등록 작업을 시작한다
2. 전송 중간에 연결을 끊는다 (레슨 5의 loss 100% 또는 업링크 차단)
3. 클라이언트 화면 상태를 기록한다
4. 연결을 복구한다
5. 서버 상태를 직접 확인한다 (관리자 화면, DB, API 조회)
6. 두 상태가 일치하는가?
7. 일치하지 않으면 앱이 그것을 발견하고 화해(reconcile)하는가?7번까지 봐야 한다. 어긋나는 것 자체는 피하기 어렵다. 중요한 건 어긋났을 때 앱이 알아채고 맞추는가 다.
확인할 케이스
- 업로드 중 끊김 → 서버에 부분 파일이 남는가
- 등록 중 끊김 → 새로고침하면 등록된 것으로 보이는가
- 삭제 중 끊김 → 목록에서는 사라졌는데 서버에는 남아 있는가
- 결제 중 끊김 → 결제는 됐는데 주문은 없는 상태가 가능한가
마지막은 반드시 확인해야 하는 케이스다.
⑦⑧ 회선 전환과 자원 소모
회선 전환
사용자는 이동한다. Wi-Fi에서 셀룰러로, 셀룰러에서 Wi-Fi로 바뀐다. 이때 IP가 바뀐다.
- 전환 시 세션이 유지되는가. 재로그인을 요구하지 않는가
- 진행 중이던 업로드·다운로드가 이어지는가, 아니면 처음부터인가
- WebSocket·실시간 연결이 재연결되는가
- 전환 직후 요청이 실패하면 재시도로 복구되는가
재현 방법은 간단하다. 모바일 데이터를 켠 상태에서 Wi-Fi를 껐다 켜면 된다. (레슨 6의 조건 테스트와 달리 이때는 모바일 데이터를 켠다.)
자원 소모
재시도 폭주는 서버만 괴롭히는 게 아니다. 클라이언트도 배터리와 데이터를 태운다.
- Limited 조건을 10분 유지했을 때 배터리 소모가 정상 대비 몇 배인가
- 기기가 눈에 띄게 뜨거워지는가
- 데이터 사용량이 비정상적으로 증가하는가 (실패한 요청도 데이터를 쓴다)
- 백그라운드로 보낸 뒤에도 재시도가 계속되는가
마지막 항목이 특히 중요하다. 사용자가 앱을 닫았는데도 백그라운드에서 재시도가 도는 앱이 있다. 배터리 소모 리뷰의 원인이 된다.
결함 리포트 — 조건이 없으면 닫힌다
레슨 1에서 예고한 양식이다. 저하 상태 결함은 조건 없이는 재현되지 않으므로 리포트 형식이 특히 중요하다.
제목 [조건식별자] 증상 요약
조건 조건명 + 4축 수치 + 사용한 도구/스크립트
측정 적용 확인 결과 (스크린샷 첨부)
절차 1. ... 2. ... 3. ...
실제 관찰된 동작 (재시도 횟수, 경과 시간 등 수치 포함)
기대 무엇이 일어나야 했는가 + 근거
증거 로그, 프록시 세션 캡처, 화면 녹화"실제" 칸에 수치를 넣는 것 이 핵심이다. "재시도를 많이 함"이 아니라 "12초간 7회 재시도, 간격 고정 1.7초"라고 적으면 개발자가 바로 코드를 찾아간다.
실무에서 왜 필요한가
중복 결제 사고를 미리 막는다
중복 결제는 환불 처리 비용, CS 응대, 신뢰 손실이 전부 따라오는 사고다. 그리고 거의 항상 네트워크 저하 상황에서 발생한다. 정상 속도에서는 응답이 빨라 사용자가 연타할 틈이 없다.
레시피 A(극단 저속) 조건에서 결제 버튼을 연타해보는 5분짜리 테스트 가 이 사고를 막는다.
앱스토어 리뷰의 상당수가 이 영역이다
"자꾸 멈춰요", "배터리가 빨리 닳아요", "글 쓴 게 날아갔어요" — 전부 이 여덟 가지 체크포인트에 해당한다. 기능은 다 되는데 리뷰가 나쁜 앱은 대개 여기가 약하다.
개발과의 대화가 구체적이 된다
"네트워크 처리가 부실합니다"는 대화가 안 된다. "재시도 간격이 고정 1.7초이고 상한이 없습니다. 지수 백오프와 지터를 넣고 5회로 제한하는 게 어떨까요"는 대화가 된다.