(544)

이직 후 로그 개선을 하려다 DevOps/SRE가 되기로 마음먹었다.

로그 개선을 하려다 개발자로서의 목표가 생겼어요현재 조직은 Product Engineer 체제로 각자가 실제 PRD ~ 배포까지의 사이클을 가진 조직이에요. 각자가 발산하는 영역은 넓어졌지만, 누군가 Product Engineer를 위해 시스템 안전성, 보안 거버넌스, 배포 파이프라인 등의 전반적인 관리를 해주지는 않아요. 각 개발자들이 책임져야하는 영역입니다. 이 책임이라는건, 관심이 없으면 지속적으로 개선해나가기 힘들어요. 특히 Observability 또한 지속적으로 Alert 구축 및 개선, SLO/SLA 정의나 모니터링 도구들도 계속해서 최신화 해야하는 등의 역할이 많을 것 같은데요. 관측성을 전담하는 사람이 없는 상황이에요. 참고로 저는 백엔드 개발자고, 입사 6개월 차에요.어느날 무언가 이상..

600억 토큰을 쓰면서 Claude Code와 Codex 세션을 최적화한 방법들

올 한 해 600억 가까이 되는 토큰을 사용하면서, (많이 사용한 편은 아닌 것 같지만 ㅋㅋ) 내 사용 경험과 여러 레퍼런스들을 참고하여 작업 세션을 최적화 한 방법에 대해 조금 남겨놓으려고 한다. 핵심은 예나 지금이나 세션 단위의 PRD를 정확히 물려주고, 발산하는 영역들을 휴먼 루프를 최대한 덜 태우면서 어떻게 검증할지를 항상 고민했던 것 같다. 가장 좋은 모델을 오래 붙잡는 것보다, 작업을 나누고 필요한 컨텍스트만 남기고 마지막에 다시 검증하는 편이 더 빨랐다.처음에는 가장 좋은 모델에게 최대한 많은 맥락을 주면 가장 좋은 결과가 나올 거라고 생각했다. 실제로도 초반에는 꽤 잘 통했다. 프로젝트 전체를 읽히고, 내가 알고 있는 제약을 길게 적고, 한 세션에서 구현부터 테스트와 리뷰까지 계속 이..

30GB 영상 일괄 다운로드를 안전하게 서빙하기

요즘 시대에 이정도 요구사항은 AI와 함께라면 전혀 두렵지 않지만, 개발을 접하고 지금까지 파일을 다루는 일을 크게 해오지 않았던 터라, 기록 형태로 남겨보려고 한다. 정의사용자는 여러 명이 올린 영상들을 하나하나 클릭해서 다운로드 받아야 했다. 이에 불편함을 느껴 일괄 다운로드를 받고싶어 한다. 추가적인 정보는 다음과 같다.숏폼 형태의 영상으로, 영상 하나당 최대 300MB 제한이다.영상의 개수는 최대 100개, 약 30GB이다.앞으로 개수는 더 늘어날 수 있다.기존 단건 다운로드 방식은 public URL로 S3에서 직접 다운로드 받는 방식이다. 생각 정리얼마 안되는 개발 짬빱으로 1차로 생각을 정리했다.압축스트리밍멱등한 비동기 처리 여튼 효율적으로, 빠르게 다운로드가 가능하게 구현하고 요청과..

HTTP QUERY method는 POST /search를 대체할 수 있을까

서론개발자라면 한 번쯤은 특정 필터, 검색 요건을 충족하는 검색 API를 만들어 본 적이 있을 것이다.조건이 단순하면 간단하지만, 조금만 필터가 복잡해져도 URL은 금방 지저분해진다. 중첩 조건, 범위 검색, 정렬, 페이지네이션, OR 조건 등 여러 가지 조건들이 붙으면 쿼리 스트링은 읽기도 어렵고 지저분해진다. 그래서 조건을 body나 operation payload에 싣는 POST나 GraphQL을 통해 이런 문제를 풀기도 한다. POST 메서드나 GraphQL의 query operation과 variables를 POST body에 담아 보내기도 한다. POST /contacts/search HTTP/1.1Content-Type: application/jsonAccept: application/..

티빙, 패스트캠퍼스, 모두의 창업 등 이어지는 개인 정보 유출과 최근 개발 방식에 대한 회고

티빙, 패스트캠퍼스, 모두의 창업 등 최근 연달아 대규모 개인정보 유출이 발생하고 있다.개인의, 조직의 실수로 데이터가 유출되는건 생각보다 빈번하겠으나, 어느정도 대규모 조직이나 개발 조직이 두터운 곳들에서도 정말 기본적인 것들을 지키지 않은데에서 오는 파장이 엄청나다고 느끼는 요즘이다. 오늘도 AI를 신뢰하지 않고 언제나 불매를 할 준비가 되어있다 라는 마인드를 가지고 약간의 원인 해부와 나도 개선할 점을 조금 느끼는 것 같아 회고를 남긴다. 시크릿 키 유출티빙은 DB에 직접 침입해서 탈취당했다고 한다. 그에 따른 사후 조치로는 AWS 액세스 키를 폐기하고, 깃허브 자격증명을 교체했다고 했다. 정확한 원인을 밝히지 않았으니, 티빙은 감히 추측하지 않도록 하겠다. 패스트캠퍼스는 소위 인강 플랫폼이다..

5월 회고 - AI가 개발자에게 가져다준 명암

서론링크드인이나 다른 레퍼런스들에 너무 많은 노이즈가 끼어 있다. AI로 뭘 자동화했다. 생산성이 몇 배 향상 됐다. 시도하면서 배우는 것들이 있기 때문에, 시도 자체에는 부정의 여지가 단 1%도 존재하지 않는다.하지만, 나는 이런 레퍼런스들이 성공 사례라고 생각하지 않는다. 개인 혹은 본인이 속한 조직에서 여러 연관 관계에 따른 사이드 이펙트가 있음에도 이를 선택한 설명이나 도입 전후의 공개 가능한 지표들을 충분히 제공하지 않은 채로 무지성 AX를 외치고 있다고 생각한다. 이게 무지성 뇌동매매 후 화성 갈끄니께~~ 를 외치는 것과 뭐가 다를까? 나도 이런 부분에서 자유롭지 않다. 한 조직의 개발자로서 AI로 뭔가 빨라지긴 한 것 같은데, 정말 나아진 게 맞나? 라는 생각이 끊임 없이 든다. 정말 생..

4월 회고 - Claude Blue와 Grafana Meetup 오거나이저

서론눈에 보이면 닥치는대로 하면서 최대한 중심을 지키려고 했던 4월이었다. CLI와 API를 합하면 200억 토큰을 조금 넘게 사용해오면서 나에게도 드디어 진하게 우울증이 왔다. LLM을 점점 많이 사용하면서 회의감 같은 것들이 들기 시작했다. (한국에서는 AI로 인한 우울증을 Claude Blue라고 하더라.) 그럼에도 원하는 것들이, 해보고싶은 것들이 있기 때문에 킵 고잉할 수 밖에 없었던 4월이었다. Claude Blue그 전에도 나는 무언가를 시작하면 항상 끝장을 봐야하는, 최상위권은 달성을 해봐야하는 유형의 사람이었다. 개발자로 커리어를 시작하면서도 마찬가지었다. 벽은 너무 높지만 착실히 하나하나 쌓아나가며 의지가 꺾이지 않는다면 언젠가는 도달할 수 있지 않을까? 라고 생각하며 22년부터 ..

늦은 3월 회고 - 기존의 업무 방식을 깨보기, AX로 나아가기

들어가며꽃이 만개하든 말든, 여러 다양한 시도들을 하며 재밌게 바쁘게 알차게 보냈던 3월의 회고.3월엔 이전의 회고들에서도 주구장창 얘기했던, 깊게 하는 생각을 다 깨고 빠르게 빠르게 바로 시도해 볼 수 있는 기반을 닦았다고 생각이 든다. 기존의 틀을 깨보기어떻게 보면 AI 과도기인, 산업 혁명(?) 과도 같은 시기에 있는 지금. 아무리 스타트업이라고 하더라도 스프린트에 어느정도 고착화되어 있을 수 있다. 우리 또한 마찬가지라고 생각했다. 기존 업무 방식인 스프린트 단위의 사이클도 물론 빠르지만 PM/디자이너/개발자 등의 업무 체계가 명확하게 나뉘어져있고, 의사 소통에서 오는 병목이 반드시 존재한다. 물론 AI Native하게 바로 가자! 도 아니고, AI를 맹신해서 하는 말도 아니다. 다만 지금 시..

uWSGI buffer-size와 502, 서버가 죽었나?

서론여느 때처럼 평화롭게 개발을 하던 어느날, 프로덕트의 dev 환경에서 502가 계속해서 발생했습니다. 지표도 정상이고 헬스 체크도 정상이고, 도대체 무슨 문제인지 긴가민가 했습니다. 로그를 확인 해 본 결과 처음보는 낯선 에러가 발생하더군요 요약하자면 Python WSGI 애플리케이션 서버인 uWSGI의 buffer-size 설정 값을 초과했기 때문이었습니다. 찾아보니 이 buffer-size는 요청 헤더의 사이즈의 설정 값이라고 하더군요. Express를 사용했을 때를 떠올려보면 발생하지 않았던 문제였습니다. 요청 헤더가 커서 요청이 Drop된 적이 없었기 때문입니다.이에 관련된 CS적인 지식은 기초 수준의 간단한 것입니다만, Django 진영의 웹 서버는 왜 이런 방식을 선택했는지 궁금해서, 기록..

이직 후 로그 개선을 하려다 DevOps/SRE가 되기로 마음먹었다.

Tech/기타 2026. 9. 3. 00:10
728x90
728x90

 

 

로그 개선을 하려다 개발자로서의 목표가 생겼어요


현재 조직은 Product Engineer 체제로 각자가 실제 PRD ~ 배포까지의 사이클을 가진 조직이에요. 각자가 발산하는 영역은 넓어졌지만, 누군가 Product Engineer를 위해 시스템 안전성, 보안 거버넌스, 배포 파이프라인 등의 전반적인 관리를 해주지는 않아요. 각 개발자들이 책임져야하는 영역입니다.

이 책임이라는건, 관심이 없으면 지속적으로 개선해나가기 힘들어요. 특히 Observability 또한 지속적으로 Alert 구축 및 개선, SLO/SLA 정의나 모니터링 도구들도 계속해서 최신화 해야하는 등의 역할이 많을 것 같은데요. 관측성을 전담하는 사람이 없는 상황이에요. 참고로 저는 백엔드 개발자고, 입사 6개월 차에요.

어느날 무언가 이상하다고 느꼈어요. 우리가 가진 threshold 기반 알림은 RDS CPU 50% / 80% 임계치 알림이 전부였고, 가진건 CloudWatch밖에 없었습니다. 별도 모니터링 도구도 없고 트레이싱 수단도 없었고 로그는 평문이었어요.

이 글은 입사 후 무언가 이상함을 느낀 주니어의 삽질 기록이에요. 도구가 없는 상태에서 무엇부터 손댈지 정하고, 로그 한 줄을 고쳐서 지표로 만들고, 그러다 데이터베이스가 인터넷에 열려 있는 걸 발견해서 거기까지 간 이야기예요. 장황하게 Obs, SRE를 얘기했지만, 크게 별로 한 건 없을 수도 있습니다 ㅋㅋ. 끝난 작업도 아니에요. 지금도 새로 들어오는 코드는 파이프라인을 비켜 가고, 무엇을 더 해야 하는지도 다 알지 못해요. 다만 문제를 발의하고 기능 조직 전체에 경각심을 심어 조금 더 관심을 가지도록 했다고는 볼 수 있겠네요.

여튼 왜 이런 작업들을 했는지 기록을 남기려고 해요.

 

제품이 빠르게 성장할 것이라고 확신했어요

저는 인플루언서와 광고주를 잇는 인플루언서 광고 플랫폼을 만드는 회사에서 제품을 만들고 있어요.

인플루언서 광고 시장에는 최근 1년 사이 새 플랫폼이 계속 들어왔고, 이미 자리를 잡은 커머스 플랫폼도 이 안으로 들어왔어요. 
우리 조직도 1월 대비 매출은 20배가 넘는 성장을 하였지만, 다른 제품들도 마찬가지라고 생각해요. 정말 많은 제품들이 자리잡았고, 또 생겨나고 있는 상황이에요.

PMF 자체는 찾았고, 다른 조직들도 이 PMF를 해자라고 생각하고 뛰어들고 있어요. 저는 여기서 성장할 것이라는 확신이 들었어요.

애딧
2026.08 · 시드 투자 유치

브랜드의 캠페인 목표에 맞는 인스타그램 디지털 매거진과 크리에이터를 AI로 연결하는 광고 서비스예요.

매거진 네트워크 70곳 이상
매쉬업벤처스, 카카오벤처스 등 참여

뮤즈바이
2025.07 · 화이트큐브

숏폼 전문 인플루언서 마케팅 플랫폼이에요. 뷰티로 시작해 건기식 같은 다른 카테고리로 넓히고 있어요.

출시 7개월 만에 월매출 15배
연결 인플루언서 5,613명

글로브
앤마들린 · 뷰티 숏폼 제작

브랜드 대신 릴스를 만들어 주는 제작 플랫폼이에요. 뷰티로 시작해 식품과 영양제, 이너뷰티까지 넓히고 있어요.

감성형, 정보형, 방문형 세 갈래
원고료 20만원부터, 2차 활용 평생 무료

레뷰
레뷰코퍼레이션 · 코스닥 상장

블로그와 인스타그램, 유튜브를 아우르는 국내 최대 규모의 인플루언서 마케팅 플랫폼이에요.

인플루언서 회원 150만명
누적 캠페인 126만건, 월 1.8만건

피처링
올인원 인플루언서 마케팅 SaaS

인플루언서를 찾고 관리하고 성과를 증명하는 구간을 데이터로 잇는 B2B 서비스예요.

누적 인플루언서 DB 1,700만건
SNS 콘텐츠 3억건 분석

태그바이
올인원 셀프 마케팅 플랫폼

광고주가 직접 체험단을 모집하고 운영하는 플랫폼이에요. 팔로워를 부풀린 계정을 걸러내는 쪽에 힘을 줬어요.

활동 인플루언서 5만명 이상
도달과 노출을 API로 확인

미디언스
자동화 인플루언서 마케팅

브랜드 성장에 필요한 콘텐츠를 크리에이터가 만들어 내보내는 구조를 자동화한 플랫폼이에요.

크리에이터 모집과 집행을 한 흐름으로
브랜드와 크리에이터 양쪽 진입점

우리는 인플루언서 광고를 캠페인이라고 불러요. 위에서 언급했듯이 시장이 이렇게 움직이는 동안 우리 캠페인 수도 같이 늘었어요.
1월 대비 매출은 20배 가량, 캠페인 수는 1월 대비 5배가 넘게 성장했어요.

2026년 월별 시딩 캠페인 등록 수. 1월 19건에서 8월 104건까지 실측, 9월부터 12월 200건 이상까지 예상

여기서 중요한 건 곡선 자체가 아니라 캠페인이 늘 때 뒤따라 붙는 것들이에요. 늘어나는 게 트래픽만이 아니거든요.
운영 이슈 대응과 개발 속도 요구가 같이 붙어서 셋이 비례해 늘어요.
캠페인마다 브랜드가 다르고 사용 방식이 다르니 문제가 나타나는 모습도 그만큼 여러 갈래로 갈리고요.

관측해야 할 경계와 각 경계가 만드는 실패, 그리고 앞으로 늘어날 경계
경계마다 나는 실패가 다르고, 경계가 늘면 실패의 종류가 그만큼 늘어나요. 트래픽은 배수로 늘지만 이건 종류로 늘어서, 각 경계를 같은 사건으로 이어 볼 기준이 필요해요.

성장을 가속하려면 시스템이 버티는지 미리 확인하고 점검할 수 있는 수단이 있어야 한다고 생각했어요.
우리의 일하는 방식에 맞게, 각자가 발산을 하더라도 사전에, 사후에 쉽게 점검할 수 있도록 말이에요.

알림은 전부 사후 대응이에요

입사하고 6개월을 되돌아보니 임계치 알림은 RDS 기반이 거의 전부였고, 나머지는 특정 에러가 났을 때 슬랙으로 오는 알림이었어요.
둘을 나란히 놓고 보니 공통점이 하나 있었고요. 이미 상황이 벌어진 후에야 알 수 있다는 거죠.

사용자가 먼저 알고, 문의가 들어온 다음에 우리가 알아요.
임계치를 넘은 다음에야 그래프를 열어봐요.

여기서 걸리는 게 있었어요. 우리 시스템은 대부분 종단이 RDS예요. 그러니 무슨 일이 나든 RDS 지표가 먼저 흔들려 보이고, 전부 RDS 피크 이슈처럼 보이게 되어 있어요. 정작 사용자 경험이 어디서 무너지는지는 그 지표로 알 수 없어요.

실제로 그런 일이 있었어요.

2026년 7월 13일 오전, 요청 증가 뒤 replica lag와 5xx가 튀었지만 DB CPU는 임계 아래였다
그날 사람에게 오는 RDS 알람은 클러스터 평균 CPU 50% 경고 하나였어요. 평균이 49.1%까지 올라 0.9%p 차이로 안 울렸고요.
같은 5분에 replica lag가 117초, 5xx가 1,555건이었어요. 창 전체로는 2,008건이고, 인스턴스 하나는 73.7%까지 갔고요.
DB는 곧 정상으로 돌아왔지만 실패한 요청은 다시 처리되지 않았어요. 자동 복구가 영향 복구는 아니에요.
2026년 7월 13일 오전 프로덕션 실측이에요. CPU와 replica lag는 CloudWatch 지표, 5xx와 요청 수는 로그에서 뽑았어요.

알림이 발생하지 않아도 사용자의 UX는 저하될 수 있어요. 사용자가 문제를 겪고 있을 수도 있고요.
임계치 아래에서 조용히 실패하는 것들, 알림이 울리지 않는 지금 이 순간에도 누군가는 문제를 겪고 있을 수도 있어요. 우리는 그걸 알 수 있을까요?

지금은 사건 발생부터 인지까지 보이지 않는 구간이 길고, 목표는 이상 징후로 그 구간을 줄이는 것

알림이 울려도 쉽게 파악할 수 있는 구조가 아니에요

별도의 모니터링 도구 없이 CloudWatch에 의존하다 보니, 장애가 났을 때 관측하는 방법이 개발자마다 달랐어요. 누구는 로그 그룹부터 열고 누구는 메트릭부터 보고 누구는 DB 화면부터 열었고요. 도구가 없으니 각자 자기 방법을 만든 거예요.
문제는 각자 익숙한 축만 본다는 거고요. 그러면 아무도 안 보는 축이 생기고, 원인을 두고 의견이 갈렸을 때 어느 쪽이 맞는지 가릴 공통 근거도 없어요.

모두 같은 AWS를 보고 있었지만 시작점이 달랐어요.

서버 장애는 보통 로그와 메트릭을 조합해서 원인을 특정해요. 그런데 우리 로그는 평문이었고 트레이싱 수단도 없었어요. 조합할 재료의 한쪽이 통째로 없었던 셈이에요.

이벤트 93만 줄, 스캔량 315MB. 그 안에 요청 하나를 이어줄 식별자는 0건이었어요.
5xx 집계도 0이었는데, 서버 오류가 없었다는 뜻이 아니라 상태 코드가 구조화된 필드로 안 찍혀서 셀 대상 자체가 없었다는 뜻이에요.

여기에 하나 더 붙어요. 요즘은 디버깅도 AI를 붙여서 하는데, 사람이 한 번에 파악 못 하는 로그를 AI가 한 번에 파악할 리가 없어요. 연관을 못 잡으니 명령 가짓수만 늘어나고 그만큼 오래 걸려요.

 

문제는 생각보다 컸어요

클로드와 함께 사전 조사를 시작했어요. 로그 포맷이 Plain Text인 문제와 포맷 불일치부터 시작해서, 로그 위생과 시스템 배포 안전성 문제, 인프라 전반의 보안 문제도 있었어요. 긴 호흡의 작업이 되겠다는 직감이 들었어요. 그래서 조사한 내용을 그때그때 컨텍스트로 남기고, 우선순위를 세워 태스크 단위로 쪼개서 가기로 했어요.

우선순위를 정했어요

우선순위는 이렇게 정했어요.

  • 0순위 무조건 해결해야 하는 보안 이슈
  • 1순위 사이드 이펙트가 작고 빠르게 적용할 수 있는 것
  • 2순위 최소한의 작업으로 기준을 정할 수 있는 것

0순위는 팀 내에서 함께 해결해야 하는 문제였어요. 지금 안 쓰는 시스템이 무엇인지부터 가려내야 하는데 입사 6개월 차가 혼자 판단할 수 있는 게 아니었고, 네트워크를 바꾸려면 다운타임도 필요했거든요. 이 부분은 팀 내부 합의가 필요했어요.

1순위와 2순위를 모두 만족하는 건 로그 개선이었어요. 시스템 로그는 추상화된 미들웨어에서 관리하게끔 하면 되니까, 포맷을 거기서 강제하고 기존 코드를 물리기만 하면 돼요. 로그 개선을 시발점으로, 관측 가능성을 개편하고 우리에게 필요한 로그 기반 알림들을 구축할 수 있겠다 생각했어요. 기존에는 로그 기반 알림이 거의 없었거든요.

기존 코드들에 흩어진 출력들을 찾고 수정하는 작업은 클로드 같은 AI가 가장 잘하는 일이에요. 구조화된 기준만 잡아주면 언어·프레임워크·라이브러리마다 다르게 쓰인 모든 출력을 찾고 개선하기 쉬워요. 그래서 사이드 이펙트가 거의 없고 빠르게 적용할 수 있다고 봤어요.

여기까지만 되어도 trace가 생겨요. 기준이 하나로 맞으니 사람이 로그를 한 번만 확인해도 요청 내 흐름들을 한 번에 볼 수 있어요.
AI도 마찬가지예요. 연관관계가 있는 그룹 몇 개만 골라 trace 기반 검색으로 디버깅이 훨씬 빨라질 수 있어요.

이 글에서 관측성이라고 부르는 건 로그를 많이 쌓는 일이 아니에요. 질문에서 증거를 거쳐 행동에 닿는 시간을 줄이는 것이에요.
모니터링이 미리 정해둔 이상을 놓치지 않는 쪽이라면, 관측성은 처음 보는 문제의 범위를 남아 있는 증거로 좁히는 쪽이에요.

 

로그 포맷부터 통일했어요

기존 로그는 Plain Text라 검색이 불편했어요. 필드 단위 조회가 안 되니 문자열이나 정규식으로 매칭하거나, 매번 parse로 즉석에서 뜯어야 했거든요.

그리고 각자 남기는 로그가 달랐어요. 애플리케이션은 Django 기본 포맷, 엣지는 nginx combined, 라이브러리는 또 제각각이었고요. 그래서 누가 검색해도 같은 메타데이터로 찾을 수 있게 포맷부터 다시 정의했어요.

 
Internal Server Error: /v1/campaigns/12/apply

192.0.2.10 - - [22/Jun/2026:08:52:11 +0900]
"GET /v1/users/me?email=a%40b.com HTTP/1.1"
200 1834 "-" "Mozilla/5.0"
 
{
  "timestamp": "2026-06-25T02:11:07.418+00:00",
  "request_id": "452f5593dd0cdcfc95d4...",
  "trace_id": "9cab7561c59dc034f854...",
  "service": "link-server",
  "release": "a1b2c3d",
  "method": "GET",
  "path": "/v1/users/me?email=***",
  "status_code": 500,
  "status_class": "5xx",
  "duration_ms": 1842
}

포맷을 정의하면서 trace도 같이 만들었어요. 얻은 게 세 가지예요.

  • 누가 조회해도 같은 필드로 같은 결과가 나와요.
  • 흩어진 로그를 한 번에 이어서 볼 수 있어요.
  • 어느 배포에서 난 문제인지 release로 추적할 수 있어요.

개선 이후 프로덕션 로그 그룹 다섯 개에서 0건이던 request_id가 배포 이틀 뒤 엣지 로그 기준 99.99%가 됐어요. 셀 수 없던 5xx도 숫자로 잡히기 시작했고요.

한 줄이 지표가 됐어요

포맷이 맞으니 질문하는 방식이 달라졌어요. 로그는 365일 보관이라 개선 전 로그가 그대로 남아 있어요. 그래서 같은 질문을 양쪽에 던져봤어요.
5xx가 어디서 났고, 그 요청에 무슨 일이 있었나. 질문은 이거 하나예요.

 
# 로그가 어떻게 생겼는지부터 읽어야 해요
parse @message /(?<m>[A-Z]+) (?<p>\S+) =>
  generated \d+ bytes in (?<ms>\d+) msecs
  \(HTTP\/1\.1 (?<code>\d{3})\)/
| filter code like /^5/
| stats count() as n by bin(1d), p

6/16  /blocks       3,875
6/16  /users           525

# 그 요청에 무슨 일이 있었나
→ 이을 방법이 없어요
 
filter status_class="5xx"
| stats count() as n by bin(1d), path

8/23  /smartstore/products   12
8/29  /blocks                11

# 한 건 집어서 그대로 따라가요
filter request_id="2bc354c812081ccb..."

03:24:59.567 ERROR  Internal Server Error
03:24:59.568 INFO   PUT /smart.. - 500

왼쪽에서 막힌 이유는 이거예요. 파이썬 traceback이 CloudWatch에 한 줄씩 서로 다른 이벤트로 들어가 있었어요. 순서도 안 맞고, 어느 요청 것인지 알려주는 것도 없어요.

raise self.ResponseError(self.http_response.status_code, ...)
Traceback (most recent call last):
utils.payments.client.PaymentClient.ResponseError: 503

AI도 마찬가지예요. 굳이 request_idtrace_id의 역할을 알려주지 않아도 자연스레 트레이스 수단으로 인식하고 디버깅을 더 빠르게 진행할 수 있어요. 그리고 이 한 줄이 그대로 지표가 돼요. status_class는 요청 하나에 정확히 한 번 찍히는 마커라, 메트릭 필터로 세기만 하면 요청 수가 되고 duration_ms를 얹으면 p99가 나와요.

여기가 시발점이에요. 우리에게 필요한 알림은 이 지표 위에 하나씩 붙여나가면 돼요. 무엇을 신호로 볼지가 앱이 아니라 로그 한 줄 위에 있으니까요.

로그 개선 과정에서 같이 진행한 것들이에요.

  • 중복 로그 제거. 같은 요청이 앱, nginx, uWSGI 세 군데에 남고 지표용 EMF까지 한 줄 더 붙어서, 로그량이 한때 개선 전의 3.7배였어요. 중복 로그를 걷어내고 1.6배 정도로 최적화했어요.
  • 로그 로테이션 설정. 프로덕션이 2년, Lambda는 무기한이었어요. 지금은 프로덕션 1년, 개발 30일이고 더 줄일지는 논의가 필요해요.
  • 로그 위생. 로그가 남는 전 구간에 PII 같은 민감정보를 마스킹해요.
 

로그를 켜니까 DB도 보였어요

데이터베이스 로그도 활성화하고 나니 이상한 로그가 보였어요. RDS superuser로 누군가 접속을 시도한 흔적이 있었어요. 전수 조사를 해보니 해외 주소에서 들어온 시도들이 종종 보였고요. 더 큰 문제는 성공한 접속은 로그에 남지 않는다는 점이었어요. 뚫렸는지 아닌지를 판단할 근거가 아예 없었어요.

앞에서 각자 익숙한 축만 보다 보면 아무도 안 보는 축이 생긴다고 했는데, 누군가 DB에 붙어보고 있다는 사실 자체를 우리가 전혀 모르고 있었어요.

내용
prod Aurora6대전부 공인 IP를 달고 있었어요
라우팅 테이블67 / 69인터넷 게이트웨이로 나가요. 프라이빗 구간이 0개예요
DB 접속 람다43 / 216전부 VPC 밖에서 공인 경로로 붙어요
레포 하드코딩27곳DB 비밀번호가 평문이고 dev와 prod가 같아요
손대기 전 네트워크 흐름. 인터넷을 지나 들어오는 경로와 VPC 안에서 붙는 경로
인터넷을 지나 들어오는 경로VPC 안에서 붙는 경로
손대기 전 구조예요. 공격자도 개발자도 우리 람다도 인터넷을 지나 데이터베이스에 닿았어요. 앱과 DB가 같은 층에 있었고, DB 앞을 막는 건 비밀번호 하나였어요. EC2는 프로덕션 DB에 실제로 붙는 두 대만 그렸어요.

제일 먼저 하고 싶었던 건 당연히 차단이었어요. 그런데 그러면 안 됐어요. 누가 이 데이터베이스에 붙는지 아무도 몰랐거든요. 그래서 며칠 더 지켜본 다음에 막았어요. 차단은 언제든 되돌릴 수 있지만, 무엇이 깨졌는지는 사후에 복원할 수 없으니까요.

실험 1. 다운타임 없이 인터넷에서 뗄 수 있을까

정리하면 세 겹이 전부 퍼블릭이었어요. DB가 퍼블릭 서브넷에 있고, 공인 IP를 달고 있고, 앞을 지키는 보안그룹이 5432를 0.0.0.0/0으로 열어두고 있었어요. 서브넷 단위로 거르는 NACL은 기본값 그대로라 아무것도 안 막고 있었고요.

셋은 AND라서 하나만 아니어도 바깥에서는 안 닿아요. 그러니 어디를 먼저 끊을지가 선택이었어요.

외부에서 데이터베이스에 닿기 위한 세 조건
셋이 AND로 묶여 있어서 하나만 끊어도 외부 도달은 끝나요. 다만 끊는 값과 되돌리는 값이 서로 달라요.

그래서 위험을 얼마나 줄이느냐가 아니라 틀렸을 때 얼마나 빨리 되돌릴 수 있느냐로 순서를 정했어요. 되돌리기가 싼 것부터 손대면 아니다 싶을 때 원상복구가 빠르니까요.

개발 환경에서 이 순서로 돌렸어요.

단계한 일걸린 시간
01log_connections 켜기누가 어디서 붙는지부터 봐요즉시
02프라이빗 서브넷과 NAT 구축VPC에 넣은 람다는 나갈 길이 없어요5분
03람다 12개 VPC 이전VPC 밖이면 공인 경로를 타요2분
04로컬 접속을 SSM 터널로인바운드 포트 0개, IAM 인증, 감사 로그10분
05PubliclyAccessible = falseDNS가 공인 IP에서 사설 IP로 바뀌어요1분
06보안그룹을 SG 참조로주소가 아니라 소속으로 접근을 정해요10분

결과는 좀 허탈했어요. 공인 경로로 들어오는 접속이 거의 없었거든요. 컨테이너도 EC2도 원래부터 사설 주소로 붙고 있었어요. 공인 IP는 우리는 아무도 안 쓰면서 공격자한테만 열려 있던 문이었어요. 플래그 해제는 1분 걸렸고 재부팅도 장애 조치도 없었어요. 앱 접속이 끊긴 건 0건이고요.

다만 플래그 해제는 최종 형태가 아니에요. 한 겹에 의존하는 상태라 누가 되돌리면 다시 열려요. 그래서 이건 지금의 노출을 줄이는 임시 조치로 두고, 서브넷 이전을 최종으로 잡았어요.

실험 2. 참조처를 다 고쳐야 할까

Aurora는 이미 만들어진 클러스터의 서브넷 그룹을 바꿀 수 없어요. modify-db-cluster에 그 옵션이 아예 없어요. 그러니 프라이빗 서브넷에 새로 만들어 옮겨가야 하는데, 그러면 접속 주소가 바뀌어요. 접속 문자열은 람다 환경변수와 인프라 코드와 태스크 정의에 흩어져 박혀 있고요.

Aurora는 클론을 지원해요. 스토리지를 복사하지 않고 쓰기가 일어난 블록만 뒤늦게 나눠 갖는 방식이라, 크기와 무관하게 몇 분이면 만들어져요. 그래서 프라이빗 서브넷에 클론을 만들고 원본과 클러스터 이름을 맞바꿨어요. 엔드포인트 주소가 클러스터 이름을 따라가니까요.

프라이빗 서브넷 컷오버 구간별 소요 시간
120초. ECS desiredCount 0을 줘서 새 쓰기를 막아요
90초. copy-on-write라 데이터를 안 옮겨요
360초. 여기가 병목이에요. 전체의 46%예요
95초. 원본을 -old로, 클론을 원래 이름으로
120초
2026년 8월 13일 개발 환경 컷오버 실측이에요. 절반 가까이가 라이터 인스턴스 생성에 들어가요.

다운타임이 필요한 이유는 쓰기 때문이에요. 클론은 만든 시점까지의 데이터만 갖고 있어요. 그래서 이름을 바꾸는 동안 앱이 옛 클러스터에 계속 쓰면, 그 쓰기는 새 쪽에 없어요. 그래서 그 구간만 앱을 내려서 새 쓰기를 막았어요. 읽기만 하면 이럴 필요가 없지만, 쓰기가 섞이면 정합성이 깨져요.

결과적으로 컨테이너와 EC2와 람다 설정은 한 곳도 안 바꿨어요. 같은 VPC 안이라 사설 경로로 붙고 주소도 그대로거든요. 바뀐 건 개발자 로컬 접속 경로뿐이에요. 클론이 승계하지 않는 것도 있었어요. CloudWatch 로그 내보내기와 삭제 방지가 안 따라와요. 컷오버 직후에 관측이 끊긴다는 뜻이라, 가장 필요한 시점에 못 보게 돼요. 만들 때 같이 넣어야 해요.

여기까지가 개발 환경에서 돌린 한 사이클이에요. 다만 VPC 안에 넣은 람다는 아웃바운드 경로가 없어요. DB로 가는 사설 길은 얻지만 인터넷으로 나갈 길이 사라지거든요. 외부 API를 호출해야 하니 NAT Gateway를 뒀어요. 서울 리전에서 띄워두기만 해도 월 43달러쯤이고, 처리한 트래픽에 또 붙어요. 개발 환경은 가용영역마다 두지 않고 하나만 뒀어요.

최종 형태는 이래요. DB를 프라이빗 계층에 두고, 주소가 아니라 리소스 소속으로 접근을 정해요. 보안그룹에 CIDR 규칙을 0개로 두고 SG 참조만 남기면, 누가 붙을 수 있는지가 IP 목록이 아니라 어느 서비스인지로 바뀌어요.

 

한 번 세우고 끝나는 게 아니었어요

포맷을 기준으로 만든 순간 지켜야 할 계약이 하나 생겼어요. 메트릭 필터가 status_classrequest_id를 읽으니까, 누가 필드 이름 하나만 바꿔도 필터가 조용히 안 맞고 지표가 0으로 떨어져요. 에러도 안 나고요.

그리고 새로 들어오는 코드는 계속 파이프를 비켜 가요. 로그 개선이 끝났다고 생각했는데, 결제 이슈를 파다가 앱 전역에 파이프라인 밖으로 나가는 출력이 174개 남아 있는 걸 발견했어요. 완료 판정이 미들웨어 안쪽에만 해당됐던 거예요.

경계가 늘면 봐야 할 자리도 늘어요. 마스킹을 앱에 걸었는데 nginx는 앱 앞에서 로그를 남기니까 엣지에는 그대로 남아 있었어요. 볼륨과 보관 기간도 마찬가지예요. 한 번 걷어냈지만 캠페인이 늘면 다시 차올라요.

알람은 특히 그래요. 안 우는 알람은 건강한 건지 죽은 건지 알람만 봐서는 몰라요. 그래서 요청 수가 5분간 0이면 우는 알람을 따로 뒀어요. 서비스를 보는 게 아니라 관측 파이프 자체를 보는 알람이에요.

돌아보면 늦었어요

자료 조사와 현행 조사는 많이 했어요. 그런데 그게 무엇을 해야 하는지까지 알려주지는 않았어요. 무엇을 버려야 하는지는 더 몰랐고요. 조사가 쌓일수록 할 일 목록만 길어졌어요.

기존 업무와 병행한 것도 컸어요. 이 작업은 제품 로드맵에 없으니까, 다른 일이 밀리면 자연스럽게 뒤로 갔어요.

그리고 로그를 고친 다음에 실측 사이클을 2~3주 돌렸어요. 정말 반영됐는지, 회귀는 없는지 계속 다시 쟀거든요. 지금 돌아보면 그만큼 필요했는지 모르겠어요. 처음 만지는 영역이라 확신이 없어서 계속 확인한 건데, 그게 안전장치였는지 그냥 늦춘 건지는 아직도 딱 잘라 말하기 어려워요.

관측성은 한 번 세우는 구조물이 아니라 제품과 같이 커지는 것이에요.

로그도 알람도 인프라 위생도 기능이 아니라서 로드맵에 안 올라오고, 안 올라오면 아무도 안 해요. 그런데 안 하면 계속 쌓이고, 쌓인 게 터지면 그때는 제품이 멈춰요. 목적 조직의 우선순위와 별개로 기능 조직이 상시로 들고 가야 하는 일이라고 봐요.

캠페인 수는 2026-08-25 프로덕션 읽기 전용 복제본에서 조회했어요. 등록 기준이고 내부 테스트 캠페인은 뺐어요.
8월은 25일까지의 진행 중 실측이고, 9월 이후는 거기에 월 18% 성장을 적용한 예상치라 실측이 아니에요. 로그 실측은 2026-06-22 08:49 KST부터 한 시간 창이에요.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

600억 토큰을 쓰면서 Claude Code와 Codex 세션을 최적화한 방법들

Tech/기타 2026. 8. 16. 15:28
728x90
728x90

 

올 한 해 600억 가까이 되는 토큰을 사용하면서, (많이 사용한 편은 아닌 것 같지만 ㅋㅋ) 내 사용 경험과 여러 레퍼런스들을 참고하여 작업 세션을 최적화 한 방법에 대해 조금 남겨놓으려고 한다. 핵심은 예나 지금이나 세션 단위의 PRD를 정확히 물려주고, 발산하는 영역들을 휴먼 루프를 최대한 덜 태우면서 어떻게 검증할지를 항상 고민했던 것 같다.

 

 

 

 

가장 좋은 모델을 오래 붙잡는 것보다, 작업을 나누고 필요한 컨텍스트만 남기고 마지막에 다시 검증하는 편이 더 빨랐다.

처음에는 가장 좋은 모델에게 최대한 많은 맥락을 주면 가장 좋은 결과가 나올 거라고 생각했다. 실제로도 초반에는 꽤 잘 통했다. 프로젝트 전체를 읽히고, 내가 알고 있는 제약을 길게 적고, 한 세션에서 구현부터 테스트와 리뷰까지 계속 이어갔다.

그런데 오래 사용할수록 이상한 장면이 반복됐다. 세션은 이미 앞선 실패와 긴 명령 출력으로 무거워졌고, 새 작업과 관계없는 판단까지 계속 따라왔다. 간단한 구현에도 가장 비싼 모델과 높은 추론 강도를 사용했다. 테스트가 통과했다는 답을 받았지만 실제 화면이나 배포 환경에서는 다른 문제가 남기도 했다.

결국 내가 최적화해야 했던 것은 토큰 하나만이 아니었다. 같은 시간과 사용량으로 얼마나 정확한 결정을 내리고, 실제로 검증된 결과까지 도달하느냐가 더 중요했다.

 

 

컨텍스트를 무조건 늘리거나 줄이는 게 아니라, 한 세션이 한 책임을 갖도록 작업 경계를 먼저 나눴다.

 

 

1. 최적화의 대상부터 다시 정의했다

예전에는 컨텍스트 사용량이 낮으면 잘 최적화한 것이라고 생각했다. 지금은 네 가지를 같이 본다.

  • 결과가 실제 요구사항을 충족했는가
  • 내가 기다린 시간은 얼마나 되는가
  • 세션과 주간 사용량을 얼마나 소모했는가
  • 마지막 결과를 사람이 검증할 수 있는가

토큰을 적게 썼는데 잘못된 코드를 만들면 다시 고쳐야 한다. 반대로 최고 성능 모델이 한 번에 정답을 내더라도, 기계적인 수정마다 같은 비용과 대기 시간을 쓰면 전체 작업량이 줄어든다. 그래서 모든 단계를 같은 모델과 같은 추론 강도로 처리하지 않게 됐다.

 

이 글에서 말하는 최적화는 모델을 덜 쓰는 방법이 아니다. 어려운 판단에는 충분히 쓰고, 반복 작업에는 과하게 쓰지 않고, 검증이 필요한 곳에는 다시 집중하는 방식에 가깝다.

 

 

2. 새 작업에는 새 컨텍스트를 쓴다

Claude Code를 처음 오래 사용했을 때 가장 먼저 체감한 문제는 긴 세션이었다. 대화뿐 아니라 읽은 파일, 도구 호출, 테스트와 빌드 출력도 다음 요청의 컨텍스트가 된다. 이전 작업에서 유효했던 정보가 다음 작업에서는 잡음이 될 수 있다.

지금도 기준은 단순하다.

  • 완전히 다른 작업을 시작하면 새 세션 또는 /clear
  • 같은 작업의 한 단계가 끝났고 핵심 맥락은 이어가야 하면 /compact
  • 잘못된 접근 이후의 대화를 통째로 끌고 가기보다 분기 전으로 돌아갈 수 있으면 /rewind
  • 별도 탐색이 주 작업의 맥락을 오염시킬 때만 서브에이전트

예전 글에서는 commit 단위로 compact하고 branch 단위로 clear한다고 표현했다. 지금도 꽤 쓸 만한 비유다. 다만 무조건 정해진 주기로 압축하지는 않는다. 현재 작업에 필요한 정보가 무엇인지, 압축 과정에서 잃어버리면 안 되는 결정이 무엇인지 먼저 본다.

컨텍스트가 크다는 사실 자체보다 관련 없는 정보가 섞여 있는 것이 더 문제였다. 새 작업이 시작됐는데 이전 장애 조사 로그와 실패한 테스트 출력이 계속 남아 있다면, 긴 기억이 장점이 아니라 편향이 된다.

 

 

3. AGENTS.md와 CLAUDE.md에는 매번 필요한 것만 남긴다

한때는 에이전트가 실수할 때마다 금지 규칙을 한 줄씩 추가했다. 당장은 안전해 보였지만, 어느 순간 모든 세션이 거대한 규칙집을 매번 읽고 시작했다. 서로 비슷한 규칙이 반복되고, 중요한 제약과 문체 선호가 같은 무게로 섞였다.

현재는 정보를 세 층으로 나눈다.

 

 

상시 규칙, 레포 규칙, 현재 작업 문서를 나눠 필요한 시점에 필요한 정보만 읽게 한다.

  • 상시 규칙: 모든 작업에 필요한 짧고 안정적인 원칙
  • 레포 규칙: 저장소 구조, 명령, 배포와 검증 계약
  • 작업 문서: 현재 PLAN, skill, 증거와 판단

핵심은 자주 쓰는 정보를 모두 한 파일에 모으는 것이 아니라, 필요한 시점에 정확한 문서를 읽게 만드는 것이다. 코딩 스타일 한 줄 때문에 배포 절차 전체를 매 요청마다 넣을 필요는 없다. 반대로 데이터 마이그레이션처럼 실패 비용이 큰 작업에서는 짧은 프롬프트보다 명시적인 게이트와 검증 절차가 더 중요하다.

지시문도 코드와 비슷했다. 중복을 줄이고 책임을 나누고, 바뀌는 정보와 안정적인 정보를 분리해야 오래 유지할 수 있었다.

언제 규칙을 상시 파일에서 빼는가?

모든 작업에서 읽을 이유가 없거나, 특정 도구·배포·업무 단계에서만 필요한 절차라면 별도 문서나 skill로 옮긴다. 반대로 권한 경계나 파괴적 작업 금지처럼 매번 적용되는 규칙은 상시 파일에 남긴다.

 

 

4. 모델보다 먼저 작업 단계를 나눈다

Claude Code와 Codex를 번갈아 사용하면서 가장 크게 바뀐 부분은 모델 선택보다 phase를 먼저 구분한다는 점이다.

 

 

일상 구현은 균형형 설정에서 처리하고, 애매한 결정과 고위험 최종 게이트에 추론 예산을 집중한다.

지금 사용하는 GPT-5.6 계열에서는 복잡한 판단에는 sol, 일상적인 구현에는 terra, 기계적인 대량 작업에는 luna를 후보로 둔다. 하지만 이름 자체가 핵심은 아니다. 같은 모델이라도 추론 강도를 낮추는 것만으로 사용량과 지연이 크게 달라질 수 있다.

clarify   → sol / high
implement → terra / medium
review    → terra / medium 또는 sol / high

구현이 복잡한 멀티 파일 변경이거나 데이터·권한·배포 계약을 건드리면 implement도 강한 모델을 쓴다. 반대로 파일명 변경, 반복적인 테스트 보강, 명확한 UI 카피 수정에 최고 추론 강도를 사용하지 않는다.

중요한 점은 중간에 즉흥적으로 바꾸기보다 phase가 끝났을 때 다음 세션의 설정을 결정하는 것이다. 한 세션이 하나의 책임을 가지면 모델 선택의 이유도 분명해지고, 결과를 비교하기도 쉬워진다.

 

 

5. 출력이 많은 일은 본문 컨텍스트에서 격리한다

테스트, 빌드, 로그 검색은 필요하지만 출력이 많다. 성공 로그 수천 줄을 메인 세션에 그대로 남기면 이후 모든 판단에 그 무게가 따라온다.

  • 테스트는 필요한 범위부터 실행한다.
  • 가능하면 quiet 옵션이나 요약 출력을 사용한다.
  • 실패했을 때만 필요한 앞뒤 로그를 넓힌다.
  • 서로 독립적인 조사만 별도 컨텍스트로 보낸다.
  • 서브에이전트의 최종 결과에는 결론과 근거 위치를 요구한다.

 

 

대량 출력을 없애는 것이 아니라, 필요할 때 실행하고 다음 판단에 필요한 증거만 메인 컨텍스트로 되돌린다.

여기서 병렬화는 무조건 빠르게 만드는 버튼이 아니었다. 같은 파일을 여러 에이전트가 만지거나, 앞선 결정이 다음 작업의 입력인 경우에는 오히려 합치는 비용이 커졌다. 독립적으로 끝낼 수 있고 결과 형태가 명확할 때만 병렬화가 이득이었다.

 

 

6. 조사, 구현, 검증, 리뷰를 섞지 않는다

AI가 코드를 빨리 만드는 것과 그 코드가 실제로 동작하는 것은 다른 문제다. 이 차이는 사용할수록 더 크게 느꼈다.

조사는 원인을 찾는 단계다. 구현은 변경을 만드는 단계다. 검증은 테스트와 빌드, 실제 화면, 런타임 증거로 요구사항을 확인하는 단계다. 리뷰는 구현자가 놓친 결함을 다시 찾는 단계다.

 

 

리뷰에서 가정이 깨지면 구현을 덧대기 전에 조사로 돌아간다. 완료는 답변의 자신감이 아니라 검증 증거로 결정한다.

이 네 단계를 한 프롬프트에서 모두 시키면 마지막 답변은 대체로 낙관적으로 수렴했다. 자신이 방금 만든 변경을 자신이 바로 리뷰하면 맥락을 잘 아는 대신 같은 가정을 공유한다. 그래서 중요한 변경에서는 새 컨텍스트로 diff와 요구사항만 주고 다시 보게 한다.

테스트가 초록색이어도 완료라고 말하지 않는 것도 같은 이유다. 테스트하지 않은 것은 통과가 아니라 미검증이다. 브라우저를 열지 못했으면 시각 검증은 하지 않은 것이고, 배포하지 않았으면 운영 반영은 되지 않은 것이다.

속도를 위해 AI를 쓰면서도 마지막 책임은 줄어들지 않았다. 오히려 구현의 허들이 낮아진 만큼 무엇을 확인하지 않았는지를 정확히 말하는 일이 더 중요해졌다.

 

 

7. 느낌 대신 사용량과 결과를 같이 본다

Claude Code를 많이 쓰기 시작하면서 JSONL 파일이 수 GB까지 쌓였고, 기존 사용량 도구가 느려져 직접 toktrack을 만들었다. 처음에는 비용과 토큰을 보기 위한 도구였지만, 지금은 작업 방식을 비교하는 계기이기도 하다.

단순히 토큰이 적은 세션을 좋은 세션이라고 보지는 않는다. 비슷한 난이도의 작업에서 다음을 같이 본다.

  • 완료까지 걸린 시간
  • 구현과 수정에 사용한 턴 수
  • 다시 열린 결함 수
  • 검증 단계에서 발견한 누락
  • 최종 입력과 출력 사용량

모델과 추론 강도도 대표 작업으로 비교해야 했다. 공식 문서의 추천값은 출발점이고, 실제 레포의 테스트 구조와 요구사항 밀도에 따라 결과가 달랐다. 가장 높은 설정을 기본값으로 두고 참는 것보다, 평소에는 중간값을 쓰고 품질 차이가 측정되는 단계에서만 올리는 편이 지속 가능했다.

 

 

8. 지금 사용하는 기본 흐름

현재의 기본 흐름을 짧게 적으면 다음과 같다.

 

 

 

작업 크기와 실패 비용에 맞춰 단계를 합치거나 깊이를 조절하되, 검증 경계만은 흐리지 않는다.

  1. 범위 고정: 요구사항, 성공 조건, 비범위를 먼저 적는다.
  2. 설정 선택: 다음 phase의 모델과 추론 강도를 고른다.
  3. 구현: 계획과 필요한 파일만 전달한다.
  4. 출력 축약: 테스트와 빌드는 필요한 범위부터 실행한다.
  5. 실제 검증: 확인한 것과 확인하지 못한 것을 나눈다.
  6. 독립 리뷰: 위험한 변경은 새 컨텍스트에서 다시 본다.
  7. 컨텍스트 종료: 다음 작업에 불필요한 맥락을 넘기지 않는다.

이 흐름이 항상 가장 빠른 것은 아니다. 한 줄 수정에 phase를 세 개 만들면 오히려 낭비다. 반대로 데이터 마이그레이션이나 권한 로직을 한 세션에서 바로 구현하고 스스로 승인하는 것은 빠른 것처럼 보여도 실패 비용이 크다.

결국 작업 크기와 실패 비용에 맞게 깊이를 조절하는 것이 핵심이었다.

실패했던 방식들

  • 하나의 긴 세션에서 관련 없는 작업까지 계속 이어가기
  • 실수할 때마다 AGENTS.md와 CLAUDE.md에 금지 규칙 추가하기
  • 모든 구현을 최고 모델과 최고 추론 강도로 실행하기
  • 독립적이지 않은 작업까지 병렬 에이전트에 맡기기
  • 테스트 성공을 실제 기능과 배포 성공으로 확대 해석하기
  • 에이전트가 만든 요약만 보고 원본 diff와 런타임 증거를 생략하기

완전히 버린 방식도 있고, 특정 상황에서는 여전히 쓰는 방식도 있다. 긴 세션이 항상 나쁜 것도 아니고, 강한 모델이 아까워서 쓰지 말아야 하는 것도 아니다. 문제는 선택의 이유가 없는 기본값이었다.

 

 

모델 이름보다 오래 남는 것

처음 Claude Code를 사용할 때는 컨텍스트를 얼마나 많이 넣을 수 있는지가 중요해 보였다. 이후에는 어떻게 줄일지가 중요해졌다. 지금은 무엇을 남기고, 어떤 단계에 어떤 모델을 쓰고, 어디서 다시 검증할지를 더 많이 고민한다.

AI 코딩 도구가 좋아질수록 프롬프트 요령은 금방 낡을 수 있다. 모델 이름과 명령어도 계속 바뀐다. 그래도 작업을 작게 정의하고, 관련 있는 컨텍스트만 전달하고, 결과를 독립적으로 검증하는 원칙은 비교적 오래 남을 것 같다.

정답이라고 생각하지는 않는다. 다만 Claude Code와 Codex를 꽤 많이 사용하면서, 지금까지는 이 방식이 가장 덜 지치고 가장 많은 결과를 실제 완료 상태까지 가져다줬다.


 

 

참고 자료

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

30GB 영상 일괄 다운로드를 안전하게 서빙하기

Tech/기타 2026. 7. 24. 22:37
728x90
728x90

 

 

요즘 시대에 이정도 요구사항은 AI와 함께라면 전혀 두렵지 않지만, 개발을 접하고 지금까지 파일을 다루는 일을 크게 해오지 않았던 터라, 기록 형태로 남겨보려고 한다.

 

 

 

정의

사용자는 여러 명이 올린 영상들을 하나하나 클릭해서 다운로드 받아야 했다. 이에 불편함을 느껴 일괄 다운로드를 받고싶어 한다.

 

 

추가적인 정보는 다음과 같다.

  • 숏폼 형태의 영상으로, 영상 하나당 최대 300MB 제한이다.
  • 영상의 개수는 최대 100개, 약 30GB이다.
  • 앞으로 개수는 더 늘어날 수 있다.
  • 기존 단건 다운로드 방식은 public URL로 S3에서 직접 다운로드 받는 방식이다.

 

 

생각 정리

얼마 안되는 개발 짬빱으로 1차로 생각을 정리했다.

  • 압축
  • 스트리밍
  • 멱등한 비동기 처리

 

여튼 효율적으로, 빠르게 다운로드가 가능하게 구현하고 요청과 분리해서 202를 떨어뜨리자.
그럼 effectively-once 하게 다운로드가 가능하지 않을까? (이탈·중복·실패에도 정확히 한 번 완료되도록)

 

 

 

영상도 파일이다

 

 

 

알다시피 파일 다운로드라고 딱히 특별하지는 않다. HTTP GET 요청의 응답 body로 온 바이트를 화면에 렌더링할지 디스크에 저장할지를 결정할 뿐이다. 응답 헤더를 통해서 말이다.

 

Content-Type을 통해 video/mp4 등의 inline 실행인지, application/octet-stream 등의 저장 방식을 결정하거나

Content-Disposition을 통해 다운로드를 강제할 수 있다. Content-Length 헤더가 있다면, 진행률 바를 그릴 수도 있고 말이다.

이 기본적인 매커니즘은 S3 저장, 다운로드 방식 위에 있는 기본적인 네트워크 개념이다.

 

 

 

촬영되는 영상의 성질

 

 
 
 
 

촬영 기기의 카메라 센서는 raw 단위의 프레임을 만들지만 촬영 기기는 raw 단위 프레임을 이어서 저장하기에 버겁다. 그렇기 때문에 하드웨어 인코더 칩 등으로 실시간 압축하여 파일로 쓴다. 글을 작성하는 2026년 기준 HEVC라는 비디오 코딩 기술이 안드로이드와 아이폰 기기에 탑재되어 있다. 

 

HEVC(High Efficiency Video Coding/H.265)는 고효율 비디오 코딩 기술이다. 기존 대비 40~50% 수준의 파일 크기로 고품질 영상을 유지할 수 으며, 4K 및 8K와 같은 초고화질(UHD) 영상의 용량을 획기적으로 줄여 스트리밍과 저장에 널리 사용된다.

 

 

 

 

아하? 영상이라 파일 크기가 클 수 밖에 없겠지만, 이미 1차로 최대한 품질을 보존한 압축 기법을 통해 저장이 되는구나?

그렇다면 내가 이걸 더 압축한다면 별도 비디오 코딩 기술을 사용해야하고 그것을 유지보수하기 위한 러닝커브가 존재하겠다.

그리고 애초에 고품질 압축을 추가 압축했을 때 광고 집행이나 2차 활용을 보장할 수 있는 품질인가?

 

 

 

 

 

그래서 어떻게 개선?

S3도 사실 큰 틀에서 다르지 않다. 어떻게 저장소 따위(?)가 더 상위 개념인 네트워크의 본질을 벗어날 수 있을까.

S3의 코어는 분산 오브젝트 스토어에 있다. 인증, 멀티파트 등등..

 

 

 

 

다운로드 요청과 처리를 분리해서, 완료된 파일을 사용자가 이탈할 때에도 다운로드 할 수 있도록 지원하고

중복 요청 방지를 위해서 멱등키로 dedup, 같은 키의 zip이 있으면 S3에서 재생성 없이 presigned URL만 새로 서명했다.

 

백그라운드 워커가 S3 GET을 통해 영상을 읽는다. 다행히 같은 리전이라 트래픽이 무료이며

영상 파일을 묶어서 멀티파트 업로드를 통해 zip 파일로 업로드해서 presigned URL로 다운로드 시킨게 전부..

 

 

 

 

 

 

되게 별게 없다

글을 쓰다보니까 문득 되게 기술적으로는 별 것 안했구나? 라는 생각이 든다.

 

광고주의 문제 정의를 하고, 기존 지표를 통해 광고주들이 크리에이터의 소재들을 일일이 번거롭게 다운로드 하는 것을 확인했다.

KPI의 가장 큰 축이 광고주의 리텐션인데, 리텐션 지표 상승의 엄청난 임팩트가 있는 개선이라고 보기는 어렵다만...

임팩트가 높은 시도와 병렬적으로 유저의 행동을 데이터로 보고 부정적인 행동들을 빠르게 끊을 수 있는 시도를 계속 하다보면 목표 달성에 더 빠르게 가까워지지 않을까..? 

 

뭔가 기술적인 내용을 쓰려고 시작했는데 제품적인 글로 마무리 되는 것 같아 찜찜하게 마무리되는 글..

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

HTTP QUERY method는 POST /search를 대체할 수 있을까

Tech/기타 2026. 6. 28. 21:13
728x90
728x90

 

서론

개발자라면 한 번쯤은 특정 필터, 검색 요건을 충족하는 검색 API를 만들어 본 적이 있을 것이다.

조건이 단순하면 간단하지만, 조금만 필터가 복잡해져도 URL은 금방 지저분해진다. 중첩 조건, 범위 검색, 정렬, 페이지네이션, OR 조건 등 여러 가지 조건들이 붙으면 쿼리 스트링은 읽기도 어렵고 지저분해진다.

 

 

 

 

그래서 조건을 body나 operation payload에 싣는 POST나 GraphQL을 통해 이런 문제를 풀기도 한다. POST 메서드나 GraphQL의 query operation과 variables를 POST body에 담아 보내기도 한다.

 

POST /contacts/search HTTP/1.1
Content-Type: application/json
Accept: application/json

{
  "filter": {
    "status": "active",
    "tags": ["a", "b"],
    "createdAt": { "gte": "2026-01-01" }
  },
  "sort": "-createdAt"
}
POST /graphql HTTP/1.1
Content-Type: application/json
Accept: application/json

{
  "query": "query Contacts($status: String!) { contacts(status: $status) { id username } }",
  "variables": { "status": "active" }
}

 

 

두 방식 모두 복잡한 검색 조건을 표현하기에는 훨씬 편하다. 하지만 HTTP 의미론 관점에서는 찜찜한 지점이 남는다.

 

우리가 원하는 것은 서버 리소스를 생성하거나 변경하는 것이 아니다. 조건이 복잡해서 URL query string만으로 표현하기 어렵기 때문에 request body가 필요할 뿐이다. 그런데 HTTP method는 POST다.

 

POST가 항상 리소스 생성을 뜻하는 것은 아니지만, RFC 9110 기준으로 POST는 safe하지도, idempotent하지도 않은 메서드로 취급된다. 즉 애플리케이션 레이어에서는 읽기 요청이라고 알고 있어도, HTTP 레이어와 중간 인프라 입장에서는 이 요청이 읽기인지 쓰기인지 method만 보고 판단하기 어렵다.

 

HTTP Method의 safe와 idempotent에 대해 다룬 글이 있으니 필요하다면 참고하길 바란다.

 

HTTP Method의 멱등성(Idempotence) 이해하기

멱등성(Idempotency)멱등성이라는 용어부터 살펴보자. 멱등성은 수학에서 유래된 개념으로, 같은 작업을 여러 번 반복해도 결과가 달라지지 않는 성질을 뜻한다. 이 개념은 HTTP Method에서도 중요한

mag1c.tistory.com

 

 

 

POST, GraphQL의 문제

POST /search와 GraphQL POST는 실무적으로 좋은 해법이다. 복잡한 조건을 JSON으로 표현할 수 있고, typed client나 schema validation을 얹기도 쉽다. GraphQL은 한 걸음 더 나아가 클라이언트가 필요한 field shape까지 operation으로 표현할 수 있다.

 

하지만 HTTP 관점에서는 어떨까?

 

요청한 애플리케이션의 의미는 읽기이고, 조건 표현을 위해 request body가 필요하다. HTTP method는 POST라서 safe, idempotent의 의미가 method에 드러나지 않는다. 문제는 조건을 어디에 담을 것인가 만이 아니라, 그 요청이 HTTP 레이어에서 어떤 의미로 보이는가를 같이 생각해보아야 한다.

 

GraphQL도 마찬가지로 애플리케이션 레이어에서는 읽기라고 말하지만 HTTP method만 보면 그냥 POST로 보인다. 그래서 GraphQL 생태계에서는 persisted query 같은 패턴도 생겼다. 긴 query document를 매번 보내는 대신 hash를 URL에 싣거나 CDN cache key를 직접 조정하거나 POST body를 해석하는 애플리케이션 레벨 캐시를 둔다. 하지만 이런 방법들이 HTTP method의 의미 자체를 바꿔주지는 않는다.

 

 

 

 

그럼 GET에 body는?

그럼 의미론적으로 읽기 요청인 GET에 body를 실어 보내는 방법은 왜 안됐을까?

 

GET에서도 당연히 실어 보낼 수는 있다. 하지만 RFC 9110에서 요청 content에 대해 일반적으로 정의된 의미가 없다고 설명한다. 서버가 GET body를 검색 조건으로서 해석하고 사용해야 하는지, 무시하고 거부해야 하는지 표준으로 정해주지 않는다.

 

 

 

클라이언트, 서버, 프록시, CDN, 브라우저, 캐시는 모두 method를 보고 이 요청을 어떻게 다룰지 판단하는데, GET body에 자체 규칙을 얹으면 내가 관리하는 서버에서는 동작할 수 있지만, 중간 경로 전체가 같은 의미를 공유한다고 기대하기 어렵다.

 

 

 

 

HTTP QUERY Method

이런 간극을 다루기 위해 IETF에서는 2026년 6월 RFC 10008을 발행했다.

QUERY method는 IANA HTTP Method Registry에 등록됐고, 이제 HTTP 안에서 body를 가진 safe하고 idempotent한 요청을 표현할 공식적인 메서드가 생겼다.

 

 

 

QUERY /contacts HTTP/1.1
Content-Type: application/json
Accept: application/json

{
  "filter": {
    "status": "active",
    "tags": ["a", "b"],
    "createdAt": { "gte": "2026-01-01" }
  },
  "sort": "-createdAt"
}

 

 

QUERY는 POST처럼 body를 실을 수 있지만, GET처럼 safe하고 idempotent한 query 요청을 표현할 수 있다.

 

RFC 10008

 

 

이런 특성을 가진 QUERY라는 method의 의미는 단순 GET 요청에 body를 실을 수 있는 것보다는 더 많은 가치가 있을 것으로 보인다.

  • 연결이 끊겼을 때 재시도를 고려할 수 있다.
  • 캐시가 safe한 query response로 판단될 수 있다.
  • gateway나 WAF 읽기 요청과 쓰기 요청을 더 명확하게 구분할 수 있다.
  • observability 관점에서 method 기준으로 요청의 성격을 해석하기 쉬워진다. (기존 POST의 오용 감소)

 

 

 

QUERY, 사용해 볼 수 있을까?

QUERY가 표준화됐다고 해서 내일부터 모든 API에 바로 적용하기는 어렵다.

우선 중간 경로가 method를 알아야 한다. 오래된 proxy, WAF, CDN, gateway는 알 수 없는 method를 거부하거나 별도 allowlist로 관리할 수 있다. 서버 framework가 QUERY 라우팅을 지원하더라도 실제 인터넷 경로에서는 막힐 수 있다.

또한 브라우저의 CORS에서 QUERY는 safelisted method가 아니다. RFC 10008도 CORS를 구현하는 user agent에서 QUERY 요청은 preflight가 필요하다고 언급한다. 브라우저 클라이언트에서 쓸 때는 OPTIONS 처리와 Access-Control-Allow-Methods 설정까지 같이 봐야 한다.

그리고 캐시가 request content를 cache key에 포함해야 한다. 이걸 모르는 캐시는 QUERY response를 아예 저장하지 않거나, 저장하더라도 잘못 재사용할 위험이 있다. QUERY의 장점을 제대로 얻으려면 cache layer가 RFC 10008의 규칙을 구현해야 한다. RFC 10008은 QUERY request의 cache key가 request content와 관련 metadata를 포함해야 한다고 명시한다.

마지막으로 지원 여부를 발견하는 절차가 필요하다. RFC 10008은 Accept-Query response header를 정의한다. 서버는 어떤 media type의 query content를 받을 수 있는지 알릴 수 있다.

 

Accept-Query: "application/jsonpath", application/sql;charset="UTF-8"

 

또는 OPTIONS 응답의 Allow header로 QUERY 지원 여부를 드러낼 수 있다.

 

Allow: GET, QUERY, OPTIONS, HEAD

 

 

현실적인 도입 순서는 공용 브라우저 API보다 내부 API나 gateway-controlled API 쪽일 가능성이 높다. 클라이언트, 서버, proxy, cache 설정을 한 팀이 함께 통제할 수 있어야 QUERY의 의미와 이점을 끝까지 보존할 수 있기 때문이다.

 

 

 

마치며

QUERY는 POST이지만 GET으로 사용했던 기존의 레거시 API들을 대체할 수 있는 것처럼 보인다. 그렇다고 당장 기존 API를 바꿀 필요는 없다. POST는 여전히 실용적이며 모든 인프라가 POST를 알고 있고, framework와 client 지원도 안정적이다. 캐시가 꼭 필요하지 않거나, 애플리케이션 레벨에서 캐시/재시도/중복 방지를 이미 잘 처리하고 있다면 POST 검색 API를 유지하는 편이 더 현실적일 수 있다.

다만 RFC 10008이 의미 있는 이유는, 우리가 그동안 관습으로 처리하던 요구사항에 HTTP 차원의 이름을 붙였다는 데 있다. GET은 복잡한 request body를 표준 의미로 다룰 수 없으며, POST는 safe, idempotent하지 않다. 그 문제 정의를 표준으로 정의했다는 것에 의미가 있다고 본다.

 

그래서, POST지만 GET으로 사용되던 레거시를 완전 대체하기보다는 아직은 생태계 지원을 확인하면서 조심스럽게 봐야 하지만, 검색 API와 GraphQL, 분석 API를 설계할 때 앞으로 꽤 자주 언급되고, 고려할 대상으로 보이지 않을까? 싶다.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

티빙, 패스트캠퍼스, 모두의 창업 등 이어지는 개인 정보 유출과 최근 개발 방식에 대한 회고

회고 2026. 6. 22. 22:14
728x90
728x90

 

 

티빙, 패스트캠퍼스, 모두의 창업 등 최근 연달아 대규모 개인정보 유출이 발생하고 있다.
개인의, 조직의 실수로 데이터가 유출되는건 생각보다 빈번하겠으나, 어느정도 대규모 조직이나 개발 조직이 두터운 곳들에서도 정말 기본적인 것들을 지키지 않은데에서 오는 파장이 엄청나다고 느끼는 요즘이다. 

 

오늘도 AI를 신뢰하지 않고 언제나 불매를 할 준비가 되어있다 라는 마인드를 가지고 약간의 원인 해부와 나도 개선할 점을 조금 느끼는 것 같아 회고를 남긴다.

 

 

 

시크릿 키 유출

티빙은 DB에 직접 침입해서 탈취당했다고 한다. 그에 따른 사후 조치로는 AWS 액세스 키를 폐기하고, 깃허브 자격증명을 교체했다고 했다. 정확한 원인을 밝히지 않았으니, 티빙은 감히 추측하지 않도록 하겠다.

 

패스트캠퍼스는 소위 인강 플랫폼이다. IT/테크 쪽의 플랫폼인줄 알았으나 최근 인프런의 행보와 유사하게 다양한 분야로 확장한 모습이었다.

 

 

패스트캠퍼스의 유출 경로는 깃허브 레포의 PAT인지, 깃허브 마스터 계정의 키가 탈취된 채로 한 달 가량 노출되어 있었다고 한다.

 

 

BOLA/BFLA

모두의 창업을 잘은 모르지만, 사용자의 창업 아이디어를 PMF로 삼는 플랫폼이다. 지인 분께서 창업 아이디어를 올렸으니 좋아요를 눌러달라고 했던 경험이 있다. 그 경험으로 미루어보아, 무언가 아이디어 경쟁을 할 수도 있겠다는 생각이다.

 

 

 

5,000개 가량의 아이디어 요약, 심사평 등이 유출되었다고 하는데, 원인은 명확하나 왜 그랬는지는 정말 모르겠다.

개발을 하다보면 기본적인 인증/인가 처리는 고려하지 않으려고해도 안할 수가 없는 기본적인 문제이기 때문에, 정말 빠르게 개발을 해야 했거나 소위 빌더라 불리우는 바이브 코딩의 영향이 아닌가 싶다.

 

헤더에도 써놨지만 Broken Object Level Authorization, Broken Function Level Authorization이라 불리는 API 보안 취약점이 원인이다. 요새 기술 스택이 python이니, python 웹 프레임워크를 예시로 간단하게 코드를 작성했다.

 

@app.get("/api/applicants/{id}")
def get_applicant(id: int):
    a = db.get(Applicant, id)
    return a.__dict__
<span>{maskEmail(applicant.email)}</span>

 

API를 위와 같이 구성하고, UI에서만 미노출하는 형태로 개발이 되었다고 한다.

직접 HTTP Request를 보이면 그대로 드러나게 구성을 해두었던 정말 말도 안되는 문제로 소중한 아이디어들이 유출되었다.

 

 

 

AI Slop

GPT5.5, Opus4.8 Fable4.8 시대에 무슨 AI Slop인가 싶겠지만, 모델의 성능에서 오는 조잡한 결과물에 대한 얘기가 아니다.

LLM이 컨텍스트를 충분하게 얻지 못한 이유로 여러 인과관계들을 고려하지 못한 단순 구현 코드를 싸질렀다고 해서 책임을 LLM이 지는 것은 아니다. 실제 업무에서 사람들과 협업 했을 때, 서로 상대방의 책임으로 떠넘긴 결과물이 과연 좋을까.

 

마찬가지로, LLM을 오케스트레이션 한 결과물을 내가 100% 검수할 수 있는 능력이 없다면, 과연 이 LLM을 가지고 기술적인 요구사항을 구현하는게 맞을까. 구현의 허들은 점점 낮아지지만 책임의 영역은 그대로 보존된다.

 

이 사고들이 모두 AI 때문에 벌어졌다고 말할 수는 없다. 다만 요즘 개발 환경에서는 구현의 허들이 낮아진 만큼, 검증하지 않은 결과물을 운영 환경에 올리는 속도도 같이 빨라졌다. LLM이 만든 코드든 사람이 급히 짠 코드든, 인증·인가·시크릿 관리의 책임은 결국 배포한 사람과 조직에 남는다. 일련의 사례들을 보고 주니어 개발자인 내가 내린 결론은, 본인이 책임질 수 있는 역량을 키우는 것이 요즘의 개발에서 개발자로 살아남는 가장 중요한 첫 단추이지 않을까 라는 생각이 들었다.

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

5월 회고 - AI가 개발자에게 가져다준 명암

회고 2026. 6. 1. 13:47
728x90
728x90

 

 

서론

링크드인이나 다른 레퍼런스들에 너무 많은 노이즈가 끼어 있다.

 

AI로 뭘 자동화했다. 생산성이 몇 배 향상 됐다.

 

시도하면서 배우는 것들이 있기 때문에, 시도 자체에는 부정의 여지가 단 1%도 존재하지 않는다.
하지만, 나는 이런 레퍼런스들이 성공 사례라고 생각하지 않는다. 개인 혹은 본인이 속한 조직에서 여러 연관 관계에 따른 사이드 이펙트가 있음에도 이를 선택한 설명이나 도입 전후의 공개 가능한 지표들을 충분히 제공하지 않은 채로 무지성 AX를 외치고 있다고 생각한다. 이게 무지성 뇌동매매 후 화성 갈끄니께~~ 를 외치는 것과 뭐가 다를까?

 

 

 

나도 이런 부분에서 자유롭지 않다. 한 조직의 개발자로서 AI로 뭔가 빨라지긴 한 것 같은데, 정말 나아진 게 맞나? 라는 생각이 끊임 없이 든다.

 

정말 생산성이 향상 됐나? 조직에 어떤 영향을 어떻게 끼쳤나? 그래서 조직의 매출이나 기타 지표들은 상승 곡선을 그리고 있나?

 

그 무엇도 명쾌하게 설명할 수 없는 것 같은 답답한 상황인 것 같다. 과도기를 어느 정도 넘어가고 있다고 생각하는데, 이 시점에서 어느정도 정리할 수 있는 생각들은 정리할 필요가 있다고 판단이 섰다.

 

 

 

개인은 빨라진 것 같다. 그렇다면 조직은?

개인의 생산성이 올라간 건 부정하기 어렵다. 예전 같으면 누군가에게 부탁하거나 미뤄뒀을 일을, 각자가 필요한 걸 직접 만들기 너무 쉬워졌다. 거기까진 분명한 이득이다.

 

문제는 그 다음이다. 조직의 경우 직접 만든 그 무언가가 이미 운영되고 있는 제품에 닿는 순간, 결국 관련 담당자의 리뷰가 반드시 필요한 상황에 놓인다. 물론 이 부분들까지 관련 담당자들의 컨텍스트들을 잘 정리해서 자동화 할 수 있다. 리뷰, QA, 배포 등 모든 영역에서 가능하다. 하지만 그게 항상 옳지만은 않다는 것을 모두가 알고 있기 때문에 Human-Loof, Human-in-the-Loof 라는 용어도 생겨났다고 생각한다. 결국 필요 시에는 담당자가 들여다보고 검증하고 책임질 수 있는 형태로 다듬어야 한다는 말이다.  그럼 이건 생산성이 향상되었다고 할 수 있을까? 누군가는 생산성이 향상되었다고 할 수 있겠지만, 이걸 검증 해야하는 사람의 입장에서는 일이 더 늘어났다고도 볼 수 있다. 그렇다면 이게 조직 관점에서 더 이득일까? 라고 물으면 모두가 그렇다. 라고 할 수 있을까?  전체의 관점에서는 비슷한 자리에서 일의 모양만 바뀐 것 같기도 하다.

 

 

조직 내 개발자의 생산성은 정말 올랐나?

그럼 우리 조직으로 문제를 가져와서, 개발자 본인의 생산성은 정말 올랐을까?

우리와 비슷한 유사 스타트업들도 스프린트 단위의 업무를 진행하는데, 예나 지금이나 마찬가지로 스프린트의 일정을 준수하며 개발하는 건 똑같다. 달라진건 직접 코드를 작성하는 것에서, 코드를 LLM이 더 잘 작성할 수 있게 PRD를 다듬고, 산출물을 리뷰하는 역할의 차이가 전부이다.

 

도대체 어떤 부분이 진짜로 향상된 것일까? 개인이 LLM을 다루는 스킬이 향상된 것을 가지고 개인의 자산으로 남았으니 잘했다고 칭찬을 해야할까? 그것도 잘 모르겠다. 어쨋든 시대의 변화에 따라 LLM을 다루는 스킬도 역량이라고 했을 때, 그것을 다시 조직의 무언가로 환원할 수 있는 구조를 만들어가고 있는지에 대해 되돌아 볼 필요가 있다.

 

 

나 또한 마찬가지다. 결국 큰 컨텍스트에서 작은 단위로 쪼개어 개발을 할텐데, 그게 LLM Driven Development에서는 자연스럽게 작은 단위에서 발생하는 반복적인 무언가를 스킬 단위로 분리하고, 반복적인 작업에 필요한 것들을 컨텍스트화하고, Do-Not 행위들을 가드레일로 삼고 하는 등의 행위들을 자연스럽게 하고 있는데, 이게 조직 단위로 쌓일 수 있는 것들을 같이 만들어 나가고 있는가? 에 대해서는 세모이다. 전사적인 AX를 시도하고 있지만, 제품의 스프린트에서는 별개로 보았던 것 같다. 나의 개발 행위와 AX 행위에서 교집합을 찾지 않았던 것이 문제였던 것 같다.

 

 

 

그럼 조직으로 관점을 넓혀볼까? 어떻게?

스프린트 밖의 조직 AX로 넘어가서, 비개발자들의 업무 효율을 개선해보려고 이런저런 시도들을 했고, 하고 있다. 결과적으로 개선되는 부분이 생기다보니 생산성이 오르는 것 같고 조직의 형용할 수 없는 무언가(?)도 올라가는 것처럼 보이더라. 서론에서 언급했던 지표나 뒷받침 할 수 있는 근거는 아직 부족해서, 개인 블로그 회고 형태로 밖에 쓸 수 없지만 언젠간 기술 블로그 형태로 정리해서 공유해보고 싶다.

 

여튼 AX의 시행착오를 겪으면서 여태 가장 크게 문제점이라고 생각했던 부분은, 초기에 무언가 구축할 수 있는 환경을 전달했을 때,

 

"이런 것도 AI로 할 수 있구나. 나도 해보고 싶다. "

 

가 아니라

 

"오 편하네, 근데 이런건 더 있으면 좋겠네, 이것도 해달라고 해야지"

 

로 나에게 되돌아오는 것이 가장 큰 문제라고 생각한다. 본인이 직접 개선해보거나 만들어보는 경험을 통해 무한히 확장될 수 있는 사고, 경험들을 의존성 체인에 갇혀 우물 안 개구리가 되는게 아닐까 안타까운 심정이 든다.

 

 

 

조직말고 개인으로 돌아와서, 가장 큰 문제는 내가 LLM을 통해 병렬로 싸지르고 다닌 모든 것들에 대해 나는 완벽하게 알고 있나? 에 대한 의문이다. 분명 제품적으로, 조직 내부적으로 개선이 되었는데 내가 구현에 대해 모든 것들을 이해하고 있지는 않은 상태가 더러 발생했다. 이게 조직에 쌓이면, 나중에 어떤 모양으로 돌아올지 잘 모르겠다만 부정적인건 분명하다. 나부터 이 악습을 끊을 필요가 있다.

 

공부하자 공부

 

 

 

AX? 숫자로 증명이 가능한가?

이력서를 쓸 때도, 기업 소개를 쓸 때도 PR의 수단은 결국 숫자. 지표라고 생각한다. 그래서 도달하는 결론은 숫자다.

물론 지금 당장 AI를 통해 어떤 지표가 개선이 되었고는 잘 모른다. 미지수이다. 하지만 모든 시도들이 조직적인 자산으로 남기만 해도 언젠가 가속화의 수단이 될 것이라는 생각이 든다. 다만 지금 그게 어떻게 남고 있는지, 남겨야 하는지에 대해 시도해보면서 시행 착오를 겪는 과정에 있는 것 같다. 조직의 컨텍스트를 SSoT로 한 군데에 모은지는 벌써 꽤 되었지만, 이건 단순 시발점에 불과하니까.

 

결국 AI에 돈을 더 쓰는 만큼 다른 부분에서 향상된 것이 있나? 그걸 숫자로 표현할 수 있나? 에 대해 생각해보게 된다.

이 질문에 답하지 못한다면, 결국 기분의 영역으로 AI로 뭔가 잘 되고 있어!!! 라는 환각 상태에 계속 머무르지 않을까?

 

 

 

 

마치며 (완전 다른얘기)

Opus 4.7이 나온 시점부터, 뭔가 겹겹이 쌓였던 하네스를 걷어내고 있다.

모델 버전이 올라갈수록 LLM 자체의 내부 하네스가 견고해질 것이라고 생각한다. 그래서 내가 덧대둔 로컬 세팅을 조금씩 덜어내고 있었는데, Opus 4.7, GPT-5.5가 나온 시점부터는 아예 모델의 업데이트마다 로컬 세팅을 모두 제거하고 퓨어한 상태에서 시작해보고 있다. 그래야 그 모델을 있는 그대로 이해하고 그 위에 현재에 필요한 새 하네스를 깔아볼 수 있기 떄문이다.

 

도구는 점점 알아서 잘해지기 때문에, 그럴수록 나는 내가 뭘 알고 모르는지를 더 또렷이 보는 눈을 길러야겠다. 빨라진 만큼 남는 게 있는지, 그 남는 것들을 내가 이해하고 내 것으로 만들어 성장할 수 있는지.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

4월 회고 - Claude Blue와 Grafana Meetup 오거나이저

회고 2026. 4. 30. 20:20
728x90
728x90

 

서론

눈에 보이면 닥치는대로 하면서 최대한 중심을 지키려고 했던 4월이었다. CLI와 API를 합하면 200억 토큰을 조금 넘게 사용해오면서 나에게도 드디어 진하게 우울증이 왔다. LLM을 점점 많이 사용하면서 회의감 같은 것들이 들기 시작했다. (한국에서는 AI로 인한 우울증을 Claude Blue라고 하더라.) 그럼에도 원하는 것들이, 해보고싶은 것들이 있기 때문에 킵 고잉할 수 밖에 없었던 4월이었다.

 

개인 AI CLI 사용량

 

 

 

Claude Blue

그 전에도 나는 무언가를 시작하면 항상 끝장을 봐야하는, 최상위권은 달성을 해봐야하는 유형의 사람이었다. 개발자로 커리어를 시작하면서도 마찬가지었다. 벽은 너무 높지만 착실히 하나하나 쌓아나가며 의지가 꺾이지 않는다면 언젠가는 도달할 수 있지 않을까? 라고 생각하며 22년부터 25년까지 정말 미친듯이 몰두했던 것 같다.

 

 

 

처음 LLM 코딩 도구들이 등장했을 때, 생산성이 가속화되고 반복 작업들을 대신 해줄 수 있는 것에 매우 만족하며 지냈다.


언제부터였을까, 아마 Opus 4.5가 나오면서부터 조금 많이 변했던 것 같다. 이전까지는 여전히 깊이가 중요하고 내가 처한 상황에서 적절한 선택을 할 수 있는건 엔지니어의 필수 역량이며, LLM도 도구이자 수단으로 오케스트레이션을 할 수 있어야 한다고 생각했다. 하지만 도구들도 서로 경쟁하며 발전하고 Claude Code가 장애가 발생하면 Codex 등의 여러 대체 수단도 경쟁하며 성장하고 있고, 이 성장 속도는 내가 성장하는 것과는 비교할 수 없을 정도로 가파르게 성장하고 있다.

 

그러다보니, 개발자로 언젠가 닿고자 했던 영역 자체를 부정하게 되었고 그러다보니 내가 사랑하던 개발 자체에 회의감이 들기 시작했다.

 

 

 

꺾이면 어쩔건데.. 존나게 해야지

그래도 다행인건 무언가 만들면서 누군가의 불편함을 해소하고, 더 효율적으로 그들의 리소스를 사용할 수 있게 하는 것에는 여전히 흥미가 있다는 점이다. 조직 내에 조금이라도 불편한 것들을 나서서 해결하고, 지난달 회고에서처럼 정형화된 업무 프로세스를 깨보기도하고 하면서 닥치는대로 이것저것 시도했다.

 

 

 

존나게 하다보니까 느끼는건, 무언가 개발 지식이 깊게 채워지지는 않는다는 것이었다. 물론 내가 가는 방향이 달라서 그런 것 같긴 하지만, 대신 여러 상황을 동시에 생각하는 능력과 여러 병렬 작업 간에 컨텍스트를 관리하는 스킬들이 점점 늘어나는 것 같다.

 

이런 것들에서 개발자와 유사하지만 또 다른 무언가의 스킬들이 늘면서 어떻게든 성장은 하겠구나 라는 생각이 들었다. 그러니까 또 이상하게 재밌어지더라.. 

 

 

 

 

 

빠르게, 빠르게, 그리고 정교하게

그러던 와중에 조직에서 다면 평가를 했다. 현재 내가 밀고있는 점들을 장점이라고 알아주셔서 너무 감사했다. 반대로 단점은 기존에도 가지고 있었던 것들이라, 더 빠르게 빠르게 병렬적으로 일을 하면서 더 단점이 뾰족하게 드러났다고 생각이 들었다. 조금 더 정교하게 깎을 필요가 있겠다는 생각이 들었다. 직접 코드를 짜든 AI Native하게 일을 하든 말이다.

 

 

 

혼란스러움에 익숙해지기... 익숙해질 수 있을까

정말 혼란스럽다는 말을 입에 달고 사는 것 같다. 스타트업에서의 혼란스러움을 회고에서도 자주 나타냈었는데, 이 위에 작금의 산업 혁명과도 같은 AI들 때문에 더 가중치가 쌓이는 것 같다. 아마 글에서도 정리가 안되는 모습이 많이 보일 것이라고 생각한다.

 

혼란을 잠재울 수 있다면 너무 좋겠지만, 요즈음 시대에 나처럼 나약한 범부들이 혼란을 어떻게 잠재울 수 있을까.. 그냥 프론티어들의 뒤를 따르면서 LTS를 잘 쓰는 장인으로 익숙해지는 수밖에..

 

 

 

내 오픈소스 이야기

2월달엔가 CLI 토큰 사용량 트래커인 ccusage가 너무 느려졌다. 기존에도 세션 파일은 쌓여있었을텐데 거의 로딩에 1분이 걸릴 정도로 느려져서, Rust로 포팅하다시피한 toktrack이라는 오픈소스를 직접 만들었다.

 

만들 때 홍보를 한 번 하고, 그 뒤로 건드리지 않았는데 어디선가 입소문이 타는건지 이번달에 갑자기 스타 수가 2배나 늘었다.

그래서 기분이 좋다. 그렇다.

 

 

 

 

 

Grafana Meetup을 개최하다

Loki에 기여를 수 차례 했던 것을 계기로, 싱가폴의 Grafana 매니저분과 연결되었다. 한국의, 서울의 그라파나 밋업의 오거나이저가 될 생각이 없냐고... 그 내가 SRE도 아니고 단순 기여 정도에 그치지만, 개발자 커뮤니티를 운영하면서 오는 소프트 스킬이나 기타 다른 부분들의 역량도 향상되지 않을까? 하는 러프한 생각으로 1인으로 개최를 진행했다. 100명 신청을 받았고, 51분 참석하셨다.

 

운영하는데 별로 힘들거라 생각하지 않았던 것 같다. 실제로 한 번 경험해보니, 혼자서는 도저히 불가능하다는 것을 깨달았다. 준비 과정에서 연락해야할 곳도 많고, 당일에도 운영진으로서 발표는 거의 듣지 못했다. 그래도 다들 오셔서 좋아해주셨기 때문에, 그리고 지속 가능한 밋업이었으면 좋겠다는 니즈들을 많이 들어서 지속 가능하게 발전시켜 보려한다.

 

 

 

마무리

팔란티어의 FDE라는 직군의 등장 이래로 벌써 많은 시간이 흘렀다. LLM의 발전도 벌써 엄청나게 되었고 최근 Codex의 GPT 5.5는 정말 감탄스러운 속도와 품질을 제공하고 있다. 개인적으로는 Opus 4.7은 GPT 5.5에 비하면 음식물 쓰레기라고 생각한다.

 

하네스, 헤르메스 등의 LLM을 잘 다루기 위한 트렌드는 계속 변한다. 물론 프론티어들이 발굴해내는 영역이겠지만, 그 사이에서 중심을 잃지 않아야 한다는 것이 가장 중요한 것 같다. LLM이던 그 이전의 DDD 등의 아키텍처나 디자인 패턴 또한 상황에 맞게 적절하게 가져다 쓸 수 있어야 했고 지금의 트렌드도 마찬가지다. 단순 PoC를 만드는데 엄청나게 많은 스킬셋이나 훅으로 가드레일이 덕지덕지 붙어 있는 셋업? 아니면 그 유명한 YC의 gstack을 가져다 쓸 필요는 없을 것이다.

 

내 중심을 지키면서, 환경에 대한 객관화를 항상 해두고, 다음 스텝을 미리 생각해놓는 습관이야말로 기초 소양이자 가장 중요한 덕목 중 하나이지 않을까 라는 생각이 드는 요즘이다.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

늦은 3월 회고 - 기존의 업무 방식을 깨보기, AX로 나아가기

회고 2026. 4. 8. 22:01
728x90
728x90

 

들어가며

꽃이 만개하든 말든, 여러 다양한 시도들을 하며 재밌게 바쁘게 알차게 보냈던 3월의 회고.

3월엔 이전의 회고들에서도 주구장창 얘기했던, 깊게 하는 생각을 다 깨고 빠르게 빠르게 바로 시도해 볼 수 있는 기반을 닦았다고 생각이 든다.

 

 

 

기존의 틀을 깨보기

어떻게 보면 AI 과도기인, 산업 혁명(?) 과도 같은 시기에 있는 지금. 아무리 스타트업이라고 하더라도 스프린트에 어느정도 고착화되어 있을 수 있다. 우리 또한 마찬가지라고 생각했다. 기존 업무 방식인 스프린트 단위의 사이클도 물론 빠르지만 PM/디자이너/개발자 등의 업무 체계가 명확하게 나뉘어져있고, 의사 소통에서 오는 병목이 반드시 존재한다.

 

물론 AI Native하게 바로 가자! 도 아니고, AI를 맹신해서 하는 말도 아니다. 다만 지금 시점에는 스프린트 업무를 가져가면서 병렬로 충분히 가져갈 수 있는 부수 업무들이 많아질 수 있다고 생각한다. 또 다른 얘기로는, 본인의 판단이 맞다고 생각이 든다면 어느정도 PoC를 빠르게 내볼 수 있는 환경 정도는 이미 갖추어져 있다고 생각이 든다.

 

그래서 시도했던건, 조직 내에 여러 레거시 제품들 중에서, 꾸준히 수익이 나는데 휴먼 루프가 많은 것들을 선정하여 1인 풀스택으로 AI Native하게 개발을 진행했다. 그들의 리소스를 절감하면서, 자연스레 제품의 뎁스도 간소화도 이루어졌고 그 결과 해당 기능들을 이용하는 고객들이 조금 늘어났다.

 

 

풀스택 개발을 자랑하려고 하는 것도, 컨텍스트 엔지니어링을 좀 잘하는 것 같다. 라고 으스대려고 하는 말도 아니다. 다만 기존의 업무 방식을 냅다 망치들고 갖다 깨버릴 수 있는 사람이 나타났다!!! 정도로 이해해주길 바란다. 충분히 의도한 바가 맞고, 이런 시도들이 모여 결국 조직의 업무 방식이 긍정적으로 변화할 수 있길 바라는 마음이다.

 

 

 

AX, AX, AX!!!!

AI 관련된 글을 많이 쓰긴 하지만, AI 맹신론자는 아니다. 오히려 Claude Blue라고 불리는, AI 우울증 환자 중 한명이다.

그럼에도 분명한건, 더 많은 기여를 할 수 있는 환경이 깔렸다는 것에는 이견이 없다.

 

위에 업무 틀을 깨부수는 시도를 했던 것 처럼, 업무 틀을 깨부술 수 있는 가장 좋은 것이 전사적으로 AI를 활용하여 필요한 것들을 만들어 나갈 수 있으면 어떨까? 하는 것이었다. 그게 AX가 되겠지..

 

그 전까지는, 전사적으로 가져갈만한 조직적인 컨텍스트 관리 도구라던가, 부수적인 봇들을 만드는데 치중했던 것 같다. 뭔가 이런 것들로는 한계가 있을 것이라고 판단해서 - 결국 내가 계속 만들게 되면 업무 틀을 깨는게 아니게 되니 - 3월 초에는, 제품 운영팀 리드분께 Claude Code 사용을 위해 하네스 세팅을 해드리는 것으로 방향을 틀었다.

 

 

 

하루에도 수십 번씩 커밋을 하시면서 나아가시는 모습들을 보면서 괜히 뿌듯하다. 요새는 직접 하네스도 고쳐보시고 스킬도 직접 만들어보시는 등 사내 AX 챔피언으로 자리매김하고 계신다.

 

의도는 명확했던 것 같다. 최초에는 운영 팀 분들이 쓸 수 있는 전용 대시보드가 필요했고 - 구글 시트, 어드민 페이지 등등 여러 관리 도구들을 통합하기 위함 - 이걸 통합하는 PoC를 빠르게 구축해 드리려했다. 하지만 니즈를 듣고 이해하는데도 병목이 생기니, 직접 해보시라고 깔아드린 것이었다.

 

이런 사례들도 하나하나 씨앗이 되어 조직적인 AX의 한 걸음을 걸을 수 있지 않을까? 라는 생각이 든다. 더불어 내가 만든 도구들도 잘 말아서, 조직적인 AX에 여기저기 활용할 수 있도록 시도해 볼 생각이다.

 

 

 

묵묵히 나아갈 걸음

프론티어가 아니면 어떤가 라는 생각이 계속해서 든다. 조직적으로도 개인적으로도 말이다.

며칠 전 Claude Code 유출 사건 때 Sionic AI의 진형님이 몇 시간 만에 Rust로 클로드 코드를 포팅한 레포가 화제가 됐다.

오픈클로를 만든 사람이나, OmC, OmX등을 만든 예찬님, 링크드인에 좋은 사례들을 공유해주시는 프론티어처럼 욕심을 내고 잘 안됐을 때 현타가 오는 경험도 겪었던 것 같다.

 

현재 공유 오피스 건물에 AICX 팀을 별도로 또 운영하고 있는, 미디엄 블로그로 AX 사례를 거의 찍어내다시피 공유하는 마이리얼트립 또한 어떻게 보면 조직적인 AX는 프론티어라는 생각이 들었다.

 

요즘은 그에 비해 속도가 느리다고 도태되었다거나, 조급해하는 생각을 아예 없애버렸다. 생각해보면 Node도 Nightly 스냅샷을 내놓고 여러 실험들을 하지만 결국 LTS가 선택받는다. LLM 사용 사례를 봐도 여러 키워드들이 나왔다 사라졌지만 최근 트렌드는 결국 Nightly를 시도했던 프론티어 분들이 결론낸 하네스나 컨텍스트 엔지니어링이고, 그걸 잘 말아서 쓰는 사람이 결국 퍼포먼스를 내고 있다고 생각한다.

 

그래서, 결국 도구에 불과한 이 녀석을 멀리 하지만 않고 변화에 능동적으로 대응할 수 있으면 되지 않을까? 라는 조금 내려놓는 마음가짐을 요새는 가지려고 하는 중인 것 같다.

 

 

 

 

마치며

다음달 회고에는 단기 목표도 어느정도 이뤘는지 리마인드 하기 위해 이번 달에 세운 목표를 첨부하는 것을 마무리로, 글을 마무리하려고 한다. 어떤 시대건 간에 결국 묵묵히 해내고 해낸 것들을 체화할 수 있는 사람이 결국 성장하고 나아가지 않나. 라는 생각이다

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

uWSGI buffer-size와 502, 서버가 죽었나?

삽질/트러블슈팅 2026. 4. 2. 23:05
728x90
728x90

서론

여느 때처럼 평화롭게 개발을 하던 어느날, 프로덕트의 dev 환경에서 502가 계속해서 발생했습니다. 지표도 정상이고 헬스 체크도 정상이고, 도대체 무슨 문제인지 긴가민가 했습니다. 로그를 확인 해 본 결과 처음보는 낯선 에러가 발생하더군요

 

요약하자면 Python WSGI 애플리케이션 서버인 uWSGI의 buffer-size 설정 값을 초과했기 때문이었습니다. 찾아보니 이 buffer-size는 요청 헤더의 사이즈의 설정 값이라고 하더군요.

 

Express를 사용했을 때를 떠올려보면 발생하지 않았던 문제였습니다. 요청 헤더가 커서 요청이 Drop된 적이 없었기 때문입니다.

이에 관련된 CS적인 지식은 기초 수준의 간단한 것입니다만, Django 진영의 웹 서버는 왜 이런 방식을 선택했는지 궁금해서, 기록 겸 포스팅을 남깁니다.

 

 

 

원인

위에서 정리했듯이, 원인은 HTTP 요청 헤더의 사이즈 초과입니다. 

 

 

uWSGI 관련 공식 문서를 보면, 버퍼 사이즈는 매우 작게 설정되어있으며, 로그에서 이상 현상이 감지된다면 더 큰 사이즈로 변경이 필요할 수도 있다고 언급되어 있습니다. 말은 쉽지만 실제 환경에서는 충분히 문제가 발생할 수 있고 저 또한 겪었습니다. 핫픽스로 배포를 나가도 물려있는 CICD 때문에 10여분 넘게 다운타임이 발생할 수 있는 것이죠 (실제로는 서버 다운은 아니고 UX적인 다운타임이겠습니다.)

 

정확한 원인을 추적하기 위해 좀 더 깊게 파악해보겠습니다.

 

일반적인 Python 웹 서비스는 요청이 리버스 프록시(nginx)를 통해 uWSGI를 거쳐 Django로 들어오는 흐름입니다. 리버스 프록시는 클라이언트로부터 요청을 받아 uWSGI로 패킷을 전달 할 때 uwsgi 바이너리 프로토콜로 변환해서 보내게 됩니다.

 

 

# nginx 설정
location / {
  uwsgi_pass unix:///tmp/uwsgi.sock;
  include    uwsgi_params;
}


uwsgi_pass를 쓰면 nginx가 HTTP 헤더들을 uwsgi 프로토콜의 key-value 쌍으로 변환하고, 이걸 하나의 uwsgi 패킷으로 직렬화해서 전송합니다. uwsgi 프로토콜 스펙 자체가 하나의 패킷을 전달 받아야만 하는 스펙으로 설계가 되어있기 때문입니다. uwsgi 프로토콜은 패킷 헤더에 datasize라는 변수를 16비트 정수로 담습니다.

struct uwsgi_packet_header {
  uint8_t modifier1;
  uint16_t datasize;
  uint8_t modifier2;
};

 

 

패킷 헤더의 datasize가 뒤따르는 전체 데이터 크기를 나타내는 구조라서, 요청 메타데이터를 여러 패킷으로 나눠 보내는 개념이 없습니다. 그래서 수신 측인 uWSGI도 이 패킷을 고정 버퍼에 통째로 받는 구조가 됩니다.

 

즉, 버퍼 사이즈를 초과하는 요청이 들어오면 uWSGI는 Django에 요청을 넘기지 않고 Drop하게 됩니다. 서버가 죽은 것이 아니라 요청이 앱에 도달하기 전에 버려지게 되어 헬스 체크는 200인데 UX는 다운된 것처럼 보였던 것입니다.

 

 

 

 

Express

이전에 썻던 Node의 Express는 파싱 방식과 기본값에서 차이가 있었습니다.

Node의 HTTP 파서인 llhttp는 헤더를 고정 버퍼에 담지 않습니다. 들어오는 데이터를 바이트 단위로 스트리밍하면서, 누적 크기만 카운터로 추적합니다.

 

nodejs의 http parser

 

고정 버퍼에 통째로 담는 uWSGI와 달리 파싱하면서 카운터만 올리는 방식입니다. 그리고 결정적으로 기본 제한값이 16KB로 크기 때문에 대부분의 상황에서 발견하지 못했습니다. 더불어 uwsgi처럼 바이너리 프로토콜 변환 없이 raw HTTP를 직접 파싱하기 때문에 프로토콜 크기의 제약도 없습니다.

 

즉, Express를 사용했을 때는 같은 요청이여도 서버가 받아들이는 구조가 다르기 때문에 한 번도 겪어본 적이 없었습니다.

 

 

 

 

해결

원인을 알았으니 해결책은 명확하겠죠..?

 

근본 원인인 헤더가 커진 이유를 조사하기 시작했습니다. 짐작하시겠지만, 프로덕트가 커져가면서 구워지는(?) 쿠키도 많아졌습니다. 구체적으로는 A/B 테스트나 지표 추적을 위해 이벤트를 심는 양이 늘어남에 따라 쿠키가 같이 늘어났습니다. 디버깅 결과 전체 쿠키의 70% 이상이 해당 지표 추적을 위한 쿠키였고, 계속해서 늘어나고 있었습니다. 브라우저가 요청마다 이 쿠키를 전부 실어 보내는 구조였기 때문에 점점 헤더 사이즈가 커지면서 결국 터져버렸던 것입니다.

 

저희는 일단 버퍼 사이즈를 늘리는 것으로 선조치를 했습니다. 하지만 쿠키가 계속 늘어난다면 또 터질테니 근본 해결책은 아니겠죠.

 

 

 

 

CloudFront Origin Request Policy에서 쿠키를 필터링하거나 로컬 스토리지로 쿠키를 마이그레이션, 쿠키 도메인을 분리하는 등의 방안을 고려중에 있습니다. 각각 하나씩 간단히 살펴보면서 마무리하겠습니다.

 

CloudFront가 오리진(nginx/uWSGI)에 요청을 전달할 때 필요한 쿠키만 화이트리스트로 전달하도록 설정이 가능합니다.

브라우저는 쿠키를 다 보내지만 CF가 걸러줄 수 있도록 설정이 가능하다고 합니다. 프론트 코드 변경 없이 인프라 설정만으로 적용이 가능하지만, CF를 통하지 않고 직접 접근할 때는 효과가 없습니다.

 

서버에 보낼 필요가 없는 데이터를 쿠키에서 로컬 스토리지로 마이그레이션하는 방법도 있습니다.

이 방법은 서버가 읽어야 하는 쿠키는 이동이 불가능하고, 프론트 코드 변경이 필요할 수도 있습니다.

 

마지막으로 쿠키 도메인을 분리하는 방법입니다.

.example.com에 쿠키가 설정되면 모든 서브도메인에 전송됩니다. 이걸 api.example.com으로 분리하고 쿠키 도메인을 서브도메인 별로 격리하면, API 요청에는 해당 서브도메인 쿠키만 실리게 되겠죠. 가장 구조적인 해결이 될 것이라 생각하는데, 서드파티 스크립트인 GA나 이벤트 도구 등이 최상위 도메인에 쿠키를 설정해야한다면, 통제가 어려울 수도 있겠습니다. (아직 확인해보진 않았습니다.)

 

 

 

 

마무리

오랜만에, 정말 오랜만에 AI에서 벗어나 실제 문제를 좀 깊게 파볼 수 있는 시간이었습니다. 

 

 

 

 

Ref

https://peps.python.org/pep-3333/

https://uwsgi-docs.readthedocs.io/en/latest/Protocol.html

https://uwsgi-docs.readthedocs.io/en/latest

https://github.com/nodejs/llhttp

https://github.com/nodejs/node/blob/main/src/node_http_parser.cc#L1023-L1030

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-origin-requests.html

https://datatracker.ietf.org/doc/html/rfc6265#section-5.4

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록