API 키를 깃허브에 올렸을 때, 순서는 삭제가 아니라 무효화입니다

커밋 메시지를 쓰고 푸시 버튼을 누른 다음에야, 방금 올린 코드 안에 .env 파일이나 설정 파일 안에 적어둔 값이 API 키였다는 걸 알아차리신 적 있으신가요. API 키를 깃허브에 올렸을 때 가장 먼저 떠오르는 대응은 보통 “커밋을 지우고 히스토리를 깨끗하게 되돌리자”이지만, 그 순서부터 잘못됐습니다. 키가 한 번이라도 올라갔다면 히스토리를 아무리 정리해도 이미 노출된 값 자체는 사라지지 않기 … Read more

LLM 스트리밍이 60초에 끊기는 이유와 nginx·ALB·gunicorn 타임아웃 잡는 법

이 글을 읽으시면 LLM 스트리밍이 60초에 끊기는 원인이 nginx·ALB·gunicorn 세 군데에 동시에 걸려 있다는 것과, 각 지점을 어떻게 고쳐야 하는지 바로 적용할 수 있는 설정값까지 확인하실 수 있어요. 결론을 먼저 말씀드리면, 토큰이 느리게 나오는 LLM 응답은 중간에 “데이터가 안 들어온다”고 판단되는 순간 끊기는데, 공교롭게도 nginx의 proxy_read_timeout과 ALB의 Idle timeout 기본값이 똑같이 60초라서 거의 동시에 끊기는 … Read more

Prometheus 카디널리티가 터질 때, 레이블은 이렇게 골라야 합니다

레이블 하나를 잘못 넣으면 시계열 개수가 어떻게 늘어나는지 계산부터 해보겠습니다. job 레이블 값이 5개, method 레이블 값이 20개, 여기에 user_id 레이블(값 5만 개)을 하나 추가하면 이론상 조합 가능한 시계열은 5 × 20 × 50,000 = 500만 개까지 늘어납니다. Prometheus 카디널리티가 터질 때 실무에서 가장 흔한 원인이 바로 이렇게 고유값이 많은 필드를 레이블로 넣는 습관입니다. 이 … Read more

GitHub Actions가 매번 5분씩 걸릴 때 점검할 캐시 키

이 글에서는 GitHub Actions가 매번 5분 가까이 걸릴 때 캐시 키 설계와 적중률을 어떤 순서로 점검하면 되는지 확인하실 수 있습니다. 미리 답을 드리면, 원인은 대부분 세 가지 중 하나입니다. key에 매번 달라지는 값(커밋 SHA, 타임스탬프)을 넣어서 캐시가 매번 새로 생성되는 경우, 브랜치 범위 때문에 캐시에 접근이 안 되는 경우, 그리고 restore-keys를 안 써서 부분 일치조차 … Read more

CI에서만 테스트가 깨질 때, 로컬과 다른 네 가지를 좁히는 순서

CI에서만 테스트가 깨질 때는 거의 항상 환경 변수, OS·런타임·타임존, 병렬 실행과 격리, 네트워크 접근성 이 네 가지 중 하나에서 원인을 찾을 수 있습니다. 로컬에서는 멀쩡히 통과하던 테스트가 GitHub Actions나 GitLab CI 같은 파이프라인에서만 빨갛게 실패한다면, 코드 로직을 의심하기 전에 이 네 가지를 순서대로 좁혀 나가는 쪽이 디버깅 시간을 훨씬 줄여줍니다. 이 글에서는 각 원인을 어떤 … Read more

docker stop이 매번 10초 걸릴 때 확인할 PID 1 문제

docker stop을 실행할 때마다 왜 매번 똑같이 10초가 걸릴까요? 컨테이너의 메인 프로세스가 SIGTERM을 받고도 반응하지 않아서, 도커가 정해진 대기시간을 다 채운 뒤에야 SIGKILL로 강제 종료하기 때문입니다. 대부분의 경우 원인은 Dockerfile의 CMD 작성 방식과 컨테이너 안에서 PID 1이 신호를 다루는 방식에 있습니다. docker stop이 매번 멈추는 순서: SIGTERM 다음에 오는 10초 대기 docker stop 명령을 내리면 … Read more

Docker 파이썬 이미지가 1GB일 때 — slim·alpine·멀티스테이지 비교

Docker 파이썬 이미지가 1GB일 때 가장 먼저 의심해야 할 건 베이스 이미지입니다. python:3.12처럼 태그를 명시하지 않거나 기본 태그를 그대로 쓰면 Debian 전체 패키지와 빌드 도구가 딸려오면서 이미지가 1GB 안팎까지 불어납니다. slim 태그로 바꾸면 130~150MB대로, alpine 태그로 바꾸면 50MB 안팎까지 줄어들지만 둘 다 공짜로 얻는 이득은 아닙니다. 이 글에서는 세 방식의 실제 차이와, alpine으로 넘어갔을 때 … Read more

워커를 늘렸는데 캐시 적중률이 떨어지는 이유

새벽에 트래픽이 몰려서 워커 프로세스를 4개로 늘렸는데, 정작 대시보드의 캐시 적중률이 뚝 떨어지는 그래프를 보고 당황한 적이 있으신가요. 원인은 메모리 부족이나 캐시 로직 버그가 아니라, 워커마다 캐시가 따로 만들어지는 구조 때문인 경우가 많습니다. 이 글에서는 프로세스별 메모리가 어떻게 캐시 적중률을 갉아먹는지, 그리고 어떤 조건에서 공유 캐시로 옮기는 게 맞는지를 구체적으로 짚어보겠습니다. 워커 수를 늘렸는데 캐시 … Read more

캐시가 만료되는 순간 DB가 무너지는 이유, 스탬피드를 막는 세 가지 방법

이 글에서는 캐시가 만료되는 순간 DB가 무너지는 정확한 원인과, 현장에서 실제로 쓰는 방어 기법 세 가지를 코드와 함께 확인하실 수 있습니다. 핵심만 먼저 말씀드리면, 캐시 미스 요청이 한순간에 몰리는 ‘캐시 스탬피드’ 현상은 분산 락, TTL 지터, 확률적 조기 재계산 중 하나만 제대로 적용해도 크게 줄어듭니다. 다만 세 방법은 구현 난이도와 적용 조건이 서로 다르기 때문에, … Read more

어느 쿼리가 느린지 모를 때, pg_stat_statements와 EXPLAIN 읽는 순서

결론부터 말씀드리면, 어느 쿼리가 느린지 모를 때는 감으로 찾지 말고 pg_stat_statements로 범위를 좁힌 다음 EXPLAIN으로 원인을 확인하는 순서를 지키는 것이 가장 빠릅니다. 로그를 뒤지거나 “이 쿼리가 느릴 것 같다”는 추측으로 튜닝을 시작하면 시간만 쓰고 원인은 못 찾는 경우가 많습니다. 이 글은 PostgreSQL 13 이상 환경을 기준으로, 통계 확장을 켜는 설정부터 EXPLAIN 출력에서 실제로 봐야 할 … Read more