Jun Kang 👋

  • AI와 빅데이터로 건설업계의 디지털 혁신을 이끄는 (주)산군의 Jun Kang입니다. 대규모 데이터 처리, DB 최적화, 확장 가능한 시스템 구축에 관심이 많으며, 개발팀의 건강하고 지속 가능한 성장 문화를 문화를 만들어가고 있습니다.
  • 주요 관심사: Backend Development, Cloud Computing, DevOps, Database, AI, 개발 문화 형성

[PostgreSQL] 플래너는 통계를 보고 결정한다 — 그 통계는 언제 틀리나

EXPLAIN ANALYZE를 보면 두 숫자가 나란히 있다. rows=100과 actual rows=1500000. 플래너는 백 행이 나올 줄 알고 계획을 짰는데 실제로는 백오십만 행이 나왔다. 그 계획은 백 행에 맞는 계획이었을 테니, 결과는 느린 쿼리다. 플래너가 왜 백 행이라고 생각했는지를 따라가면 통계에 닿는다. 이 시리즈는 그 통계 이야기다. 통계는 표본이다 플래너는 실행 전에 테이블을 읽지 않는다. ANALYZE가 미리 만들어둔 요약을 본다. ANALYZE는 테이블 전체를 읽지 않는다. 표본을 뽑는다. 표본 크기는 default_statistics_target × 300이고, 기본값이 100이라 3만 행이다. 테이블이 천 행이든 십억 행이든 3만 행이다. ...

9월 19, 2026 · Jun Kang

[PostgreSQL] SKIP LOCKED로 작업 큐 만들기

잠금 시리즈 마지막 편이다. 앞의 두 편이 잠금 때문에 멈추는 이야기였다면, 이번엔 잠금을 이용해서 일을 나누는 이야기다. 문제 작업 테이블이 있다. 워커 여러 대가 여기서 하나씩 집어가서 처리한다. CREATE TABLE jobs ( id bigserial PRIMARY KEY, status text NOT NULL DEFAULT 'pending', payload jsonb, created_at timestamptz NOT NULL DEFAULT now() ); 순진하게 짜면 이렇게 된다. SELECT * FROM jobs WHERE status = 'pending' ORDER BY id LIMIT 1; UPDATE jobs SET status = 'running' WHERE id = ?; 워커 둘이 동시에 SELECT하면 같은 행을 본다. 둘 다 UPDATE하고 둘 다 처리한다. 중복이다. ...

9월 18, 2026 · Jun Kang

[PostgreSQL] 데드락은 어떻게 생기고 어떻게 읽나

지난 편이 한 줄로 늘어선 대기였다면, 이번 편은 대기가 원을 그리는 경우다. 서로를 기다리면 끝나지 않는다 트랜잭션 A가 행 1을 잠그고 행 2를 기다린다. 트랜잭션 B는 행 2를 잠그고 행 1을 기다린다. 둘 다 상대가 끝나야 진행할 수 있고, 상대는 내가 끝나야 진행할 수 있다. A: UPDATE accounts SET ... WHERE id = 1; -- 행 1 잠금 B: UPDATE accounts SET ... WHERE id = 2; -- 행 2 잠금 A: UPDATE accounts SET ... WHERE id = 2; -- B가 끝나길 대기 B: UPDATE accounts SET ... WHERE id = 1; -- A가 끝나길 대기 → 데드락 PostgreSQL은 이걸 막지 않는다. 대신 감지한다. 잠금 대기가 deadlock_timeout(기본 1초)을 넘으면 대기 그래프를 검사하고, 순환이 있으면 그중 하나를 끊는다. ...

9월 17, 2026 · Jun Kang

[PostgreSQL] ALTER TABLE 하나가 서비스를 멈추는 구조

컬럼 하나 추가하는 ALTER TABLE을 돌렸는데 서비스가 멈췄다는 이야기는 흔하다. 원인은 대개 ALTER 자체가 아니다. ALTER가 잠금을 기다리는 동안 그 뒤로 줄이 생기는 구조 때문이다. 잠금은 줄을 선다 PostgreSQL의 테이블 잠금은 여덟 단계가 있고, 서로 충돌하는 조합이 정해져 있다. 실무에서 기억할 건 두 가지면 된다. SELECT는 ACCESS SHARE를 잡는다. 거의 아무것과도 충돌하지 않는다. 대부분의 ALTER TABLE은 ACCESS EXCLUSIVE를 잡는다. 모든 것과 충돌한다. SELECT와도. 문제는 잠금 요청이 도착 순서대로 처리된다는 점이다. 앞의 요청이 못 받고 있으면 뒤의 요청도 기다린다. 뒤의 요청이 앞의 것과 충돌하는 종류라면 그렇다. ...

9월 16, 2026 · Jun Kang

[PostgreSQL] HOT update와 fillfactor — 죽은 튜플을 덜 만드는 쪽

앞선 세 편은 이미 생긴 죽은 튜플을 어떻게 치울 것인가에 대한 이야기였다. 이번 편은 반대쪽이다. 애초에 덜 만드는 방법. UPDATE가 인덱스까지 건드리는 문제 PostgreSQL에서 UPDATE는 새 버전을 쓰는 일이라고 1편에서 정리했다. 그런데 여기 딸려오는 비용이 하나 더 있다. 새 버전이 생기면 그 위치를 가리키도록 인덱스도 갱신해야 한다. 인덱스가 다섯 개인 테이블에서 한 행을 UPDATE하면, 힙에 새 버전 하나가 생기고 인덱스 다섯 곳에 엔트리가 추가된다. 그래서 갱신이 잦은 테이블은 힙보다 인덱스가 먼저 부푸는 경우가 많다. 그리고 인덱스 팽창은 조회 성능에 직접 영향을 준다. ...

9월 12, 2026 · Jun Kang