created_at 인덱스로 게시글 처리량을 높인 기록

게시글 최신순 조회에서 인덱스 전후의 실행 계획과 응답량을 비교하는 흐름

PopcornTalk 메인 화면은 게시글을 최신순으로 보여줬다. 데이터가 쌓이고 조회가 느려지자 게시글 생성 시각인 created_at에 인덱스를 추가해 차이를 확인했다. 원문에 남아 있는 결과는 분명했다. 게시글 10만 건을 넣은 테스트에서 초당 처리량이 33.2에서 259.1로 올랐다. 계산하면 약 7.8배다.

원문은 부하 조건을 “100명의 사용자가 1초에 10번 요청”으로 적고 있다. 다만 이 표현이 사용자마다 초당 10회였는지 전체 요청이 초당 10회였는지, 어떤 도구로 측정했는지는 기록되어 있지 않다. 쿼리 전문과 실행 계획도 함께 남아 있지 않아, 이 수치를 다른 DB나 테이블에 그대로 기대할 수는 없다. 당시 테스트가 보여주는 건 해당 데이터와 환경에서 created_at 인덱스가 효과를 냈다는 사실이다.

최신순 목록에서 created_at이 후보였던 배경

게시글 목록은 대개 ORDER BY created_at DESC로 최근 글부터 정렬한다. 인덱스가 없다면 DB는 조건에 맞는 행을 찾은 뒤 정렬하는 데 큰 비용을 쓸 수 있다. 날짜 컬럼의 B-tree 인덱스는 정렬된 키를 따라 최근 행부터 읽는 후보가 된다. 페이지 크기가 작고 최신 글을 읽는 요청이 반복되는 서비스라면 특히 테스트해 볼 만하다.

그렇다고 created_at이라는 이름만 보고 인덱스를 추가하면 안 된다. 실제 화면 쿼리가 작성자 필터와 조인을 포함하는지, LIMIT이 얼마인지, 오래된 글을 찾는 조건도 있는지에 따라 실행 계획이 달라진다. 예를 들어 다음은 설명을 위한 단순화된 목록 쿼리다.

SELECT p.id, p.title, p.created_at, u.nickname
FROM post p
JOIN user u ON u.id = p.user_id
ORDER BY p.created_at DESC, p.id DESC
LIMIT 20;

여기서 p.created_at 단일 인덱스와 (created_at, id) 조합은 후보일 뿐이다. 정렬 키가 같은 행의 순서를 고정하려고 id를 추가했지만, 그 복합 인덱스가 항상 더 좋은 것은 아니다. 옵티마이저가 실제로 선택하는지, 조인과 정렬 비용이 줄어드는지 확인해야 한다.

실행 계획과 부하 결과 비교

MySQL의 EXPLAIN은 테이블 접근 순서와 선택한 인덱스 등 옵티마이저가 예상한 실행 방식을 보여준다. 실제 실행 비용을 확인할 수 있는 환경이라면 EXPLAIN ANALYZE도 사용할 수 있다.

EXPLAIN ANALYZE
SELECT p.id, p.title, p.created_at, u.nickname
FROM post p
JOIN user u ON u.id = p.user_id
ORDER BY p.created_at DESC, p.id DESC
LIMIT 20;

인덱스 전후의 실행 계획을 비교할 때는 선택한 key만 보지 않는다. 읽은 행 수, 정렬 단계, 각 iterator의 실제 시간, 예상 행 수와 실제 행 수의 차이를 함께 본다. 개발 DB의 데이터가 너무 작으면 테이블을 전부 읽는 편이 더 싸다고 옵티마이저가 판단할 수도 있다. 운영과 비슷한 데이터 분포가 중요한 이유다.

부하 테스트도 조건을 고정해야 의미가 있다. 같은 SQL과 데이터, 같은 서버 자원에서 인덱스만 바꾸고, 캐시가 예열된 상태와 초기 상태를 구분한다. 처리량과 함께 p95 응답 시간과 오류율을 기록하면 평균이 가리는 느린 요청을 확인할 수 있다. 인덱스는 조회를 돕는 대신 INSERT·UPDATE·DELETE 때마다 함께 갱신되고 디스크도 사용하므로, 쓰기 비용까지 보고 채택해야 한다.

원문에 적힌 ID 인덱스 결과의 해석 범위

원문에는 게시글과 사용자 테이블을 userId로 조인하는 상황에서 ID 인덱스만으로는 원하는 결과가 나오지 않아 created_at 인덱스를 적용했다고 적혀 있다. 하지만 어떤 ID 컬럼에 어떤 인덱스가 있었는지, 전체 조건과 실행 계획이 남아 있지 않다. 따라서 “ID 인덱스는 쓸모없었다”라고 일반화하지 않고, 당시 문제 설명으로만 남긴다. 기본 키나 조인 키 인덱스는 해당 쿼리에서 여전히 중요한 후보일 수 있다.

이 실험의 가장 쓸모 있는 부분은 7.8배라는 숫자 자체보다, 최신순 게시글 목록이라는 반복 쿼리를 찾아 인덱스를 적용해 보고 처리량 변화를 관찰했다는 과정이다. 다음에 같은 실험을 반복한다면 쿼리 원문, EXPLAIN ANALYZE 결과, 데이터 건수, DB 버전, 동시 요청 수와 지연 시간 분포까지 함께 저장할 것이다. 그러면 개선 폭이 왜 나왔는지, 다른 데이터에서 재현되는지 설명할 수 있다.

반복 조회가 DB까지 매번 도달하는 경우에는 Redis 캐시를 적용한 성능 기록도 이어서 읽을 수 있다. 인덱스와 캐시는 같은 문제의 대체재가 아니라 서로 다른 병목을 다룬다.

참고 문서: MySQL 인덱스 최적화와 인덱스 사용 검증, MySQL EXPLAIN과 EXPLAIN ANALYZE

당시 기록: 게시글 조회 인덱스 적용