금요일

Nginx upstream 502 에러 완벽 해결 가이드

 🔍 검색 키워드: Nginx upstream 502, Nginx Bad Gateway, Nginx 리버스 프록시 에러, Nginx 서버 다운


Nginx upstream 502 에러 완벽 해결 가이드


증상: 502 Bad Gateway

브라우저에서 사이트에 접속했는데 갑자기 502 Bad Gateway 에러가 뜨나요?


502 Bad Gateway

nginx/1.24.0


이것이 upstream 502 에러입니다. Nginx가 백엔드 서버(upstream)로부터 유효한 응답을 받지 못했을 때 발생합니다.


원인 분석


원인 1: 백엔드 애플리케이션 서버 다운

가장 흔한 원인입니다. Node.js, PHP-FPM, Gunicorn 등 실제 애플리케이션 서버가 죽어있거나 재시작 중인 경우입니다.


원인 2: upstream 타임아웃

백엔드 서버 응답이 너무 느려서 Nginx가 설정된 타임아웃 시간 안에 응답을 받지 못하는 경우입니다.


원인 3: 소켓/포트 연결 실패

Nginx 설정의 upstream 주소(포트, 소켓 경로)가 실제 백엔드 서버와 일치하지 않는 경우입니다.


원인 4: 백엔드 서버의 버퍼 크기 초과

응답 헤더나 바디가 Nginx의 기본 버퍼 크기를 초과하면 502가 발생할 수 있습니다.


해결 방법


방법 1: 백엔드 서버 상태 확인

가장 먼저 애플리케이션 서버가 실제로 살아있는지 확인합니다.


systemctl status my-app

또는

pm2 status

또는

docker ps


프로세스가 죽어있다면 재시작하고, 재시작 후에도 계속 죽는다면 애플리케이션 로그를 확인해야 합니다.


방법 2: Nginx 에러 로그 확인

정확한 원인은 Nginx 에러 로그에서 확인할 수 있습니다.


tail -f /var/log/nginx/error.log


connect() failed (111: Connection refused) 메시지가 보이면 백엔드 서버 연결 자체가 안 되는 것이고, upstream timed out 메시지가 보이면 타임아웃 문제입니다.


방법 3: upstream 설정 확인 및 수정

nginx.conf 또는 사이트 설정 파일에서 upstream 블록을 확인합니다.


upstream backend {

    server 127.0.0.1:3000;

}


server {

    location / {

        proxy_pass http://backend;

    }

}


포트 번호가 실제 애플리케이션이 리스닝하는 포트와 일치하는지 반드시 확인합니다.


방법 4: 타임아웃 값 조정

백엔드 처리가 원래 느린 작업(대용량 파일 업로드, 무거운 연산)이라면 타임아웃을 늘려줍니다.


proxy_connect_timeout 60s;

proxy_send_timeout 60s;

proxy_read_timeout 60s;


단, 무작정 늘리기보다 애플리케이션 자체의 응답 속도를 개선하는 것이 근본적인 해결책입니다.


방법 5: 버퍼 크기 조정

헤더/바디 크기 초과 문제라면 버퍼 설정을 늘립니다.


proxy_buffer_size 16k;

proxy_buffers 4 32k;

proxy_busy_buffers_size 64k;


방법 6: 헬스체크 및 자동 재시작 설정

재발 방지를 위해 systemd나 pm2, docker의 헬스체크와 자동 재시작 설정을 구성해두면 서버 다운으로 인한 502를 최소화할 수 있습니다.


정리

- 502는 Nginx가 백엔드로부터 유효한 응답을 못 받았다는 의미

- 에러 로그(error.log)를 먼저 확인하는 것이 진단의 시작

- Connection refused는 서버 다운, timed out은 응답 지연 문제

- upstream 포트 설정 오타는 의외로 자주 발생하는 원인

- 헬스체크와 자동 재시작으로 재발 방지


Nginx는 프록시일 뿐이므로, 502 에러의 근본 원인은 대부분 백엔드 애플리케이션 쪽에 있다는 점을 기억해야 합니다.

Redis WRONGTYPE 에러 완벽 해결 가이드

 🔍 검색 키워드: Redis WRONGTYPE 에러, Redis 데이터 타입 오류, Redis 자료구조, Redis 디버깅


Redis WRONGTYPE 에러 완벽 해결 가이드


증상: WRONGTYPE Operation against a key holding the wrong kind of value

Redis 명령어를 실행했는데 갑자기 이런 에러가 뜨나요?


(error) WRONGTYPE Operation against a key holding the wrong kind of value


이것이 WRONGTYPE 에러입니다. 이미 다른 자료구조로 저장된 키에 맞지 않는 명령어를 사용할 때 발생합니다.


원인 분석


원인 1: 키에 저장된 자료구조와 명령어 불일치

예를 들어 String로 저장된 키에 List 명령어(LPUSH)를 사용하면 에러가 납니다.


SET user:1:name "John"

LPUSH user:1:name "extra" 

결과: WRONGTYPE 에러 (String 키에 List 명령어 사용)


원인 2: 키 이름 재사용으로 인한 타입 충돌

여러 개발자가 같은 키 네이밍 규칙 없이 사용하다 보면 흔히 발생합니다.


원인 3: 캐시 데이터 구조 변경 후 기존 키 미정리

애플리케이션 로직이 바뀌어 자료구조를 변경했는데 기존 키가 남아있는 경우


해결 방법


방법 1: TYPE 명령어로 현재 키의 타입 확인

TYPE user:1:name

결과: string 또는 list, hash, set, zset 중 하나 반환


먼저 문제가 되는 키의 실제 타입을 확인하는 것이 첫 걸음입니다.


방법 2: 올바른 명령어로 재시도

확인된 타입에 맞는 명령어를 사용합니다.

- String이면: GET, SET, INCR

- List면: LPUSH, RPUSH, LRANGE

- Hash면: HSET, HGET, HGETALL

- Set이면: SADD, SMEMBERS

- Sorted Set이면: ZADD, ZRANGE


방법 3: 키를 삭제하고 재생성

기존 키의 데이터가 더 이상 필요 없다면 DEL로 삭제 후 새로 생성합니다.


DEL user:1:name

LPUSH user:1:name "value1" "value2"


주의: 프로덕션 환경에서는 DEL 실행 전 반드시 데이터 백업 여부를 확인해야 합니다.


방법 4: 키 네이밍 컨벤션 도입으로 예방

프로젝트 차원에서 키 이름에 자료구조를 명시하는 규칙을 도입하면 재발을 막을 수 있습니다.


예시:

- str:user:1:name (String)

- list:user:1:orders (List)

- hash:user:1:profile (Hash)

- set:user:1:tags (Set)


방법 5: 애플리케이션 코드에서 사전 타입 체크

Redis 클라이언트 라이브러리에서 명령어 실행 전 TYPE 체크 로직을 추가하면 에러를 사전에 방지할 수 있습니다. 특히 배치 작업이나 마이그레이션 스크립트에서 유용합니다.


정리

- WRONGTYPE은 키의 실제 자료구조와 명령어가 맞지 않을 때 발생

- TYPE 명령어로 먼저 확인하는 것이 가장 빠른 진단법

- 키 네이밍 컨벤션으로 예방하는 것이 근본적인 해결책

- 프로덕션에서 DEL 사용 시 반드시 백업 확인


Redis는 스키마리스 데이터베이스이기 때문에, 팀 차원의 키 관리 규칙이 없으면 이런 문제가 반복될 수 있습니다.

Prisma N+1 쿼리 문제 완벽 해결

 🔍 검색 키워드: Prisma N+1 쿼리, include relation, select 최적화, Prisma 성능 튜닝, 데이터베이스 쿼리 최적화


Prisma N+1 쿼리 문제 완벽 해결


증상: 데이터 조회 시 데이터베이스 쿼리가 너무 많다

사용자 목록 조회 후 각 사용자의 프로필을 가져오려니 쿼리가 100개 이상 날아가나요?


1. 사용자 목록 조회 (1개 쿼리)

2. 각 사용자의 프로필 조회 (N개 쿼리)

결과: 1 + N = 101개 쿼리!


이것이 N+1 쿼리 문제입니다. 성능 저하, 데이터베이스 부하 증가를 초래합니다.


원인 분석


원인 1: 관계 데이터를 include/select 없이 조회

const users = await prisma.user.findMany();

for (const user of users) {

  const profile = await prisma.profile.findUnique({

    where: { userId: user.id }

  });

}

결과: 1 + N개 쿼리


원인 2: 중첩 관계에서 include 누락

const posts = await prisma.post.findMany();

for (const post of posts) {

  const author = await prisma.user.findUnique({

    where: { id: post.authorId }

  });

  const comments = await prisma.comment.findMany({

    where: { postId: post.id }

  });

}

결과: 1 + N + N*M개 쿼리


해결 방법


✅ 방법 1: include로 관계 데이터 포함 (기본)

const users = await prisma.user.findMany({

  include: {

    profile: true

  }

});

1개 쿼리로 완료!


✅ 방법 2: select로 필요한 컬럼만 선택 (최적화)

const users = await prisma.user.findMany({

  select: {

    id: true,

    email: true,

    profile: {

      select: {

        bio: true,

        avatar: true

      }

    }

  }

});

데이터양 감소, 성능 향상


✅ 방법 3: where 조건으로 불필요한 데이터 필터링

const post = await prisma.post.findUnique({

  where: { id: 1 },

  include: {

    comments: {

      where: { approved: true },

      orderBy: { createdAt: 'desc' },

      take: 10

    }

  }

});

효율적!


결론

- include로 관계 데이터 포함 — N+1의 기본 해결

- select로 필요한 필드만 — 성능 + 보안

- where로 DB 수준 필터링 — 불필요한 데이터 전송 방지

- 중첩은 2단계까지 — 복잡도와 성능의 균형

- 쿼리 로깅으로 검증 — 개발 중 문제 조기 발견


Prisma는 ORM이지만, SQL 수준의 최적화 의식을 가져야 효율적인 쿼리를 만들 수 있습니다.