Skip to Content

닫는 글 — 다시, 읽는 양 앞에서

{/* 삽화 자리 — 첫 장의 그 창구 앞에 다시 선 사람. 이번엔 손에 페이지 몇 장을 세어 들고 있다 */}

시작은 말이 안 되는 장면이었습니다. 조건을 붙이면 45초, 빼면 즉시.


여는 글에서 이 모순을 놓고 시작했습니다.

SELECT * FROM request_log; -- 즉시 나온다 SELECT * FROM request_log WHERE transaction_no = ?; -- 45초

그리고 답은 허무할 만큼 단순했어요. 클라이언트가 붙인 LIMIT 500 때문에 한쪽은 500행만 읽고 끝났고, 다른 쪽은 조건에 맞는 500행을 못 채워 200만 행을 끝까지 읽었습니다.

조건이 있냐 없냐로 보면 말이 안 되고, 몇 행을 읽느냐로 보면 당연합니다. 렌즈가 틀렸던 거죠.

스무 장 동안 그 렌즈를 바꿔 끼웠습니다. 행에서 페이지로요.

이 책이 센 것

관통한 문장은 하나였습니다.

인덱스는 빠르게 읽는 기술이 아니라 덜 읽는 기술입니다. 그리고 그 “덜”의 단위는 행이 아니라 16KB 페이지예요.

그리고 계속 같은 걸 셌어요. 몇 페이지를 요청하는가.

무엇을 셌나
1부디스크 한 번 읽는 값이 비싸서 트리 모양이 정해졌다. 팬아웃 907 대 63(3장)
2부16KB 안에 무엇이 들어 있고, 왜 왕복이 생기는가. 행당 3.02회(7장)
3부옵티마이저가 무엇을 보고 인덱스를 버리는가. 4.4% 비용 차이가 실행계획을 뒤집는다(12장)
4부그 읽기를 위해 쓰기가 무는 값. 난수 키가 남긴 여유 공간 5,603바이트(14장)
5부처리량 31% 감소를 감수하고 넣고(17장), 시간 대신 페이지를 세고(19장), 인덱스 밖을 본다

같은 숫자가 다른 장에서 다시 나왔습니다

이 책을 쓰면서 제일 좋았던 순간들이 이겁니다. 다른 장에서 다른 스크립트로 쟀는데 같은 값이 나올 때요.

숫자어디서
44,571 페이지 요청여는 글의 풀스캔, 1장, 19장의 분산 측정
3.02 행당 왕복7장(왕복 몫만) 3.02, 16장(총 요청) 3.02 — 16장 값을 7장 정의로 환산하면 3.01
1,063 리프 페이지13장의 단조 증가 인덱스, 17장에서 난수 인덱스를 재구성한 뒤(1,062)
65.8% 채움률7장(엔트리 수로 어림 64%), 14장(바이트로 실측), 16장, 17장(66%로 반올림)
5,634 요청11장의 조건 붙은 인덱스 스캔, 19장

숫자가 맞아떨어질 때보다 틀렸을 때 배운 게 많았습니다. 그건 19장에 열두 건으로 정리해뒀어요.

아직 못 밝힌 것들

정직하게 남겨둡니다. 이 책이 재고도 설명하지 못한 것들입니다.

못 밝힌 것
1장JSON 행의 시간이 106ms·118ms·973ms로 8배까지 흔들린 이유
12장중복 없는 컬럼인데 인덱스 다이브 추정이 2.31배 부푼 이유
13장공간은 그대로 더해지는데 시간만 3.00 대 3.29로 갈린 이유
14장페이지당 요청 계수가 단조 1.34, 난수 1.79로 다른 이유
15장버퍼풀 1,024MiB에서 삽입이 20~50분 걸린 현상(물리 read는 오히려 적음)
17장처리량 31% 감소가 I/O 경합·redo 압박·MDL 중 어느 몫인지
20장15.2%를 지운 자리에서 OPTIMIZE가 2.8%만 돌려준 이유 (purge·반복 실행은 배제했고, 같은 행 수를 처음부터 넣으면 484.0MiB인데 재구성하면 552.0MiB)

(대표적인 것만 추렸습니다. 1장에는 100만 행 구간만 페이지당 시간이 20% 높게 나온 것도, 2장에는 루트가 두 개로 보인 것도 원인 미상으로 남아 있어요.)

그리고 제일 큰 빈칸이 하나 있습니다.

운영 수치가 여전히 비어 있습니다. 이 책의 실측은 전부 노트북의 Docker 컨테이너에서 나왔어요. 운영 값은 여는 글의 45.36초와 41.17초뿐이고, 둘 다 개선 전 수치입니다.

19장의 여섯 단계를 운영에서 밟는 것 — 이 책의 다음 판은 거기서 시작합니다.

가져갈 것 하나만 고른다면

스무 장에서 딱 하나를 남긴다면 이겁니다.

느리다고 느껴질 때, 시간을 재기 전에 읽는 양을 세보세요.

시간은 21~81% 흔들립니다(19장). 캐시가 데워졌는지, 옆에서 뭐가 도는지, 기계가 무엇인지에 따라 달라져요. 그래서 시간만 보면 아무것도 안 바꾸고 1.9배를 만들어낼 수 있습니다.

페이지 요청은 데워진 상태라면 안 흔들립니다. 스무 번을 재도 같은 값이에요. (차가운 첫 실행은 예외고, 그것도 19장에 적어뒀습니다.)

그리고 그 숫자가 44,571에서 385로 줄었다면, 시간이 얼마로 나오든 읽는 양은 확실히 줄어든 겁니다.


여는 글에서 열어둔 숫자가 하나 있었습니다.

노트북에서 200만 행을 읽는 데 504밀리초, 운영에서 같은 성격의 스캔이 41.17초. 82배였죠. 그중 18배가 초당 읽기 속도 차이(988MB/s 대 54MB/s)였고, 순차 읽기치고 54MB/s는 낮다고 했습니다.

8장이 후보를 줬습니다 — 논리적으로 순차인 스캔이 물리적으로는 랜덤에 가까울 수 있다고요. 확정은 아닙니다. 운영 DB를 계측할 수 없어 무엇이 지배적이었는지는 여전히 못 가렸어요. 다만 15장에서 랜덤 읽기 한 번을 40µs로 쟀고, 그 대역이면 54MB/s가 설명됩니다.

같은 “페이지 하나”가 어떤 때는 공짜에 가깝고 어떤 때는 밀리초를 잡아먹습니다.

그러니 마지막으로 남길 문장은 이겁니다. 인덱스는 그 페이지를 빨리 읽게 해주지 않습니다. 안 읽어도 되게 만들 뿐이에요.

읽지 않은 페이지는 언제나 공짜니까요.


여기까지 읽어주셔서 고맙습니다.

여기서 MVCC나 락이 궁금해지셨다면 『약속된 데이터』가, @Async와 스레드풀이 궁금해지셨다면 『잠드는 스레드』가 이어집니다.

처음으로 — 목차

Last updated on