첫 글: Node.js 서버 지연 이슈를 추적하고 해결한 기록

Tue, 14 Apr 2026 09:01:57 GMT

안녕하세요. Dev Log의 첫 글입니다.

이 채널에는 개발 과정에서 겪은 문제와 해결 과정을 기록하려고 합니다. 특히 운영 환경에서 발생하는 **trouble shooting** 사례를 중심으로, 증상과 원인, 확인 방법, 대응 방법을 정리해볼 예정입니다.

첫 글에서는 Node.js 서버 운영 중 발생했던 지연 이슈를 추적하고 해결한 과정을 정리합니다.

---

## 문제 상황

운영 중이던 Node.js API 서버에서 특정 시간대에 응답 지연이 발생했습니다.

평소에는 대부분의 요청이 100ms 안팎에서 처리됐지만, 문제가 발생하는 구간에는 일부 요청이 3초에서 5초까지 증가했습니다. 특이한 점은 CPU 사용률이 지속적으로 높지 않았고, 메모리 사용량도 즉시 장애를 의심할 정도는 아니었다는 점입니다.

초기에는 단순한 트래픽 증가나 인스턴스 성능 문제를 먼저 의심했습니다. 하지만 요청 패턴을 보면 전체 API가 느려지는 것이 아니라, 특정 요청들이 묶여서 지연되는 형태에 가까웠습니다.

이 패턴 때문에 애플리케이션 레벨의 병목 가능성을 먼저 확인하기로 했습니다.

---

## 1차 확인

우선 아래 항목을 기준으로 원인을 좁혀갔습니다.

1. 데이터베이스 쿼리 지연

2. 외부 API 응답 지연

3. Node.js 이벤트 루프 블로킹

DB slow query 로그에는 유의미한 이상이 없었습니다. 외부 API 호출 시간도 평소와 큰 차이가 없었습니다.

다음으로 이벤트 루프 지연을 측정하는 로그를 추가했습니다. 문제 시간대의 로그를 확인해보니, 지연이 발생한 시점마다 `event loop lag`가 함께 증가하고 있었습니다.

이 시점에서 문제는 I/O 대기보다, Node.js 프로세스 내부에서 메인 스레드를 오래 점유하는 작업일 가능성이 높다고 판단했습니다.

---

## 원인 분석

코드를 추적하던 중, 관리자 통계 페이지에서 사용하는 API 하나가 눈에 들어왔습니다. 이 API는 최근 24시간 기준 데이터를 조회한 뒤, 여러 집계 결과를 하나의 큰 객체로 조합해 응답하는 구조였습니다.

문제는 응답 직전에 끝나지 않았습니다. 같은 데이터를 Redis 캐시에 저장하기 위해, 서버에서 큰 객체를 `JSON.stringify()` 한 뒤 캐시에 기록하고 있었습니다.

데이터 양이 적을 때는 큰 문제가 없었지만, 특정 시간대에는 집계 대상이 많아지면서 직렬화 비용이 커졌고, 이 작업이 이벤트 루프를 의미 있게 점유하고 있었습니다.

정리하면 병목 지점은 다음이었습니다.

- 대용량 응답 객체 생성

- 요청 처리 경로 안에서 수행되는 `JSON.stringify()`

- 직렬화가 끝날 때까지 다른 요청이 대기하는 구조

즉, DB나 Redis 자체의 속도보다, **Node.js 메인 스레드에서 수행되는 대용량 JSON 직렬화**가 직접적인 원인이었습니다.

---

## 대응 방법

### 1. 응답 스키마 축소

먼저 응답 데이터에 실제로 필요한 필드만 남기도록 정리했습니다. 통계 화면에서 사용하지 않는 값과, 프론트엔드에서 다시 계산 가능한 값들을 제거했습니다.

이 변경으로 payload 크기가 줄었고, 객체 생성 비용도 함께 감소했습니다.

### 2. 캐시 생성 시점 변경

기존에는 요청이 들어올 때마다 응답 데이터를 만들고, 그 결과를 즉시 직렬화해서 Redis에 저장하고 있었습니다.

이 방식을 요청 경로 밖으로 분리했습니다. 트래픽이 집중되는 API에서는 이미 계산된 결과를 읽도록 바꾸고, 무거운 계산과 캐시 생성은 별도 배치 작업으로 이동했습니다.

핵심은 다음과 같습니다.

- 요청 처리 중 무거운 계산 수행 제거

- 요청 처리 중 대용량 직렬화 수행 제거

- 읽기 중심의 경로로 단순화

### 3. 관측 지표 보강

이번 문제는 CPU나 메모리만 봐서는 바로 드러나지 않았습니다. 그래서 이후에는 다음 지표를 함께 남기도록 수정했습니다.

- route별 응답 시간

- event loop lag

- Redis read/write 시간

- payload 크기

- 주요 처리 구간별 소요 시간

이렇게 바꾸고 나니, 단순히 "느리다"가 아니라 어느 구간이 병목인지 더 빠르게 확인할 수 있었습니다.

---

## 결과

수정 이후 해당 시간대의 지연은 대부분 해소됐습니다.

기존에는 일부 요청이 3~5초까지 증가했지만, 수정 후에는 대부분 200ms 내외로 안정화됐고, 이벤트 루프 지연 스파이크도 눈에 띄게 줄었습니다.

이번 이슈를 통해 다시 확인한 점은, Node.js 서버 운영에서는 인프라 리소스만으로 해결되지 않는 병목이 자주 발생한다는 것입니다. 특히 아래 항목은 실제 운영에서 쉽게 문제가 됩니다.

- 요청 경로 안에서의 무거운 계산

- 대용량 객체 생성

- 동기적인 직렬화 작업

- 관측 지표 없이 감으로 추정하는 디버깅

겉으로 보기에는 단순한 응답 지연처럼 보였지만, 실제 원인은 애플리케이션 내부 처리 방식에 있었습니다. 그래서 문제를 재현 가능한 지표로 나누고, 병목 구간을 단계적으로 확인하는 과정이 중요했습니다.

---

## 마무리

앞으로도 운영 환경에서 발생할 수 있는 trouble shooting 사례를 중심으로 기록을 남겨보겠습니다.

읽어주셔서 감사합니다. 종종 찾아와 주세요.