아카이브
날짜별로 모아 본 글 전체.
섹션8 실습 2: PostgreSQL keyset 페이지네이션
PostgreSQL 17.11에서 복합 커서 값과 경계 조건을 확인하고, 복합 B-tree가 Index Only Scan에 쓰이는지 살펴본다.
섹션8 실습 1: PostgreSQL 행 잠금과 데드락
PostgreSQL 17.11 임시 인스턴스에서 행 잠금 대기와 데드락을 재현하고, 데이터 규모에 따라 달라지는 실행 계획을 비교한다.
섹션8: PostgreSQL 페이지네이션
OFFSET의 누적 비용과 페이지 이동 흔들림을 확인하고, 단일 키와 복합 정렬 키를 이용한 keyset 페이지네이션을 정리한다.
섹션8: PostgreSQL 동시성 제어
MVCC 읽기와 네 가지 행 잠금, 대기 후 재평가, 데드락, 다중 행 잠금 순서와 Django ORM의 범위를 구분한다.
DB 커넥션 고갈: FastAPI의 스레드와 DB 커넥션 풀
FastAPI의 동기·비동기 라우트 실행 방식과 SQLAlchemy DB 커넥션 풀이 별도로 동작하는 구조를 비교한다.
DB 커넥션 고갈: Django ASGI에서 커넥션이 쌓이는 이유
요청별 ThreadSensitiveContext와 CONN_MAX_AGE가 Django ASGI의 동기 ORM 커넥션 수명에 어떤 영향을 주는지 정리한다.
DB 커넥션 고갈: WSGI와 ASGI의 동기 스레드 수명
Gunicorn의 동기 WSGI worker 프로세스와 ASGI 이벤트 루프, 동기 코드용 executor 스레드의 수명을 구분한다.
섹션8 실습 1: Django ASGI에서 idle 커넥션 재사용 확인
Django 4.2 ASGI에서 CONN_MAX_AGE에 따라 요청 뒤 PostgreSQL 커넥션이 닫히거나 idle로 남고, 다음 요청에서 재사용되는지 확인한다.
주문 원장과 잔고 원장, 순서는 어디서 맞추나
KIS 모의투자 체결을 주문 원장에 쌓고 잔고 원장을 비동기로 갱신하면, 들어온 순서대로 반영한 평균 단가가 틀린다. 순서를 맞추려 하지 말고 KIS 체결 시각 순으로 다시 계산하고, 음수 잔고는 거절이 아니라 재조회 신호로 다룬다. PostgreSQL 17 에서 돌려 확인했다.
시퀀스 id 는 스냅샷을 따르지 않는다
커밋 안 된 INSERT 가 있어도 다른 트랜잭션의 INSERT 는 기다리지 않고 다음 id 를 받는다. id 는 보이는 최대값이 아니라 트랜잭션 밖의 카운터에서 나오기 때문이다. MySQL 8.4 와 PostgreSQL 17 에서 직접 돌려 확인했다.
디버거 없는 Zed에서 코프링 하기
Zed로 Kotlin + Spring을 개발하면 디버거와 Spring 정적 분석이 빠진다. 컨벤션과 테스트로 어디까지 메울 수 있는지, 그래도 디버거가 필요한 순간은 언제인지 정리했다.
Zed에서 Kotlin 디버거 붙이기
Zed에서 Kotlin 중단점이 안 잡힌다는 결론은 이제 반만 맞다. 무료 Kotlin LSP 안에 디버거가 들어왔고, 막힌 곳은 Zed 확장 API와 서버·클라이언트 사이의 작은 불일치다. 시제품으로 붙여 보며 알게 된 것.
GitHub Pages 블로그를 구글·네이버 검색에 노출시키기
robots·sitemap이 다 있는데도 검색에 안 뜨는 건 대개 순위가 아니라 발견 단계의 문제다. github.io에서 쓸 수 있는 인증 방법과, 소유확인이 실제로 열어 주는 것을 정리했다.
섹션6: 안 쓰는 파티션을 CSV 로 내리기
detach 는 떼어 내기일 뿐 용량도 그대로다. MariaDB 는 저장 엔진을 바꿔서, Postgres 는 CSV 를 외부 테이블로 만들어 파티션에 다시 붙여서 보관한다. 각각 무엇을 잃는지 확인했다.
섹션6 실습: 파티션을 직접 쪼개 보기
부모에 파일이 없다는 것부터 DEFAULT 함정, 트리거로 파티션을 못 만드는 이유, 프루닝이 안 되는 조건, 복합 PK·FK 까지 PostgreSQL 17 컨테이너에서 하나씩 확인한다.
섹션6: 데이터베이스 파티셔닝
부모 테이블에는 힙도 인덱스도 없고 파티션 하나하나가 완전한 테이블이다. 유니크에 파티션 키가 필요한 것, FK 가 복합으로만 걸리는 것, 파티션 키 없는 조회가 모든 파티션을 뒤지는 것이 전부 여기서 나온다.
섹션4,5: 쓰기가 쌓이면 인덱스는 어떻게 되나
클러스터 구조인 InnoDB 가 더 자주 망가질 줄 알았는데 반대였다. 갱신·삭제는 스스로 되돌리고, 난수 PK 만은 스스로 못 고친다.
섹션4 실습: 인덱스 페이지 안을 직접 열어보기
인덱스가 파일 안에서 어떻게 생겼는지부터, key column 과 non-key column 이 어디서 갈리는지까지 pageinspect 로 페이지 바이트를 직접 열어 확인한다.
섹션4: key column 과 non-key column
차이는 유일성이 아니라 값이 내부 페이지까지 올라가느냐다. non-key column 으로 넣으면 인덱스가 작아지는 게 아니라 트리가 한 층 얕아진다.
섹션4,5: 데이터베이스 인덱싱과 B+트리
인덱스가 디스크에서 어떻게 생겼는지, 왜 B트리가 아니라 B+트리인지. 그리고 쓰기가 쌓였을 때 PostgreSQL 과 InnoDB 의 인덱스가 서로 다르게 망가지는 이유.
열 저장은 안에서 어떻게 생겼나 — row group, 정렬 키, zone map
1개의 댓글열 저장도 행을 먼저 자른다. 그 덩어리가 읽기의 최소 단위라서, 인덱스를 걸어도 범위 조회에는 쓰이지 않는다. 열 저장의 진짜 인덱스는 정렬 키 하나뿐이다.
행 기반과 열 기반, 모니터링은 왜 열 기반인가
인덱스를 걸면 똑같지 않나 — 그 직관이 어디서 틀리는지 Postgres 와 DuckDB 로 300만 행을 돌려 확인했다. 인덱스는 탐색을 해결하지 읽는 양을 줄이지 않고, 열 저장의 이점은 대부분 값의 카디널리티에서 나온다.
섹션3 실습: 페이지 안을 직접 열어보기
pageinspect 로 라인 포인터 배열을 열어 ctid 의 두 번째 숫자가 행 번호가 아님을 확인하고, VACUUM 이 슬롯을 남기는 기준이 인덱스 하나임을 검증한다.
섹션3: 데이터베이스 내부
PostgreSQL 에는 클러스터 인덱스가 없어서 PK 인덱스조차 보조 인덱스다. 그리고 VACUUM 이 지운 데이터는 되살리는 게 아니라, 복구 목표를 그 이전으로 잡아 애초에 지워지지 않게 한다.
섹션2 실습 2: Django 에서 같은 문제 겪어보기
transaction.atomic 은 격리 수준을 바꾸지 않는다. QuerySet 캐시는 어디까지 유효한가. ORM 이 lost update 를 유도하는 지점과 F(), select_for_update 를 실제로 돌려서 확인한다.
섹션2 실습 1: ACID 직접 까보기
A·C·I·D 를 차례로 눈으로 확인한다. t_xmin / t_xmax 를 직접 찍어 보면 네 글자가 각각 어떤 장치로 만들어지는지, 그리고 C 만 왜 다른지가 드러난다.
섹션2: ACID
ACID 네 글자를 DB가 실제로 어떤 장치로 구현하는지. MySQL InnoDB의 Undo Log와 PostgreSQL의 Append-Only는 원자성과 격리성을 정반대 방식으로 만든다.
블로그를 열며
공부한 것과 Claude와 나눈 대화를 흘려보내지 않고 남겨두기 위해 만든 공간.