개념
실행 인프라는 성능 문제가 아니라 용량 문제다
"테스트가 느리다"는 말에는 두 개의 다른 문제가 섞여 있다. 하나는 테스트 하나가 오래 걸리는 문제(성능), 다른 하나는 테스트 묶음 전체가 제 시간에 안 끝나는 문제(용량) 다. 전자는 대기 전략과 테스트 설계로 줄이고, 그건 m13·m55 의 영역이다. 이 레슨은 후자를 다룬다.
용량 문제로 보는 순간 질문이 바뀐다. "왜 느릴까"가 아니라 "몇 개를 동시에 돌릴 수 있고, 수요가 그보다 많을 때 얼마나 기다리는가" 가 된다. 이 질문은 테스트 코드를 전혀 보지 않고도 답할 수 있고, 답에 따르는 조치도 코드 수정이 아니다 — 설비를 늘리거나, 수요를 줄이거나, 둘 다다.
실행 설비 세 종류와 각각의 운영 부담
| 설비 | 무엇을 제공하는가 | 운영에서 실제로 손이 가는 곳 |
|---|---|---|
| 러너(runner / agent / executor) | 테스트 프로세스를 실행하는 컴퓨트 | 실행 이미지 버전, 의존성 캐시, 작업 공간 정리, 비밀값 주입, 동시 실행 상한 |
| 브라우저 그리드(Selenium Grid 등) | 브라우저 세션을 원격으로 제공 | 브라우저·드라이버 버전 조합, 세션 대기열, 좌석 수, 세션 누수 |
| 디바이스 팜 | 실기기·에뮬레이터 단말 | 단말 재고와 상태(배터리·OS 버전), 점유 시간, 초기화, 설치 실패 |
세 설비는 직렬로 쌓인다. 러너가 비어 있어도 그리드 좌석이 없으면 기다리고, 좌석이 있어도 단말이 점유 중이면 기다린다. 그래서 용량을 볼 때는 가장 좁은 구간 하나를 찾아야 한다. 러너를 두 배로 늘렸는데 완료 시간이 안 줄었다면, 병목은 러너가 아니었던 것이다.
러너 — 호스티드와 셀프호스티드의 운영 차이
관리형 호스티드 러너는 실행마다 깨끗한 환경을 받는다는 큰 장점이 있다. 대신 매 실행이 의존성 설치부터 시작하므로 준비 시간이 고정비로 붙고, 사내망 안의 테스트 대상에 접근하려면 별도 경로가 필요하다. 셀프호스티드 러너는 그 둘을 해결하지만 깨끗함을 스스로 보장해야 한다. 실무에서 셀프호스티드가 만드는 문제는 거의 늘 이 셋이다.
- 잔여 상태 오염 — 앞 실행이 남긴 파일·프로세스·포트·캐시가 다음 실행 결과를 바꾼다. 매 실행 전 작업 공간을 지우고, 컨테이너로 한 겹 감싸는 것이 기본이다.
- 디스크 고갈 — 아티팩트·이미지 레이어·캐시가 쌓여 어느 날 전부 실패한다. 용량 경보와 주기적 정리(prune)를 설비 쪽 운영 작업으로 등록해 둬야 한다.
- 버전 표류 — 러너 A 와 러너 B 의 브라우저·런타임 버전이 달라져, 같은 테스트가 어느 러너에 배치되느냐에 따라 결과가 갈린다. 이건 플래키로 오진되기 가장 쉬운 원인이다.
세 문제의 공통 해법은 실행 환경을 이미지로 고정하고 그 이미지 태그를 결과에 기록 하는 것이다. m14 레슨 3에서 다룬 Docker 재현성이 여기서 운영 규칙으로 올라온다 — 재현 가능한 이미지를 만드는 것(m14)과, 어느 실행이 어느 이미지 태그로 돌았는지를 추적 가능하게 유지하는 것(m59)은 다른 일이다.
러너 풀 운영 체크 항목 (주 1회)
- 풀의 각 러너가 보고하는 이미지 태그가 모두 같은가
- 디스크 여유가 임계 이하인 러너가 있는가 (임계: 전체의 20% 권장)
- 마지막 24시간 안에 '오프라인' 으로 전환된 러너가 있는가
- 작업 공간 정리 단계가 실패한 실행이 있는가 — 실패하면 다음 실행이 오염된다
- 러너별 평균 대기 시간 편차가 큰가 — 라벨 배치가 한쪽으로 몰렸다는 신호그리드 — 좌석 수와 세션 누수
Selenium Grid 는 브라우저 세션을 중앙에서 배분하는 설비다. 구성 형태는 단독 (standalone), 허브-노드(hub and node), 그리고 역할을 쪼갠 완전 분산형이 있고, 분산형은 라우터·디스트리뷰터·세션 대기열·세션 맵·이벤트 버스 같은 구성 요소로 나뉜다. 구성 요소의 정확한 이름과 기동 옵션은 버전마다 바뀌므로 공식 문서 원문 확인이 필요하다.
운영 관점에서 중요한 것은 구성도가 아니라 세 숫자다.
- 총 좌석 수 — 노드들이 제공하는 동시 세션 수의 합. 브라우저별로 따로 센다.
- 세션 대기열 길이 — 좌석을 기다리는 요청 수. 이 값이 지속적으로 0 이 아니면 좌석이 부족하다는 뜻이다.
- 세션 평균 수명 — 테스트 하나가 좌석을 점유하는 시간. 이 값이 서서히 늘고 있으면 세션 누수(테스트가 끝나며 브라우저를 닫지 않음)를 의심한다.
세션 누수는 그리드 운영의 고전적 사고다. 좌석이 하나씩 잠기고, 어느 시점부터 전체 실행이 대기로 밀리고, 겉보기로는 "오늘 유독 느리다"로 나타난다. 대책은 두 겹이다 — 테스트 쪽에서 종료를 보장하는 것(픽스처 해제)과, 설비 쪽에서 세션 유휴 제한을 둬 오래 잡힌 세션을 강제 회수하는 것. 설비 쪽 방어가 없으면 테스트 하나의 버그가 전체 용량을 먹는다.
디바이스 팜 — 단말은 재고다
모바일 실행 설비는 소프트웨어가 아니라 재고처럼 다뤄야 한다. 단말에는 상태가 있고, 상태는 시간이 지나면 나빠진다.
| 선택지 | 적합한 상황 | 운영에서 드는 비용 |
|---|---|---|
| 로컬 에뮬레이터·시뮬레이터 | 기능 회귀의 대부분, 개발 중 빠른 확인 | 러너의 CPU·메모리, 기동 시간, 그래픽 가속 설정 |
| 사내 실기기 팜 | 기기 고유 문제(카메라·센서·지문·푸시), 보안상 외부 금지 | 단말 구매·보관·전원·OS 업데이트 통제, 케이블·허브 고장 |
| 클라우드 디바이스 팜 | 폭넓은 단말·OS 조합 커버, 단기 수요 급증 | 좌석 동시성 제약, 네트워크 지연, 사내망 접근 경로, 재현 시 같은 단말 확보 |
실무 기본형은 섞어 쓰는 것이다. 에뮬레이터로 넓게 자주 돌리고, 실기기는 좁게 정기적으로 돌린다. m13 레슨 8(크로스 브라우저·디바이스 전략)이 "어느 조합을 고를 것인가"를 다뤘다면, 여기서는 고른 조합을 유지하는 비용을 본다. 단말 재고 장부에는 최소한 이 네 열이 필요하다 — 모델·OS 버전·마지막 정상 실행 시각·현 상태(가용/점검/폐기).
단말 운영에서 가장 흔한 숨은 비용은 설치 실패 다. 앱 설치가 실패하면 테스트는 "실패"로 기록되지만 제품과는 아무 상관이 없다. 설치 단계를 테스트 본문이 아니라 준비 단계로 분리해 별도 상태 코드로 기록하면, 이 소음이 통과율 지표에서 빠진다. 레슨 7의 트리아지 규칙이 이 신호를 쓴다.
동시 실행 수를 정하는 계산
필요한 동시 실행 수는 추측하지 않고 계산한다. 네 숫자만 있으면 된다.
N = 테스트 개수
T = 테스트 평균 실행 시간(초) ← 꼬리값이 크면 평균보다 p90 을 쓴다
D = 목표 완료 시간(초)
O = 실행 1회의 고정 준비 시간(초) — 체크아웃·의존성·이미지 준비
필요 동시 실행 수 C ≈ (N × T) / (D − O)
예) N=600, T=25초, D=1200초(20분), O=180초
C ≈ (600 × 25) / (1200 − 180) = 15000 / 1020 ≈ 14.7 → 15여기에 두 가지 보정을 반드시 넣는다.
첫째, 활용률 보정. 대기열이 있는 시스템은 활용률이 올라갈수록 대기 시간이 비선형으로 늘어난다. 설비를 수요에 딱 맞추면(활용률 100%) 작은 변동에도 대기가 크게 튄다. 그래서 산정값에 여유를 얹는다 — 평상시 활용률 목표를 70% 전후로 두는 것이 운영하기 편한 지점이다. 위 예라면 15가 아니라 20 전후를 확보한다.
둘째, 상한 제약 확인. 계산값이 커도 실제로 올릴 수 없는 경우가 많다. 상한을 만드는 요인은 네 가지다.
- 테스트 대상 시스템의 처리량 — 동시 실행을 올리면 대상 서버가 먼저 무너진다. 테스트가 대상을 부하 테스트하는 상태가 되면 결과 전체가 의미를 잃는다.
- 데이터 충돌 — 같은 계정·같은 레코드를 여러 실행이 동시에 건드린다. 레슨 3의 데이터 격리가 풀리지 않으면 병렬을 올릴 수 없다.
- 좌석·라이선스 동시성 — 그리드 좌석, 클라우드 팜 동시 세션 수.
- 러너 호스트 자원 — 브라우저 하나가 쓰는 메모리를 기준으로 호스트당 가능한 동시 수가 정해진다. 이 값을 넘기면 느려지고, 느려지면 대기가 터지고, 대기가 터지면 플래키가 늘어난다.
마지막 항목이 실무에서 가장 과소평가된다. 자원이 모자란 러너에서 생기는 타임아웃은 테스트 코드의 플래키로 오진된다. 병렬 수를 올린 다음 주에 플래키율이 올랐다면, 테스트를 의심하기 전에 러너의 CPU·메모리 사용률을 본다.
셀프호스티드와 클라우드를 고르는 기준
도구별 요금과 무료 할당량은 자주 바뀌므로 숫자로 외우지 않는다. 대신 바뀌지 않는 판단 축 으로 고른다.
| 판단 축 | 셀프호스티드가 유리 | 클라우드·관리형이 유리 |
|---|---|---|
| 수요 패턴 | 하루 종일 꾸준히 높다 | 간헐적·돌발적으로 몰린다 |
| 대상 접근 | 사내망·VPN 안에만 있다 | 공개망에서 접근 가능하다 |
| 설비 다양성 | 소수 조합만 쓴다 | 브라우저·OS·단말 조합이 넓다 |
| 운영 인력 | 설비를 볼 사람이 있다 | 없다 — 그 시간을 테스트에 쓰고 싶다 |
| 보안·데이터 | 테스트 데이터에 민감정보가 섞인다 | 합성 데이터만 쓴다 |
| 재현 요구 | "같은 환경에서 다시"가 중요하다 | 넓은 커버리지가 더 중요하다 |
실무 결론은 보통 혼합이다. 상시 수요는 셀프호스티드로 받고, 돌발 수요와 넓은 조합은 클라우드로 받는다. 이때 중요한 것은 같은 테스트가 어느 설비에서 돌았는지를 결과에 기록하는 것 이다. 기록이 없으면 "클라우드에서만 깨지는 테스트" 같은 패턴을 영원히 발견하지 못한다. 레슨 6의 상관 식별자에 설비 종류를 넣는 이유가 여기 있다.
실행이 밀릴 때 — 감축 계획을 미리 쓴다
용량이 부족한 순간에 무엇을 줄일지 정하는 것은 급할 때 할 결정이 아니다. 평상시에 순서를 정해 문서로 두고, 밀릴 때 그 순서대로 집행한다. 아래가 기본 순서이고, 위쪽이 먼저다 — 정보 손실이 적은 것부터 줄인다.
감축 순서 (실행 지연이 목표 완료 시간을 넘길 때)
1. 아티팩트 수집 축소
통과한 테스트의 비디오·트레이스를 끈다. 실패 건만 남긴다.
(정보 손실 거의 없음 — 통과 아티팩트는 대개 보지 않는다)
2. 중복 설비 조합 축소
브라우저·단말 조합을 핵심 1~2개로 줄인다. 나머지는 야간·주간으로 미룬다.
3. 실행 빈도 하향
PR 마다 돌던 전체 묶음을 머지 후로, 머지마다 돌던 것을 시간당으로 내린다.
4. 범위 축소 — 변경 영향 기반
변경된 모듈과 연관된 테스트만 돌린다. 나머지는 야간 전체 실행이 받는다.
5. 우선순위 하위 테스트 보류
비즈니스 위험이 낮은 테스트를 명시적으로 '보류' 표시하고 목록을 남긴다.
(여기서부터 정보 손실이 실재한다 — 보류 목록은 매주 리뷰한다)
6. 설비 증설 요청
1~5를 적용한 뒤에도 못 맞추면 용량 산정 근거와 함께 증설을 요청한다.순서가 거꾸로 가는 조직이 많다. 밀리면 먼저 "테스트를 지우자"는 말이 나오는데, 그건
5번이다. 14를 집행하지 않은 상태에서 5번으로 가면 커버리지만 잃고 원인은 그대로
남는다. 반대로 15를 다 하고도 못 맞춘다면 그때의 증설 요청은 설득력이 다르다 —
"줄일 건 다 줄였다"는 근거가 붙기 때문이다.
감축 계획에 반드시 들어가야 하는 한 줄이 있다. 감축은 기본값이 아니라 한시 조치이고, 해제 조건을 함께 쓴다. 해제 조건이 없는 감축은 조용히 영구화되고, 반년 뒤에 아무도 비디오 수집이 꺼져 있는 것을 모르게 된다.
실무에서 왜 필요한가
실행이 밀리는 상태는 품질 활동 전체를 서서히 무력화한다. 완료가 늦으면 개발자는 결과를 기다리지 않고 다음 작업으로 넘어가고, 넘어간 뒤에 온 실패는 맥락이 사라져 조사 비용이 몇 배로 뛴다. 그래서 완료 시간은 단순한 편의 지표가 아니라 피드백이 행동으로 이어지는지를 결정하는 변수 다.
용량 산정이 문서로 있으면 대화의 성격이 바뀐다. "테스트 좀 빨리 되게 해 주세요"는 요청이 아니라 불만이고, 받는 쪽에서 할 수 있는 일이 없다. 반면 "현재 600개 × 25초를 20분에 맞추려면 동시 15가 필요하고 지금은 8이다. 1~4번 감축을 적용하면 24분, 증설 없이는 목표 미달"은 결정 가능한 선택지다. 인프라 예산 요청이 통과하는 자료는 거의 항상 이 형태를 갖는다.