백업은 있는데 복구를 못 할 때, pg_dump와 PITR 중 무엇을 써야 할까요

백업은 있는데 복구를 못 하는 상황은 대부분 백업 방식과 복구 시간(RTO)을 미리 맞춰보지 않아서 생깁니다. pg_dump로 받은 백업은 논리적 스냅숏이라 특정 시점을 골라서 복구할 수 없고, PITR(Point-In-Time Recovery)은 원하는 시점을 고를 수 있는 대신 장애가 나기 전부터 WAL 아카이빙을 켜두지 않았다면 아예 쓸 수 없습니다. 이 글은 PostgreSQL 13 이상에서 recovery.signal / standby.signal 방식을 쓰는 … Read more

행을 지웠는데 디스크가 그대로인 이유, 데드 튜플과 오토배큠 튜닝으로 해결하기

DELETE 쿼리를 수십만 건 실행했는데 df -h로 확인한 디스크 사용량이 줄지 않아서 검색하셨다면, 결론은 간단합니다. PostgreSQL은 행을 지워도 그 공간을 바로 운영체제에 돌려주지 않습니다. 삭제된 행은 “데드 튜플”로 테이블 파일 안에 그대로 남아 있고, 이걸 치우는 오토배큠(autovacuum)이 제대로 돌지 않으면 테이블 파일 크기는 줄어들지 않습니다. 이 글은 PostgreSQL 13 이상, 단일 테이블 기준 autovacuum 파라미터 … Read more

기본키를 UUID로 했더니 느려질 때 — UUIDv4·UUIDv7·BIGSERIAL 비교

기본키를 UUID로 했더니 인서트가 느려질 수 있다는 이야기, 실제로 겪어보면 사실일까요? 조건만 맞으면 사실입니다. 완전히 무작위로 생성되는 UUIDv4를 기본키로 쓰면 B-Tree 인덱스에 값이 뒤죽박죽 꽂히면서 페이지 분할과 캐시 미스가 늘어나고, 테이블이 수백만 행을 넘기는 시점부터 그 차이가 체감될 만큼 커집니다. 이 문제는 시간 순서를 담은 UUIDv7이나 애초에 정수를 쓰는 BIGSERIAL로 기본키를 바꾸면 상당 부분 줄어듭니다. … Read more

인덱스를 만들었는데 Seq Scan이 뜨는 이유, EXPLAIN으로 좁히기

인덱스를 만들었는데 Seq Scan이 그대로 뜨는 상황 때문에 이 글을 검색하셨다면, 답은 대부분 “인덱스가 안 걸려서”가 아니라 “플래너가 비용 계산상 인덱스 쪽이 더 비싸다고 판단해서”입니다. PostgreSQL의 쿼리 플래너는 인덱스 존재 여부와 상관없이 통계 정보, 테이블 크기, 선택도를 종합해 더 저렴한 경로를 고릅니다. 그래서 인덱스를 지우는 것보다 EXPLAIN으로 플래너가 어떤 수치를 보고 있는지 먼저 확인하는 쪽이 … Read more

커넥션 풀 크기를 몇으로 둘까 — pool_size·워커 수 계산법

커넥션 풀 크기를 정할 때 “일단 넉넉하게 잡아두면 안전하다”고 생각하는 경우가 많습니다. 하지만 코어 4개짜리 서버라면 HikariCP가 제시하는 공식으로 계산했을 때 적정 pool_size는 9 정도에 불과합니다. 100, 200씩 크게 잡아둔 풀이 오히려 DB 서버의 컨텍스트 스위칭 부하를 키우고 응답 속도를 떨어뜨리는 경우가 흔합니다. 이 글에서는 pool_size·워커 수·max_connections를 어떤 순서로 맞춰야 하는지, 실제 공식과 숫자로 짚어보겠습니다. … Read more

운영 중 컬럼 추가가 테이블을 잠그는 이유와 피하는 마이그레이션 순서

많은 분들이 ALTER TABLE ADD COLUMN을 가벼운 메타데이터 작업이라고 생각하시는데, 실제로는 운영 중 컬럼 추가가 테이블 전체를 잠그는 사고로 이어지는 경우가 많습니다. DEFAULT 값의 종류, NOT NULL 제약조건 유무, DB 엔진과 버전에 따라 잠금 범위와 지속 시간이 완전히 달라지기 때문입니다. 서비스 중단을 피하려면 nullable 컬럼을 먼저 추가하고, 데이터를 배치로 채운 다음, 제약조건은 검증 단계를 분리해서 … Read more

크롤러가 조용히 0건을 내놓을 때, 진짜 실패인지 판단하는 기준

크롤러를 운영하다 보면 “에러 로그가 없으니 정상 작동했다”고 믿는 경우가 많습니다. 하지만 크롤러가 조용히 0건을 내놓는 상황은 에러 한 줄 남기지 않고도 자주 일어납니다. 0이라는 숫자 자체만 보고 성공과 실패를 가르면 안 되고, 상태 코드·응답 크기·과거 수집량 같은 주변 신호를 같이 봐야 실패인지 정상인지 구분할 수 있습니다. 0건이 나오는 세 가지 서로 다른 상황 같은 … Read more

매번 전부 다시 긁지 않으려면: ETag와 304로 크롤러 재방문 주기 정하기

새벽마다 돌려둔 크롤러가 어제와 똑같은 페이지를 처음부터 끝까지 다시 내려받고 있는 걸 로그에서 발견하면 마음이 무거워집니다. 매번 전부 다시 긁지 않으려면 서버가 보내주는 ETag와 Last-Modified 값을 저장해두고, 다음 요청 때 If-None-Match·If-Modified-Since 헤더에 같이 담아 보내는 조건부 요청 방식을 쓰면 됩니다. 서버가 내용이 바뀌지 않았다고 판단하면 본문 없이 304 Not Modified만 돌려주기 때문에, 대상 페이지 수가 … Read more

같은 URL을 두 번 긁고 있다면 점검할 정규화와 방문 기록 설계

크롤러 속도가 느려지면 네트워크 지연이나 서버 응답 시간부터 의심하는 경우가 많은데, 실제로 요청 로그를 펼쳐보면 같은 URL을 두 번, 많게는 열 번 넘게 긁고 있다면 원인은 네트워크가 아니라 URL 정규화 누락인 경우가 훨씬 흔합니다. 쿼리스트링 순서나 대소문자, 트레일링 슬래시만 달라도 크롤러 입장에서는 완전히 다른 주소로 보이기 때문입니다. 이 글에서는 어떤 표기 차이를 정규화 대상으로 삼아야 … Read more

브라우저로 로그인하고 requests로 긁기: 쿠키·토큰 전달법과 한계

로그인이 필요한 사이트를 requests로 긁어오려다가 로그인 폼부터 막혀서 검색하셨다면, 답은 이렇습니다. 브라우저에서 수동으로 로그인한 다음 그 세션의 쿠키나 토큰을 꺼내 requests.Session()에 그대로 넘겨주면 로그인 페이지를 자동화할 필요 없이 로그인된 상태로 요청을 보낼 수 있습니다. 다만 httpOnly 쿠키, TLS 핑거프린팅, 자동 갱신되는 토큰 때문에 이 방법이 안 통하는 사이트도 적지 않아서, 어디까지 되고 어디서부터 막히는지를 같이 … Read more