Cloudflare Workers 메모리 지표로 배포 후 메모리 회귀와 Exceeded Memory 오류 찾기
배포하고 나서 Worker 오류가 늘었는데 원인이 메모리인지 애매할 때가 있어요. Error 1102가 보인다고 바로 메모리 문제라 하기엔 다른 리소스 한도 초과일 수도 있고, 요청 하나만 로컬에서 돌려보면 또 멀쩡함. 꽤 답답한 조합이죠.
Cloudflare는 2026년 6월 30일 Workers와 Durable Objects 대시보드에 Memory Usage 차트를 추가했어요. 이제 배포 마커 전후의 P50·P90·P99·P999를 비교하고, 실제 Exceeded Memory 오류와 힙 스냅샷까지 연결하면 메모리 회귀의 범위를 훨씬 구체적으로 좁힐 수 있습니다.
다만 이 차트는 요청 하나가 단독으로 쓴 메모리를 보여주는 게 아니에요. 여러 동시 요청이 공유하는 V8 isolate 전체 메모리를 호출 시점에 표본화한 값입니다. 이 차이부터 알고 봐야 차트 해석이 산으로 가지 않음.
이 글은 2026년 7월 26일 기준 Cloudflare 공식 문서를 바탕으로 정리했습니다.

Memory Usage 차트는 요청별 사용량이 아니다
차트 위치는 Workers & Pages → 대상 Worker → Metrics예요. Durable Object namespace의 Metrics에서도 확인할 수 있습니다. 각 호출 시점에 그 호출을 처리하던 isolate의 메모리 사용량을 reservoir sampling으로 표본화하고, 결과를 네 가지 백분위로 나눠 보여줘요. 같은 차트에 배포 마커도 표시됩니다.
| 지표 | 의미 | 먼저 볼 부분 |
|---|---|---|
| P50 | 표본 절반이 이 값 이하인 중앙값 | 배포 뒤 평상시 기준선이 높아졌는지 |
| P90 | 표본 90%가 이 값 이하 | 자주 나타나는 고메모리 패턴이 늘었는지 |
| P99 | 표본 99%가 이 값 이하 | 특정 경로·큰 입력·버스트에서 치솟는지 |
| P999 | 표본 99.9%가 이 값 이하 | 아주 드문 스파이크가 128 MB에 접근하는지 |
여기서 제일 중요한 포인트. Workers의 메모리 한도는 요청당이 아니라 isolate당 128 MB예요. Free와 Paid 모두 같고 JavaScript heap과 WebAssembly 할당도 포함됩니다. 한 isolate가 여러 요청을 동시에 처리하며 전역 객체와 캐시 등을 공유하니, P999가 튀었다고 특정 URL 하나가 그 메모리를 전부 썼다고 단정하면 안 돼요.
또 reservoir sampling 기반이라 모든 호출을 빠짐없이 센 원시 로그도 아닙니다. 높은 백분위는 위험 신호를 찾는 용도, 실제 실패 건수는 오류 지표와 로그로 따로 확인. 역할 분담이 딱 이렇습니다.
참고로 Worker metrics는 최대 3개월 보존되며 대시보드에서 한 번에 선택할 수 있는 구간은 최대 1주예요. 최근 몇 분은 집계 전달 지연 때문에 트래픽이 줄어든 것처럼 보일 수도 있습니다.
출처: Workers 변경 로그, Metrics and analytics, Workers Limits
배포 마커 전후를 같은 조건으로 비교한다
먼저 배포 직전과 직후가 함께 들어오도록 시간 구간을 잡아요. 그냥 앞뒤 그래프만 비교하면 안 되고, 가능하면 같은 요일과 비슷한 시간대처럼 트래픽 성격도 맞춰야 합니다.
함께 볼 조건은 요청·응답 크기, 주요 URL 경로 구성, 동시 요청 수, 버스트 여부, 기능 플래그, 캐시 warm/cold 상태, WebAssembly 사용 여부예요. gradual deployment 중이라면 버전별 트래픽 비율도 맞춰 봅니다.
Workers Metrics UI에서는 그래프 구간을 드래그해 선택하고 범례에서 버전을 비교할 수 있어요. 점진 배포라면 전체 평균 하나만 보는 것보다 구버전과 신버전을 동시에 띄워 새 버전에만 변화가 몰리는지 확인하는 편이 훨씬 낫습니다.

백분위 모양은 원인 후보를 줄이는 데 꽤 쓸 만해요.
-
P50·P90·P99가 함께 계단식 상승했다면 번들 초기화, 전역 캐시, 공통 미들웨어, 라이브러리 교체처럼 대부분의 호출이 거치는 코드부터 확인합니다.
-
P50은 그대로인데 P99/P999만 상승했다면 큰 payload, 특정 라우트, 압축·파싱, 일부 고객 데이터, 높은 동시성처럼 드문 조건을 먼저 의심해요.
-
모든 백분위가 시간에 따라 계속 상승한다면 해제되지 않는 전역 참조, 상한 없는 Map·Set·cache, 누적 문자열과 배열 같은 leak 후보를 살펴봅니다.
-
P999만 잠깐 튀고 오류가 없다면 드문 고메모리 isolate가 있었다는 신호일 뿐, 128 MB 초과 실패가 발생했다고 확정할 수는 없어요.
이 분류는 백분위의 의미에서 끌어낸 운영상 가설입니다. Cloudflare가 보장하는 자동 판정 규칙은 아니에요. 배포 마커와 상승 시점이 겹쳐도 같은 순간 대형 요청이나 cache miss, 특정 테넌트 집중이 시작됐다면 코드 회귀가 아닐 수 있습니다. 그래프가 너무 그럴듯하면 바로 범인 취급하고 싶지만… 상관관계와 원인은 따로 확인해야 함.
출처: Revamped Workers Metrics, Metrics and analytics
Exceeded Memory를 오류 지표에서 따로 확인한다
메모리 백분위가 올랐다면 다음은 Metrics → Errors → Invocation Statuses → Exceeded Memory 순서예요. Analytics와 Logpush에서는 invocation outcome exceededMemory를 확인합니다.
메모리 한도를 넘을 때 클라이언트에는 Error 1102와 Worker exceeded resource limits 메시지가 나타날 수 있어요. 근데 Error 1102나 상위 요약의 Exceeded Resources만으로 메모리 문제라고 확정하면 곤란합니다. Exceeded Resources에는 CPU time, startup time, free-tier limit 같은 다른 자원 초과도 포함될 수 있거든요.
즉 높은 P999와 실제 오류도 별개로 봐야 해요.
- P999가 128 MB에 가까움: 메모리 여유가 줄어든 위험 신호
Exceeded Memory또는exceededMemory확인: 실제 메모리 초과 실패의 근거- Error 1102 또는
Exceeded Resources만 확인: 메모리 외 원인도 남아 있음
Memory limit would be exceeded before EOF 메시지가 보인다면 큰 body를 끝까지 읽어 메모리에 올리는 경로부터 확인할 만합니다. response.text(), response.json(), arrayBuffer()처럼 전체 body를 버퍼링하는 코드가 우선 후보예요.
128 MB를 넘었다고 진행 중인 모든 요청이 즉시 똑같이 죽는 것도 아닙니다. 런타임은 진행 중인 요청을 완료하도록 두고 다음 요청을 위해 새 isolate를 만들어요. 다만 부하가 극도로 높으면 안정성을 위해 일부 새 요청을 취소할 수 있습니다. 새 isolate가 생긴다고 누수가 해결되는 건 당연히 아니고, 같은 누적 패턴이면 다시 차오름.
출처: Workers Limits, Errors and exceptions
힙 스냅샷으로 남는 객체와 코드 위치를 찾는다
대시보드가 알려주는 건 “언제, 어느 정도, 어느 버전에서”예요. 실제로 어떤 객체가 남았고 어느 코드가 키웠는지는 로컬 DevTools에서 좁혀야 합니다.
Cloudflare 공식 절차는 다음과 같아요.
wrangler dev로 Worker를 실행합니다.- 터미널에서
D를 눌러 DevTools를 엽니다. - Memory 탭으로 이동해요.
- 대시보드에서 좁힌 운영 조건과 비슷한 요청을 보냅니다.
Take snapshot으로 힙 스냅샷을 찍습니다.- 같은 workload를 더 실행한 뒤 다시 스냅샷을 찍고, 계속 늘어나는 객체 type과 retained object를 비교합니다.
스냅샷 한 장의 절대 크기보다 같은 부하를 반복했을 때 가비지 컬렉션 뒤에도 회수되지 않고 늘어나는 객체가 핵심이에요. 공식 예제도 전역 문자열에 요청마다 timestamp를 이어 붙인 뒤, 반복 요청 전후의 string 사용량과 비정상적으로 큰 문자열을 비교해 누적 코드 줄을 찾는 방식입니다.
운영에서 P99/P999만 올랐는데 로컬에서 작은 요청 하나씩 보내면 재현이 안 될 수 있어요. 공유 isolate의 동시성이나 큰 입력 경로가 빠졌으니 멀쩡한 게 오히려 자연스러움. 대시보드에서 경로, payload 크기, 동시성, 부하 패턴을 먼저 좁힌 뒤 그 조건을 닮게 재현해야 합니다.

출처: Profiling Memory
코드에서는 버퍼링과 무제한 전역 상태부터 본다
원인 후보가 좁혀졌다면 아래 패턴을 우선 확인해요.
큰 body를 통째로 버퍼링하는 코드
큰 request나 response를 text(), json(), arrayBuffer()로 전부 읽으면 원문 body와 파싱 결과가 동시에 남아 peak memory가 커질 수 있습니다. 공식 대응은 TransformStream 또는 node:stream을 이용해 스트리밍하는 것. 특히 EOF 전에 한도를 넘는다는 메시지가 있었다면 먼저 볼 부분입니다.
상한 없이 커지는 전역 캐시와 컬렉션
isolate 전역의 배열, Map, Set, 문자열, 사용자별 cache는 호출 사이에도 남을 수 있어요. TTL과 최대 항목 수를 두고, 큰 상태는 KV·R2·D1 같은 외부 저장소로 옮기는 방법을 검토합니다.
다만 외부 저장소가 모든 workload에서 자동으로 더 빠르다는 뜻은 아니에요. 메모리 안전성과 I/O 비용을 같이 봐야 함.
중복 표현과 WebAssembly 메모리
큰 JSON을 문자열과 파싱 객체로 동시에 들고 있거나 복사·직렬화·압축 단계마다 중간 버퍼를 만들면 peak가 커집니다. WebAssembly linear memory도 128 MB에 포함되니 JavaScript heap만 보고 여유 있다고 판단하면 안 돼요.
단일 요청 테스트에서 빠지는 동시성
요청 한 건은 정상이어도 여러 요청이 동시에 처리되면 isolate 전체 메모리가 커질 수 있습니다. P50은 안정적인데 높은 백분위만 오른 경우라면 운영과 닮은 동시 부하로 다시 확인해 보는 게 맞아요.
배포 후 10단계로 빠르게 점검하기
- Workers & Pages → Worker → Metrics를 엽니다.
- Memory Usage의 배포 마커 전후로 같은 길이의 구간을 잡아요.
- P50 기준선이 배포 뒤 올라갔는지 봅니다.
- P90·P99·P999만 상승했는지와 128 MB까지 남은 여유를 확인합니다.
- gradual deployment라면 구버전과 신버전을 비교해요.
- Errors → Invocation Statuses →
Exceeded Memory를 확인합니다. - Analytics나 Logpush의
exceededMemory도 함께 봅니다. - Error 1102와
Exceeded Resources만으로 메모리 오류라 단정하지 않습니다. - 경로·payload·동시성을 맞춰
wrangler dev와 여러 힙 스냅샷으로 재현해요. - 수정 뒤 같은 workload에서 객체 증가가 멈췄는지, 다음 배포 뒤 백분위와 오류가 기준선으로 돌아왔는지 확인합니다.
Cloudflare가 “P99가 몇 퍼센트 오르면 롤백” 같은 공식 임계값을 제공하는 건 아니에요. 팀의 정상 변동폭과 트래픽 특성에 맞춰 기준을 정해야 합니다. 그래도 배포 전후의 같은 조건, 네 가지 백분위, 실제 Exceeded Memory, 반복 힙 스냅샷을 차례로 연결하면 막연하게 메모리 누수를 의심하는 단계에서는 벗어날 수 있어요.
결론은 간단함. P50은 평소 기준선, P99/P999는 드문 꼬리, 오류 지표는 실제 실패, DevTools는 원인 코드 확인용. 이 네 역할만 섞지 않아도 배포 후 메모리 회귀 찾기가 꽤 마음 편-안해집니다.