DB 인덱스 준비를 서버 기동과 나누는 이유
목차
서버를 시작할 때 필요한 DB 인덱스를 모두 만들면 배포도 간단해 보인다. 하지만 데이터가 쌓이면 인덱스 준비 시간이 서버의 기동 예산을 넘길 수 있다. 이때는 오래 걸리는 준비 작업을 배포 전에 수행하고 서버 기동에서는 필요한 상태가 갖춰졌는지 확인하는 편이 책임을 나누기 쉽다.
최근 인덱스 준비 도구와 배포 선행 조건을 검토하며 정리한 판단이다. 아래 시간과 데이터 구조는 설명을 위해 새로 만든 예제다. 실제 시스템의 인덱스 목록이나 처리 시간을 옮긴 것이 아니다.
같은 호출을 다시 해도 된다는 것과 빨리 끝난다는 것
인덱스는 기존 문서를 특정 키 순서로 찾을 수 있도록 만드는 조회 구조다. 없는 인덱스를 처음 만들 때는 기존 데이터에서 그 구조를 준비해야 한다. 이미 완성된 같은 인덱스를 확인하는 경우와 비용이 다르다.
MongoDB 7.0은 인덱스 생성 중 메모리와 임시 디스크를 사용한다. 생성의 시작과 끝에는 collection을 잠깐 배타적으로 잠그고 그 사이에는 읽기·쓰기와 진행을 나눈다. 쓰기가 많은 환경에서는 서비스 쓰기와 생성 작업이 모두 느려질 수 있다. MongoDB 7.0 인덱스 생성
같은 정의로 생성 명령을 다시 보낼 수 있어도 첫 생성의 비용이 사라지지는 않는다. 새 환경, 복구한 DB, 누락된 인덱스가 있는 환경에서는 서버 기동이 그 비용을 처음 부담한다. 기동 테스트에 작은 빈 DB만 사용하면 이 차이를 놓치기 쉽다.
기동 예산에 무엇을 넣었는지 계산하기
가상의 서버가 인덱스 준비 뒤 HTTP를 연다고 하자. 새 인덱스 생성에는 360초, 설정·연결·정의 확인에는 2초가 걸린다고 가정한다. 기동 완료를 기다릴 예산은 60초다. 이 숫자는 성능 측정값이 아니라 계산용 입력이다.
같은 준비 비용을 기동 안팎에 놓으면 결과가 달라진다. 작은 계산 예제를 Python 3.14.5에서 실행했다. 프로그램은 시간을 실제로 기다리지 않으며 MongoDB나 Kubernetes를 실행하지 않는다.
required = check_seconds if prepared else build_seconds + check_seconds
ready = required < budget_secondsprepared는 인덱스 생성이 미리 끝났는지, required는 서버 기동에 들어가는 초, budget_seconds는 허용한 기동 시간이다. 예제는 마감 시각보다 먼저 끝나는 경우만 성공으로 계산한다.
| 조건 | 기동 전 준비 시간 | 서버 기동에 필요한 시간 | 60초 예산 안의 완료 |
|---|---|---|---|
| 360초 생성을 기동에 포함 | 0초 | 360 + 2 = 362초 | 불가 |
| 360초 생성을 사전에 완료 | 360초 | 2초 | 가능 |
| 생성이 20초인 작은 환경 | 0초 | 20 + 2 = 22초 | 가능 |
두 번째 행도 인덱스를 더 빨리 만들지는 않았다. 전체 준비 비용을 다른 단계로 옮겼다. 대신 서버를 다시 띄우는 작업이 그 긴 작업을 기다릴 필요가 줄었다. 세 번째 행은 개발 환경에서 같은 기동 방식이 문제없이 보일 수 있는 이유를 설명한다.
Kubernetes의 startup probe는 애플리케이션의 기동 성공을 확인한다. 성공 전에는 liveness와 readiness probe가 시작되지 않으며 정한 실패 기준을 넘으면 컨테이너를 재시작한다. readiness 실패만으로 컨테이너를 재시작하는 것은 아니다. 요청을 받을 준비와 실행을 다시 시작할 판단은 구분해야 한다. Kubernetes probe 동작
startup 예산을 늘리는 선택도 가능하다. 다만 DB 크기와 부하에 따라 생성 시간이 달라지면 길어진 예산도 다시 넘을 수 있다. 준비를 분리하는 이유는 생성 속도 향상보다 작업 시간과 실패 처리를 별도로 관리하는 데 있다.
준비 단계는 전체를 보고 나서 바꾼다
가상의 jobs collection에 owner_id로 작업을 찾는 인덱스가 필요하다고 하자. 준비 도구는 필수 정의를 현재 메타데이터와 비교한 뒤 다음 상태를 구분한다.
| 관찰한 상태 | 다음 행동 |
|---|---|
| 필요한 키와 옵션이 일치하는 완성된 인덱스 | 그대로 사용 |
| 필요한 인덱스가 없음 | 생성 후보로 기록 |
| 같은 이름이지만 정의가 다름 | 충돌로 보고하고 변경 중단 |
| 생성이 진행 중 | 진행 상태를 확인하고 중복 실행 판단 보류 |
이 표는 특정 제품 도구의 기본 동작이 아니라 준비 절차를 설계하는 예시다. 이름을 계약으로 삼을지, 같은 정의의 다른 이름도 허용할지는 애플리케이션이 이름으로 hint를 주는지 등을 보고 정해야 한다.
키가 같다는 이유만으로 같은 인덱스라고 판단할 수도 없다. unique는 중복 키를 제한하고, partialFilterExpression은 어떤 문서가 인덱스에 들어갈지 정한다. 정렬에 쓰는 복합 키는 순서도 의미가 있다. 필요한 옵션과 키 순서를 함께 비교한다. MongoDB 인덱스 속성, createIndexes 옵션
예를 들어 필수 인덱스가 세 개인데 마지막 하나의 정의가 충돌한다고 하자. 첫 둘을 만든 뒤 충돌을 발견하기보다 전체 상태를 먼저 점검하는 편이 변경 범위를 알기 쉽다. 그렇다고 사전 점검이 이후 상태를 고정하지는 않는다. 다른 관리 작업과 겹칠 수 있으므로 적용 직전과 완료 후에도 다시 확인해야 한다.
생성도 데이터 수명을 바꿀 수 있다
인덱스 추가를 언제나 읽기 성능만 바꾸는 작업으로 취급하면 TTL을 놓친다. MongoDB의 TTL 인덱스는 날짜 필드와 만료 시간에 따라 문서를 자동 삭제한다. 기존 문서가 이미 만료 조건에 해당하면 인덱스 생성 이후 삭제 대상이 된다. 삭제 시각은 즉시로 보장되지 않는다. MongoDB 7.0 TTL 인덱스
그래서 누락된 일반 인덱스 추가와 누락된 TTL 추가는 검토할 영향이 다르다. TTL에는 보존 기간과 삭제 대상 검토가 필요하다. unique 인덱스도 기존 중복 데이터에 걸리면 생성에 실패할 수 있으며 도구가 임의로 중복 문서를 지우도록 만들어서는 안 된다.
준비 작업을 하나씩 제출하면 동시에 새로 만드는 작업 수를 줄일 수 있다. 대신 같은 collection의 여러 인덱스를 따로 만들면 데이터 읽기를 반복할 수 있다. 순차 실행은 단일 작업의 CPU나 디스크 사용량을 제한하는 기능도 아니다. 실행 순서와 작업 크기는 서로 다른 부하 조절 수단이다.
준비 완료를 서버가 확인하는 경계
분리한 절차는 사전 준비, 메타데이터 재검사, 서버 기동의 확인 순서로 이어진다. 준비 단계가 실패하면 배포를 진행하지 않고 기동에서는 필요한 정의가 없거나 다르면 요청을 받기 전에 실패하도록 설계할 수 있다. 기동 확인을 모든 요청의 readiness 검사마다 반복할지는 별도 비용과 장애 동작을 보고 정한다.
이 분리는 준비 도구를 배포 전에 실행하고 실패를 전달하는 자동화가 있어야 유지된다. 실행을 사람의 기억에만 맡기면 새 환경에서 준비가 빠질 수 있다. 조회 전용 도구가 누락 목록을 출력하며 종료 코드 0을 돌려주는 계약이라면 배포는 종료 코드만 보지 말고 실제 준비 상태도 확인해야 한다.
마지막 확인은 서비스 쿼리다. 인덱스가 존재한다는 사실은 그 쿼리가 효율적으로 사용한다는 보장이 아니다. 실제 filter·sort와 실행 계획을 따로 대조한다. 서버 이미지의 롤백도 이미 만들어진 TTL이나 그로 인해 삭제된 문서를 되돌려 주지 않는다.
새 환경에서 같은 배포를 한다고 가정해 보자. DB 준비가 없으면 서버가 긴 생성을 시작하는지, 빠르게 누락을 알리는지, 준비 단계를 먼저 통과해야만 배포가 열리는지 확인하면 된다. 이 순서를 설명할 수 있어야 기동 성공과 DB 준비 완료를 각각 판단할 수 있다.