잘 돌던 앱이 배포 한 번에? Firestore 인덱스 삭제 사고

멀쩡히 돌던 복합 쿼리가 배포 한 번에 failed-precondition으로 깨지는 이유와, Firestore 인덱스를 코드로 관리해 재발을 막는 방법을 사고 서사로 풀어봤어요.

창가 자연광 아래 개발자가 노트북에 아무 목록도 안 뜬 것을 발견하고 골똘히 살펴보는 장면 (대표 히어로, ChatGPT)

배포 한 번에 어제까지 멀쩡하던 화면이 텅 비어버린 적 있으세요? 코드는 그대로고 로그도 조용한데 목록만 안 나오면 답답하죠. 범인은 “콘솔에서 손으로 만든 인덱스”일 수 있어요.

  1. 배포가 인덱스를 “무조건” 지우진 않지만, 파일에 없고 서버에만 있는 인덱스는 삭제 후보로 감지돼요.
  2. 프롬프트에 동의하거나 --force·비대화형 CI에서 실행되면 그 인덱스가 사라지고, 복합 쿼리가 failed-precondition으로 깨져요.
  3. 그래서 복합 인덱스는 콘솔 수동 생성 대신 firestore.indexes.json으로 코드 관리하는 게 재발 방지 핵심이에요.
왼쪽 정상 목록 / 'deploy' 화살표 / 오른쪽 빈 화면의 before-after 평면 대비도 (코드렌더)

멀쩡하던 쿼리가 왜 갑자기 failed-precondition으로 깨졌을까요?

증상은 이렇게 시작돼요. 정렬 랭킹, 채널 통계, 홈 피드처럼 조건이 여러 개 붙는 복합 쿼리가 어느 순간 실패해요. Firestore는 기존 인덱스로 지원되지 않는 쿼리를 만나면 FAILED_PRECONDITION: The query requires an index. 형태의 오류와 링크를 돌려줘요.

콘솔/터미널 스타일 카드에 'FAILED_PRECONDITION: The query requires an index. You can create it here: [Console Link]' 오류 메시지를 코드 스니펫으로 렌더

더 골치 아픈 건 오류가 화면에 안 보이는 경우예요. 데이터를 불러오는 코드에 catch가 걸려 있으면 오류를 조용히 삼켜 빈 리스트를 돌려주기도 해요. 그러면 앱은 크래시도 에러 팝업도 없이 “빈 화면”만 보여줘요. 오류를 본 적이 없으니 어디가 고장 났는지조차 모르는, 관측이 가장 어려운 침묵 실패죠. 그래서 던질 질문은 “왜 잘 돌던 게 사라졌나”예요.

원인 — 배포가 “파일에 없는 인덱스”를 삭제 후보로 감지해요

사고의 뿌리는 드리프트예요. 개발 중에 복합 인덱스를 급하게 콘솔에서 손으로 만들면, 서버에는 있지만 저장소의 firestore.indexes.json 파일에는 그 정의가 빠진 상태로 남게 돼요. 잘 돌아가니까 아무도 문제로 느끼지 못하는 게 함정이죠.

'콘솔 수동 생성 → 라이브엔 있음, 파일엔 없음(드리프트) → deploy 시 '파일에 없는 서버 인덱스' 삭제 후보 감지 → 동의/force면 삭제' 4단계 좌우 흐름도

그러다 firebase deploy --only firestore:indexes를 실행하는 순간이 방아쇠예요. 이 명령은 파일에 정의된 인덱스를 만들 뿐 아니라, 서버에는 있지만 파일에 없는 인덱스를 삭제 대상으로 처리해요. 실행하면 그런 인덱스가 있다는 안내와 함께 삭제 여부를 물어봐요.

여기서 오해를 짚어야 해요. 배포가 라이브 인덱스를 “무조건” 지우는 건 아니에요. 프롬프트에서 “Yes”를 고르면 삭제가 진행되고, “No”면 삭제 없이 나머지 배포만 계속돼요. 즉 “지운다”는 건 파일에 없는 서버 인덱스를 삭제 후보로 감지한 뒤 사람이 동의하거나 확인을 건너뛸 때 벌어지는 조건부 동작이에요. 이 삭제 기능은 2020년 6월 4일 도입됐고, 그 전에는 삭제 없는 append-only였어요.

복원 — 삭제된 인덱스를 코드로 되살리는 법

이미 사라졌다면 복구는 단순해요. 삭제된 인덱스를 firestore.indexes.json에 코드로 다시 정의하고 재배포하면 라이브 인덱스가 복원돼요. 이어서 문제됐던 쿼리를 재실행해 에러 0을 확인하면 되죠.

'firestore.indexes.json에 인덱스 정의 추가 → firebase deploy → 라이브 인덱스 복원 → 쿼리 재현으로 에러 0 확인' 4단계 복원 흐름 카드

이때 알아둘 명령이 있어요. firebase firestore:indexes는 라이브 인덱스를 JSON 스펙으로 출력해줘요. 콘솔에서 여러 개 만들어 뒀다면 이걸로 뽑아 파일로 코드화하면 드리프트를 한 번에 정리할 수 있죠. 복합 인덱스는 다중 where나 where+orderBy(다른 필드), 범위 필터와 등가 조건을 함께 쓰는 쿼리처럼 조건이 겹칠 때 필요해요. 그래서 이런 쿼리를 많이 쓰는 화면일수록 인덱스 하나가 사라졌을 때 한꺼번에 깨지기 쉬워요. 어떤 화면이 어떤 복합 인덱스에 기대고 있는지 미리 파일로 정리해 두면, 배포 전에 무엇이 위험한지 눈으로 확인할 수 있죠.

문제를 해결한 뒤 개발자가 따뜻한 커피잔을 들고 안도하며 미소 짓는 장면 (ChatGPT)

재발 방지 — 콘솔 수동 인덱스를 잠복 지뢰로 남기지 않으려면

가장 위험한 환경은 CI 자동 배포예요. CI 파이프라인은 대화형 프롬프트에 응답할 수 없어서, 삭제를 묻는 질문이 떠도 “No”를 누를 사람이 없어요. 그래서 사실상 --force와 동등하게 동작해 확인 없이 파일에 없는 인덱스를 조용히 지울 수 있죠. 로컬 배포의 프롬프트라는 방어막이 자동 배포엔 없는 셈이에요.

왼쪽 '로컬 배포: 프롬프트로 No 선택 가능', 오른쪽 'CI 배포: 프롬프트 응답 불가 → force와 동등, 조용히 삭제' 두 갈래 비교 다이어그램

그래서 세 가지를 습관으로 만들면 좋아요. 첫째, 복합 인덱스는 콘솔에서 손으로 만들지 말고 firestore.indexes.json을 거쳐서만 생성·변경·배포하세요. 콘솔에서 편집했다면 로컬 파일도 같이 갱신하고요. 둘째, 배포 전에 삭제 예정 인덱스 목록을 먼저 확인하고, 승인 없이는 삭제로 넘어가지 않게 게이트를 두세요.

'①콘솔 수동 금지, 파일 경유만 ②배포 전 삭제 예정 인덱스 확인·승인 ③failed-precondition을 catch로 삼키지 말고 로그·알림' 3항목 체크리스트 카드

셋째, 침묵 실패를 막는 관측을 심어두세요. failed-precondition 오류를 catch에서 빈 리스트로 삼키지 말고 로그나 알림으로 노출하면, 같은 일이 또 생겨도 곧바로 원인을 잡을 수 있어요. 콘솔 수동 인덱스는 지금 잘 돌아도 언젠가 터질 잠복 지뢰라는 감각, 이게 이 사고의 교훈이죠.

FAQ

Q. firebase deploy firestore:indexes가 콘솔에서 만든 인덱스를 삭제하나요? A. 무조건은 아니에요. firestore.indexes.json에 없고 서버에만 있는 인덱스를 삭제 후보로 감지한 뒤 삭제 여부를 물어봐요. “Yes”면 삭제되고 “No”면 나머지 배포만 이어져요. 다만 --force나 비대화형 CI에서는 프롬프트를 건너뛰고 확인 없이 자동 삭제될 수 있어요.

Q. 라이브 인덱스를 firestore.indexes.json으로 어떻게 내보내나요? A. firebase firestore:indexes 명령을 쓰면 라이브 인덱스를 JSON 스펙으로 출력해줘요. 콘솔에서 이미 만든 인덱스가 있다면 이걸로 뽑아 파일로 코드화해 드리프트를 정리할 수 있어요.

터미널 카드에 'firebase firestore:indexes' 명령과 그 아래 라이브 인덱스가 JSON 스펙으로 출력되는 예시를 코드 스니펫으로 렌더

Q. CI 자동 배포에서 인덱스가 사라지는 걸 어떻게 막나요? A. CI는 대화형 프롬프트에 응답할 수 없어 사실상 --force처럼 동작할 수 있어요. 그래서 복합 인덱스를 파일 경유로만 관리해 드리프트를 없애고, 배포 전에 삭제 예정 목록을 먼저 확인·승인하는 게이트를 두세요. 파일과 라이브가 일치하면 삭제 후보 자체가 생기지 않아요.

이 글은 AI 업무 자동화 허브의 ‘배포 사고·재발 방지’ 관련 글이다.

참고 자료

기준 시점 2026년 7월 20일 확인. 익명화한 실제 운영 사고를 바탕으로 정리했고, 사실과 출처는 사람이 검수했어요. Firebase CLI·Firestore 동작은 버전·환경에 따라 달라질 수 있으니, 배포 전 본인 환경에서 삭제 예정 인덱스를 먼저 확인하세요.

← 글 목록