[실전 서버리스 08] rows read가 곧 돈이다, 5천 행 스캔 사고

D1을 쓰면서 가장 크게 데인 게 바로 이 주제다. 어느 날 데이터베이스 읽기량이 폭증해 한도에 부딪혔는데, 원인을 파보니 인덱스 없이 큰 테이블을 통째로 스캔하는 쿼리가 있었다. D1에서는 읽은 행 수, 즉 rows read가 곧 비용이자 성능이다. 이번 편은 이 개념과 내가 겪은 실전 사고를 다룬다.


이건 서버리스 데이터베이스 초보가 거의 다 밟는 지뢰다. 개념을 정확히 알면 비용 폭탄과 성능 저하를 동시에 피할 수 있다.

1. rows read란 무엇인가

[실전 서버리스 08] rows read가 곧 돈이다, 5천 행 스캔 사고 (2)

<Limit 5M을 초과해서 39M을 사용>


D1에서 rows read는 쿼리가 읽은, 정확히는 스캔한 행의 수다. 여기서 핵심은 결과로 돌려준 행이 아니라 스캔한 행이라는 점이다. 조건에 맞는 행 하나를 찾으려고 테이블 전체를 훑었다면, 돌려준 건 한 행이라도 스캔은 전체 행 수만큼 된 것이다.


예를 들어 5천 개의 행이 있는 테이블에서 조건 없이 전체를 조회하면 5천 행을 읽는다. 조건을 걸어 하나만 찾더라도, 인덱스가 없으면 데이터베이스는 전체를 하나씩 훑어 조건에 맞는지 확인한다. 이 경우에도 5천 행을 스캔한 것이다. 결과는 하나인데 비용은 5천이다.


D1의 과금과 성능이 다 이 rows read에 달렸다. 읽은 행 수만큼 과금되고, 많이 스캔할수록 느리다. 그래서 rows read를 줄이는 게 D1 최적화의 핵심이다. 결과를 몇 개 돌려주느냐가 아니라, 그걸 찾으려고 몇 행을 훑었느냐가 중요하다.


rows read가 결과 행이 아니라 스캔 행이라는 이 구분이 핵심 중의 핵심이다. 많은 초보가 결과가 하나뿐이니 비용도 적겠지 착각하다 폭탄을 맞는다. 데이터베이스가 그 하나를 찾으려고 몇 행을 훑었는지가 진짜 비용이다. 이 관점의 전환이 D1 최적화의 출발점이다.


조금 더 배경을 주면, 이 rows read 개념은 D1만이 아니라 데이터베이스 성능 전반을 이해하는 열쇠다. 어느 데이터베이스든 전체 스캔은 비싸고 인덱스 접근은 싸다. D1은 그걸 과금으로 명확히 보여줄 뿐이다. 그래서 D1에서 배운 이 감각은 다른 데이터베이스를 다룰 때도 그대로 통한다.


한 가지 더, rows read를 줄이는 건 비용만이 아니라 사용자 경험에도 이롭다. 적게 읽는 쿼리는 빠르게 응답하니, 페이지가 그만큼 빨리 뜬다. 앞서 다룬 페이지 속도와 직결되는 것이다. 즉 데이터베이스 최적화는 돈을 아끼는 동시에 사이트를 빠르게 만들어 SEO에도 기여한다. 한 작업이 여러 이득을 낳는다.

2. 내가 겪은 폭증 사고

어느 날 데이터베이스 읽기량이 평소의 몇 배로 튀어 한도에 가까워졌다. 놀라서 원인을 추적했다. 클라우드플레어가 제공하는 쿼리 분석 도구로 어떤 쿼리가 얼마나 읽는지를 봤더니, 특정 목록 조회가 호출 한 번에 수천 행을 읽고 있었다.


문제의 쿼리는 특정 조건으로 글을 거르는 것이었는데, 그 조건에 인덱스가 없었다. 그래서 매 호출마다 데이터베이스가 전체 글을 훑어 조건에 맞는 걸 골라냈다. 글이 수천 개니 호출마다 수천 행을 스캔한 것이다. 그리고 이 조회는 하루에 수만 번 호출됐다. 곱하면 어마어마한 읽기량이었다.


더 나빴던 건 봇 트래픽이었다. 봇들이 이 목록을 끊임없이 긁어대니, 비효율적인 쿼리가 수만 번씩 실행됐다. 사람 사용자보다 봇이 이 쿼리를 더 많이 호출하고 있었다. 비효율적인 쿼리 하나가 봇 트래픽과 만나 폭탄이 된 것이다.


내가 겪은 폭증 사고가 특히 뼈아팠던 건 봇 트래픽과 겹쳐서였다. 비효율적인 쿼리 하나가 사람만 호출하면 티가 안 나지만, 봇이 수만 번 긁으면 그게 증폭돼 폭탄이 된다. 그래서 비용에 민감한 쿼리는 특히 봇이 자주 부르는 목록 조회 쪽을 우선 점검해야 한다. 트래픽과 비효율이 만나는 지점이 위험하다.

3. 인덱스가 해결책

해결책은 인덱스였다. 인덱스는 특정 컬럼에 대해 미리 정렬된 찾아보기 표를 만들어두는 것이다. 책 뒤의 색인처럼, 전체를 훑지 않고 바로 원하는 곳을 찾게 해준다. 조건에 맞는 인덱스가 있으면 데이터베이스는 전체 스캔 대신 인덱스로 바로 접근한다.


문제의 쿼리에 맞는 인덱스를 만들었더니, 호출당 읽는 행이 수천에서 수백으로, 90퍼센트 넘게 줄었다. 같은 쿼리가 훨씬 적은 행만 읽고 같은 결과를 냈다. 인덱스 하나로 비용과 성능이 동시에 극적으로 개선된 것이다. 인덱스가 곧 비용 절감이라는 걸 몸으로 배웠다.


인덱스가 실제로 쓰이는지는 확인해봐야 한다. 인덱스를 만들었다고 데이터베이스가 항상 쓰는 건 아니다. 쿼리 실행 계획을 조회하면 그 쿼리가 인덱스를 쓰는지 전체 스캔을 하는지 나온다. 인덱스를 만든 뒤엔 이 실행 계획으로 실제 사용을 확인하는 게 중요하다.


인덱스가 실제로 쓰이는지 실행 계획으로 확인하라는 게 중요한 실무 조언이다. 인덱스를 만들었다고 데이터베이스가 항상 쓰는 게 아니라서, 만들고 방심하면 여전히 전체 스캔을 하고 있을 수 있다. 실행 계획을 조회해 인덱스를 쓴다고 확인해야 비로소 최적화가 완료된 것이다. 만들었다와 쓰인다는 다르다.


인덱스가 해결책이라는 걸 알아도, 어디에 만들지를 정하려면 쿼리를 알아야 한다. 그래서 최적화는 늘 내 사이트가 실제로 어떤 쿼리를 얼마나 자주 날리는지 파악하는 데서 시작한다. 자주 실행되고 많이 읽는 쿼리를 찾아 거기에 맞는 인덱스를 만드는 것이다. 쿼리 패턴을 모르면 인덱스도 감으로 만들게 된다.

4. 인덱스도 공짜는 아니다

인덱스가 만능은 아니다. 인덱스도 대가가 있다. 인덱스를 만들면 그만큼 저장 공간을 더 쓰고, 데이터를 쓸 때마다 인덱스도 갱신해야 해서 쓰기가 조금 느려진다. 그래서 아무 컬럼에나 인덱스를 다 걸면 오히려 낭비다.


인덱스는 실제로 자주 조회 조건에 쓰이는 컬럼에 선별해 만들어야 한다. 어떤 쿼리가 자주 실행되고 무엇으로 거르는지를 보고, 거기에 맞는 인덱스를 만든다. 읽기가 많은 컬럼에는 인덱스로 이득을 보고, 쓰기만 하고 조회 조건에 안 쓰는 컬럼은 인덱스를 안 만든다. 이 판단이 최적화의 요령이다.


또 조건이 여러 컬럼에 걸치면 복합 인덱스를 쓴다. 여러 컬럼을 조합한 조건에 맞는 인덱스를 만들면, 그 조합 조회가 빨라진다. 다만 복합 인덱스는 컬럼 순서가 중요해서, 실제 쿼리 패턴에 맞게 순서를 정해야 효과가 난다. 이 세밀한 조정이 대규모 사이트의 데이터베이스 성능을 좌우한다.


인덱스도 대가가 있다는 걸 아는 게 균형 잡힌 최적화의 조건이다. 모든 컬럼에 인덱스를 걸면 쓰기가 느려지고 저장 공간이 낭비된다. 그래서 자주 조회 조건에 쓰이는 컬럼만 선별해 인덱스를 만든다. 읽기 이득과 쓰기 비용을 저울질하는 이 판단이, 무작정 인덱스를 남발하는 것과 제대로 된 최적화를 가른다.

5. 측정하고 최적화한다

이 사고에서 배운 가장 큰 교훈은 측정의 중요성이다. 어떤 쿼리가 얼마나 읽는지 모르면 최적화할 수 없다. 클라우드플레어의 쿼리 분석 도구로 각 쿼리의 읽기량을 보고, 가장 많이 읽는 쿼리부터 손보는 게 순서다. 감이 아니라 데이터로 최적화하는 것이다.


이건 앞서 SEO 시리즈에서 반복한 감이 아니라 데이터라는 원칙과 정확히 같다. 데이터베이스 최적화도 계기판을 보며 하는 일이다. 어느 쿼리가 병목인지 측정하고, 인덱스로 고치고, 다시 측정해 효과를 확인한다. 이 사이클이 D1 비용을 관리하는 기본이다.


정리하면 D1에서는 스캔한 행 수인 rows read가 곧 비용이자 성능이고, 인덱스로 이걸 극적으로 줄일 수 있으며, 측정을 통해 최적화한다. 다음 편에서는 D1의 또 다른 중요한 특성, 단일 스레드라는 점과 그래서 대량 작업을 배치로 쪼개야 하는 이유를 다룬다.


측정하고 최적화한다는 결론이 이 편의 핵심이자 데이터베이스 관리의 정석이다. 감으로 이 쿼리가 느릴 것 같다 짐작하지 말고, 분석 도구로 실제 읽기량을 재서 가장 무거운 것부터 고친다. 고친 뒤엔 다시 재서 효과를 확인한다. 이 측정과 개선의 사이클이 비용과 성능을 동시에 잡는 길이다.


측정하고 최적화한다는 이 사이클을 몸에 익히면, 데이터베이스 비용이 무섭지 않다. 폭증이 와도 분석 도구로 원인 쿼리를 찾아 인덱스로 고치면 되기 때문이다. 반대로 이 측정 습관이 없으면, 어느 날 청구서를 보고 놀라기만 할 뿐 왜 그런지도 모른다. 계기판을 읽을 줄 아는 게 관리의 시작이다.


덧붙이면, 이 5천 행 스캔 사고는 내 서버리스 여정에서 가장 값진 수업이었다. 비용 폭탄을 맞고서야 rows read의 의미를 몸으로 알았고, 그 뒤로는 쿼리를 짤 때마다 이건 몇 행을 읽을까를 먼저 생각하게 됐다. 아프게 배운 교훈이 가장 오래 남는다. 이 편의 이야기가 누군가는 그 아픔 없이 배우는 계기가 되면 좋겠다.