GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 1기] EC2에서만 멈추는 스트리밍, 500줄짜리 검색 쿼리 (성장일지 3회차 큐레이션) 🌱

GROWTH LOG🌱 2026. 8. 7. 01:38

안녕하세요! 그로스로그 입니다!😊 🌱

로컬에선 멀쩡하던 스트리밍이 EC2에서만 멈춘 일, 그리고 500줄짜리 검색 쿼리 앞에 선 시간. 끝까지 파고든 두 편입니다.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.

이번 회차는 문제 해결의 과정이 유난히 잘 드러난 두 편이었어요.

 

01

🔍 [Nextjs] 트러블슈팅 — 스트리밍을 위한 NGINX 프록시 설정

변기원 님

변기원 님 원문에 실린 이미지
이런 내용이에요
Next.js App Router로 만든 개인 프로젝트를 다시 배포하면서, 로딩과 렌더링 성능 최적화에 가장 공을 들인 과정을 담았습니다.
문제는 로컬에서 잘 되던 스트리밍이 EC2에 올리자 멈춰버린 것이었어요. 원인을 애플리케이션 코드가 아니라 nginx의 버퍼링 설정까지 내려가 밝혀냅니다. 프록시가 응답을 모아뒀다 한 번에 보내면서 스트리밍이 스트리밍이 아니게 된 거죠.
여기서 끝나지 않고, 그래도 남아 있던 3초의 병목을 서버 로그로 추적해 blur 이미지 캐싱으로 잡아냅니다. 가설을 세우고 지워나가는 과정이 그대로 남아 있어요.

배포 환경에서만 재현되는 버그는 개발자를 가장 지치게 만듭니다. 로컬에서는 아무리 돌려봐도 멀쩡하니까요.

이 글이 좋은 건 의심의 범위를 계속 넓혀간다는 점입니다. 내 코드 → 프레임워크 설정 → 그 앞단의 nginx까지. 스트리밍처럼 "응답을 조금씩 흘려보내는" 기능은 중간에 있는 모든 계층이 협조해야 동작하는데, 그걸 직접 부딪히며 알아낸 기록이에요.

그리고 nginx 버퍼링을 잡은 뒤에도 멈추지 않았습니다. 남은 3초가 어디서 새는지 서버 로그를 뒤져 찾아냈고, blur 이미지 캐싱으로 해결했어요.

조금 더 들어가 보면

보통 웹 서버는 페이지를 다 만들어서 한 번에 보냅니다. 그런데 페이지 안에 느린 부분이 하나라도 있으면 — 예를 들어 외부 API를 호출해야 하는 영역 — 나머지가 다 준비돼도 그 하나 때문에 전체가 기다립니다. 사용자는 빈 화면을 보고 있고요.

스트리밍 렌더링은 이 순서를 바꿉니다. 빨리 준비된 부분부터 먼저 흘려보내고, 느린 부분은 준비되는 대로 이어서 보냅니다. React의 Suspense와 Next.js App Router가 하려는 게 정확히 이거예요. 사용자는 뼈대를 먼저 보고, 내용이 차례로 채워지는 걸 보게 됩니다.

여기서 함정이 생깁니다. 스트리밍은 응답을 조금씩 끊어서 계속 보내는 방식인데, 중간에 있는 누군가가 그걸 모으고 있으면 의미가 사라집니다.

nginx는 기본적으로 뒷단 서버의 응답을 버퍼에 모았다가 한 번에 클라이언트로 넘기도록 설정돼 있습니다. 대부분의 상황에서는 이게 좋은 선택이에요. 느린 클라이언트가 뒷단 서버를 오래 붙잡고 있는 걸 막아주거든요.

그런데 스트리밍에서는 이게 정확히 반대로 작동합니다. 애플리케이션은 조각조각 열심히 내보내는데, nginx가 그걸 다 모았다가 마지막에 한 번에 보내버립니다. 결과적으로 사용자 입장에서는 스트리밍을 안 한 것과 똑같아져요. 로컬에서 잘 되던 게 EC2에서만 멈춘 이유가 이겁니다. 로컬에는 nginx가 없었으니까요.

이런 유형의 버그가 특히 어려운 이유가 있습니다. 어느 쪽도 고장 나지 않았다는 점이에요. 애플리케이션 코드도 정상이고, nginx 설정도 그 자체로는 합리적입니다. 문제는 둘의 조합에서만 나타나죠. 그래서 코드만 들여다보면 영원히 못 찾습니다.

배포 환경에서만 재현되는 버그를 만났을 때 내 코드 바깥을 의심할 줄 아는 것 — 이게 실무 경험이 쌓이면서 생기는 감각입니다. 프록시, 로드밸런서, CDN, 방화벽은 전부 "내가 안 짰지만 내 요청이 반드시 통과하는" 구간이거든요.

큐레이터 노트

트러블슈팅 글의 가치는 "해결했다"가 아니라 "어떻게 좁혀갔다"에 있습니다. 이 글은 그 좁혀가는 과정을 숨기지 않아요. 처음부터 nginx를 의심한 게 아니라, 하나씩 지워가다 거기 도달했다는 게 그대로 보입니다.

같은 증상을 만난 사람이 이 글을 찾았을 때 얻는 건 설정값 한 줄만이 아닙니다. "내 코드가 아닐 수도 있다"는 생각의 방향을 같이 얻어가게 되죠. 그게 훨씬 오래 쓰이는 지식입니다.

원문 읽으러 가기  ↗pungwa.tistory.com/225
 

02

🧱 세 번째 프로젝트에 대한 회고 :: 인생 2막 개발자 도전기

남기준 님

남기준 님 원문에 실린 이미지
이런 내용이에요
두 번째 프로젝트를 진행하던 중 갑작스러운 인사 발령을 받고, 하루짜리 인수인계와 노트북 로우 포맷을 거쳐 새 프로젝트로 옮겨간 이야기로 시작합니다.
새 현장에서 마주한 건 500줄짜리 검색 쿼리와 노출순위를 자동으로 매기는 배치 로직이었습니다. 남이 만든 거대한 쿼리를 읽어내는 일이 어떤 건지, 그 앞에서 무엇이 부족했는지를 솔직하게 적었어요.
결론은 화려한 기술이 아니라 기본기와 협업입니다. 그리고 글 마지막에 예상 못 한 반전이 하나 기다리고 있습니다.

1기 2회차에서 두 번째 프로젝트 회고를 소개해드렸는데, 그 연작의 다음 편입니다.

이번엔 상황이 더 급하게 바뀝니다. 프로젝트 도중 인사 발령이 났고, 하루 만에 인수인계를 마치고 노트북을 포맷한 뒤 새 현장으로 이동합니다. 개발자로 일해본 분이라면 이 문장들 사이의 막막함이 읽히실 거예요.

그리고 도착한 곳에서 만난 것이 500줄짜리 검색 쿼리입니다. 직접 짠 것도 아니고, 문서도 없고, 그런데 고쳐야 하는 코드요.

조금 더 들어가 보면

500줄짜리 SQL은 농담이 아닙니다. 오래된 업무 시스템에서는 드물지 않게 나옵니다. 어쩌다 이런 게 생길까요.

대개는 요구사항이 하나씩 덧붙은 결과입니다. 처음엔 상품을 검색하는 단순한 쿼리였을 겁니다. 그다음 "품절 상품은 빼주세요"가 붙고, "프로모션 중인 건 위로 올려주세요"가 붙고, "회원 등급별로 다르게 보여주세요"가 붙습니다. 각각은 작은 요청인데, 몇 년 쌓이면 아무도 전체를 이해하지 못하는 쿼리가 완성돼요.

여기에 노출순위 자동 배치 로직까지 얽히면 난이도가 한 단계 더 올라갑니다. 배치는 정해진 시간에 돌면서 데이터를 미리 계산해두는 작업인데, 문제가 생겨도 즉시 드러나지 않습니다. 새벽에 돌고 아침에 결과만 보이니까요. 원인을 찾으려면 어제 어떤 데이터가 들어왔는지부터 거슬러 올라가야 합니다.

이런 코드 앞에서 초보와 경험자를 가르는 건 함부로 고치지 않는 태도입니다.

경험이 쌓인 개발자는 500줄 쿼리를 보고 곧바로 리팩터링하려 들지 않습니다. 먼저 지금 무엇을 하고 있는지를 정확히 파악합니다. 실행 계획을 떠보고, 조건을 하나씩 떼어보며 결과가 어떻게 바뀌는지 확인하고, 실제 데이터로 검증하죠. "왜 이렇게 짰지"라고 생각되는 부분에도 대개 이유가 있습니다. 그 이유가 문서가 아니라 사람들의 기억에만 남아 있을 뿐이에요.

글이 기본기와 협업으로 결론 내린 게 그래서 정확합니다. 화려한 신기술이 필요한 상황이 아니거든요. SQL을 제대로 읽을 줄 아는 능력, 그리고 아는 사람에게 제때 물어보는 일. 이 두 가지가 실제로 문제를 푸는 도구입니다.

큐레이터 노트

이 연작이 계속 큐레이션되는 이유가 있습니다. 개발자로 산다는 게 실제로 어떤 모양인지를 미화 없이 보여주기 때문이에요.

기술 블로그에는 성공한 이야기가 많습니다. 무엇을 만들었고 어떻게 개선했는지요. 그런데 실제 개발 현장의 상당 부분은 내가 고르지 않은 코드, 내가 정하지 않은 일정, 갑자기 바뀌는 상황 위에서 굴러갑니다. 그 부분을 기록하는 사람은 훨씬 적어요.

"인생 2막"으로 개발자가 되신 분이 그 시간을 계속 적어 내려간다는 것 자체가 이 연작의 힘입니다. 마지막 반전은 직접 읽어보시길 권합니다.

원문 읽으러 가기  ↗paekma09.tistory.com/4

💡 두 편을 겹쳐 읽으며

한 편은 nginx 설정까지 내려가는 기술 추적기고, 한 편은 갑작스러운 발령과 500줄 쿼리 앞에서 쓴 회고입니다. 결이 전혀 달라 보이는데, 두 글의 구조는 같습니다.

내가 통제할 수 없는 곳에서 문제가 생겼다 → 그래도 끝까지 따라갔다.

변기원 님의 문제는 자기 코드 밖에 있었습니다. nginx는 본인이 짠 게 아니죠. 남기준 님의 상황도 본인이 선택한 게 아니었어요. 발령도, 그 500줄짜리 쿼리도요.

개발이 어려운 이유가 여기 있는 것 같습니다. 내가 쓴 코드만 잘 쓰면 되는 일이 아니라, 내가 만들지 않은 것들 위에서 동작하게 만드는 일이라서요.


이번 회차에서 가져갈 것

1. 로컬에서만 되는 건 아직 되는 게 아니다. 개발 환경에는 없고 운영 환경에만 있는 것들 — nginx, 로드밸런서, CDN, 방화벽 — 이 실제 동작을 바꿉니다. 배포 후에만 나는 버그를 만났다면, 내 코드 바깥부터 의심 목록에 올려보세요.

2. 오래된 코드에는 대개 이유가 있다. 500줄 쿼리를 보고 처음 드는 생각은 "이걸 왜 이렇게"지만, 그 안의 조건 하나하나는 누군가의 요구사항이었습니다. 먼저 이해하고 나중에 고치는 순서를 지키는 게 사고를 막습니다.

3. 기본기가 결국 도구다. 두 편 다 해결의 열쇠가 최신 기술이 아니었어요. 하나는 프록시 동작 원리를 아는 것, 하나는 SQL을 제대로 읽는 것이었습니다. 새 프레임워크를 배우는 시간만큼 밑단을 이해하는 시간도 남는 투자입니다.


좋은 글 남겨주신 변기원 님, 남기준 님께 감사드립니다. 그리고 각자의 자리에서 통제할 수 없는 문제와 씨름하고 계신 모든 멤버분들께도요! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기