이 글은 REGXA에 대한 부정적인 리뷰를 쓰기 위한 것이 아니다. 누군가에게 REGXA의 VPS를 사야 한다거나 사지 말아야 한다고 말하려는 것도 아니다. 개발자로 일하다 겪은 하루를 기록하는 것뿐이다.
2 vCPU, 2 GB RAM, 60 GB NVMe 구성의 KVM VPS로 평범한 workload를 옮겼다. Nginx는 실행 중이었고 backend도 실행 중이었다. 그런데 서버는 심하게 overloaded된 것처럼 동작했다. 요청이 쌓이고, TLS 작업이 느려지고, 연결이 오래 열려 있었으며, 일부 요청은 결국 HTTP 504를 반환하기까지 2분이 넘게 걸렸다.
CPU accounting을 확인하면서 조사 방향이 바뀌었다
처음에는 VPS 내부의 어떤 process가 CPU를 많이 사용한다고 생각했다. 그래서 mpstat를 실행했다.
Average CPU steal: 92.58%
CPU 0 steal: 90.62%
CPU 1 steal: 94.57%
CPU user: 4.02%
CPU system: 1.47%
CPU iowait: 0.41%
CPU idle: 0.53%
개별 sample에서도 약 89–98%의 steal이 반복적으로 나타났다. CPU pressure도 매우 높았다.
CPU PSI some avg10: 79.47
CPU PSI some avg60: 75.32
CPU PSI some avg300: 76.31
Load average: 5.85 / 5.75 / 5.73
vCPU가 두 개뿐인 VM이었다. Linux에는 CPU를 기다리는 runnable process가 있었다. 중요한 숫자는 일반적인 application CPU usage가 아니라 %steal이었다.
높은 CPU 사용률과 CPU steal은 다른 문제다
application이 실제로 processor를 많이 쓰고 있었다면 user나 system 수치가 높아야 한다. 하지만 VPS는 대략 다음과 같았다.
user: 4%
system: 1%
steal: 93%
CPU steal은 virtual CPU가 실행할 준비가 되어 있지만 hypervisor가 schedule하지 않은 시간이다. 따라서 VPS에서 CPU time을 빼앗기고 있었다고 표현할 때는 이 virtualization상의 기술적 의미를 말한다. REGXA가 내게 독점적으로 배정된 physical core를 의도적으로 다른 고객에게 넘겼다고 증명할 수는 없고, guest metric으로 의도를 판단할 수도 없다. 다만 guest에 실행할 작업이 있었음에도 CPU scheduling time을 받지 못했다는 사실은 측정할 수 있었다.
production traffic을 전부 제거했다
여전히 합리적인 반론은 남아 있었다. 내 workload 자체가 문제일 수도 있었다. 그래서 그 변수를 제거했다. active workload를 다른 곳으로 옮기고, 이 VPS로 들어오는 production traffic을 중단한 뒤 queue가 비워질 때까지 기다리고 같은 CPU 측정을 반복했다.
Average CPU steal: 94.17%
CPU 0 steal: 95.56%
CPU 1 steal: 92.83%
CPU user: 1.95%
CPU system: 0.59%
CPU iowait: 0.59%
CPU idle: 2.18%
결과는 오히려 더 나빠졌다. 개별 vmstat sample에서도 91–98%의 steal이 계속 나타났고, 최대 15개의 runnable process가 CPU를 기다렸다. 핵심 조합은 다음과 같았다.
user: 1.95%
system: 0.59%
steal: 94.17%
application은 거의 아무 일도 하지 않았지만 VM은 여전히 scheduling time의 대부분을 잃고 있었다. 정상적인 production load는 더 이상 설득력 있는 원인이 아니었다.
localhost조차 비정상적으로 느려졌다
127.0.0.1을 통해 HTTPS도 테스트했다. 이렇게 하면 public DNS, ISP, 지리적 거리, 외부 network path를 테스트에서 제외할 수 있다.
traffic이 있을 때 localhost HTTPS 10회 중 4회가 TLS handshake 단계에서 실패했다. 성공한 요청도 29.30, 22.77, 12.25, 11.87, 11.12, 9.40초가 걸렸다. 일부 TLS handshake만 해도 약 9초가 필요했다.
production traffic을 제거한 후 localhost 성능은 나아졌지만 여전히 불안정했다. 0.061, 0.745, 0.830, 0.873, 1.010, 1.117, 1.121, 2.188, 3.355초가 측정됐다. 같은 local operation이 한 번은 약 61 ms, 다음에는 3.3초 이상 걸릴 수 있었다.
나머지 stack에서도 결과가 드러났다
한 시점에는 약 450개의 established connection, 122개의 orphaned connection, 110개의 FIN-WAIT-1, 33개의 CLOSE-WAIT가 있었다. localhost backend의 listen queue는 약 14–15, HTTPS queue는 약 40까지 늘어났다.
Nginx에는 142.857, 138.902, 135.064, 129.819, 128.657초 후 HTTP 504가 반환된 기록이 있었다. 다른 요청은 약 67–130초 동안 열린 상태로 남았다. 35–41초 정도의 secure-connect timeout, database network timeout, 지연된 TLS 작업도 확인했다.
각각만 보면 Nginx, database, network, backend의 서로 다른 문제처럼 보일 수 있다. 그러나 timeout을 늘린다고 hypervisor가 schedule하지 않는 CPU time이 생기는 것은 아니다.
RAM과 disk는 원인이 아니었다
VPS에는 약 1.0–1.1 GiB의 available RAM이 남아 있었고 swap 사용량은 거의 없었다. OOM event나 OOM killer 동작도 없었다. filesystem 사용률은 약 20%였고 약 44 GB가 남아 있었다. 중요한 CPU 측정 중 I/O wait는 1% 미만이었다.
memory가 부족한 것도, disk가 가득 찬 것도 아니었고, application이 사라진 CPU를 소비하고 있던 것도 아니었다. 지배적인 metric은 계속 %steal이었다.
정상적인 KVM VPS는 완전히 다르게 보였다
정상 traffic을 처리 중인 다른 KVM VPS에서 같은 종류의 진단을 실행했다.
Average CPU steal: 0.02%
CPU idle: 87.86%
CPU PSI avg10: 0.29
CPU PSI avg60: 0.63
CPU PSI avg300: 0.49
Load average: 0.47 / 0.33 / 0.14
localhost HTTPS 10회는 약 37–69 ms 사이에 끝났다. 이후 실제로 busy한 다른 production VPS를 측정했을 때는 user CPU 61.71%, system CPU 5.08%, idle 24.09%, steal은 0.13%에 불과했다.
지금은 이 차이를 가장 중요하게 본다. 내 software가 실제로 processor를 사용하기 때문에 VPS가 바쁜 상황과, guest가 측정된 CPU time의 90% 이상을 hypervisor를 기다리는 데 쓰는 상황은 전혀 다르다.
REGXA는 결국 infrastructure contention을 확인했다
VM 내부에서는 guest를 측정할 수 있지만 physical host, scheduler configuration, CPU quota, 이웃 VM은 볼 수 없다. REGXA는 볼 수 있다.
지원팀은 결국 이 VPS가 shared CPU infrastructure에서 운영되고 있으며 CPU resource가 여러 virtual machine 사이에서 공유되고 physical node load에 따라 performance가 달라질 수 있다고 설명했다. 또한 Frankfurt infrastructure가 당시 특히 높은 demand를 겪고 있었고, 내가 관측한 높은 CPU steal을 resource contention on the underlying infrastructure의 결과라고 명시적으로 설명했다.
현재 infrastructure에서는 해당 shared VPS의 CPU quota나 scheduling policy를 변경하거나 추가 dedicated CPU resource를 줄 수 없다고도 했다. 기술적인 해결책으로는 utilization이 낮은 location으로 이전하는 방안을 제안했다.
이 설명은 내가 이전에 봤던 dedicated CPU cores와 guaranteed resources라는 표현과 쉽게 맞지 않았다. 정확한 host configuration을 볼 수 없으므로 직접적인 메커니즘이 CPU overcommitment, quota, scheduler weighting, throttling 또는 그 조합이었는지는 단정할 수 없다. 의도 역시 증명할 수 없다. 그럴 필요도 없다. Linux는 지속적으로 92–94%의 steal을 보여줬고, REGXA 스스로 그 높은 steal을 shared infrastructure의 contention과 연결했다.
환불을 받는 데도 추가적인 설득이 필요했다
infrastructure 문제가 명확해진 뒤에는 VPS를 계속 다른 node로 옮겨가며 시험하고 싶지 않았다. service를 취소하고 돈을 돌려받고 싶었다.
처음에는 일부 금액만 제안받았고, 환불도 결제한 카드가 아니라 REGXA account balance로 넣어주는 방식이었다. 서비스를 떠나려는 상황에서 provider credit은 내게 실제 환불과 같지 않았다.
그래서 계속 답변하며 결제금의 100%를 original payment method로 돌려달라고 요청했다. 결국 REGXA는 동의했고 전액을 원래 결제수단으로 돌려줬으며, full refund를 exception이라고 설명했다.
최종적으로 전액을 돌려준 점은 인정한다. 하지만 support가 infrastructure contention을 이미 인정한 뒤에도 financial resolution을 위해 계속 밀어붙여야 했다는 점 역시 내 경험의 일부다.
이 경험 이후 남은 원칙
가장 하기 쉬운 실수는 application optimization을 계속하는 것이었다. Nginx 설정을 바꾸고, timeout을 늘리고, concurrency를 낮추고, retry를 추가하고, MongoDB를 조사하거나 backend code를 다시 쓸 수도 있었다. 일부 증상은 달라졌을지 모른다. 하지만 중요한 질문에는 답하지 못한다. 왜 CPU steal이 90%를 넘는가?
이제는 SSH가 되고, Nginx가 시작되고, health endpoint가 200을 반환하는지만 확인하지 않는다. 새 VPS에서는 %user, %system, %iowait, %idle, %steal, CPU PSI, run queue, localhost latency도 확인한다.
그리고 이상하면 workload를 제거한 뒤 다시 측정한다. 이 사건에서는 그 테스트가 가장 명확한 결과를 만들었다.
CPU user: 1.95%
CPU system: 0.59%
CPU steal: 94.17%
모든 REGXA VPS가 이렇게 동작한다고 주장하는 것은 아니다. 한 대의 VPS를 테스트했고 그 머신에서 일어난 일을 기록했을 뿐이다. 그러나 그 VPS에서는 증거가 매우 명확했다. application은 거의 CPU를 쓰지 않았는데 Linux는 virtual CPU time 대부분이 hypervisor scheduling을 기다리느라 사라지고 있다고 보여줬다. 이후 REGXA도 바로 그 현상을 shared infrastructure의 resource contention 때문이라고 설명했다.
그래서 이 경험을 기록한다. 점수나 추천이 아니라 개발자로서 겪은 하루의 기록이며, 앞으로 VPS에서 절대 무시하지 않을 metric을 알게 된 사건이기 때문이다.