무한 스크롤 페이지를 끝까지 긁기, 스크롤 반복이 항목을 빠뜨리는 이유

무한 스크롤 페이지를 끝까지 긁기 위해 스크롤 반복문을 돌렸는데 전체 항목 수보다 적게 나온다면, 범인은 대부분 document.body.scrollHeight 비교 방식입니다. 서버 응답이 스크립트의 대기 시간보다 늦게 끝나면 스크롤 루프는 “더 이상 늘어날 콘텐츠가 없다”고 착각하고 일찍 종료해 버립니다. 예를 들어 항목이 1,200개인 목록 페이지에서 이 방식을 쓰면 700~800개 선에서 멈추는 현상이 흔하게 나타나는데, 원인은 네트워크가 아니라 … Read more

긁을 때 브라우저를 띄워야 할까? 숨은 JSON API 찾는 순서

“크롤러로 사이트를 긁을 때 브라우저를 띄워야 할까요?” 답은 대부분 아니오입니다. 개발자 도구의 네트워크 탭을 먼저 열어서 페이지가 실제로 호출하는 JSON API를 찾아내면, 셀레니움이나 플레이라이트 같은 브라우저 자동화 없이도 데이터를 훨씬 빠르고 가볍게 가져올 수 있습니다. 다만 모든 사이트에 이 방법이 통하지는 않아서, 확인 순서와 브라우저가 꼭 필요한 예외 상황까지 함께 정리했습니다. 브라우저 렌더링과 API 호출, … Read more

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