앱 화면 아래에 빨간 토스트가 계속 떠 있었습니다. 혈당 목록을 읽는 리스너가 조용히 실패하고 있었던 건데, 에러 전문을 열어보니 복합 색인이 없다는 이야기였습니다.
그래서 Firestore 인덱스 정의를 파일에 추가하고 배포한 뒤, 앱을 완전히 껐다 켜서 그 화면으로 다시 들어갔습니다. 에러가 그대로였습니다.
배포는 성공했다고 말했습니다
Firestore 인덱스 배포 명령의 출력은 이랬습니다.
i firestore: deploying indexes...
✔ firestore: deployed indexes in firestore.indexes.json successfully
✔ Deploy complete!
혹시 몰라 정의도 따로 확인해봤습니다.
npx firebase-tools firestore:indexes --project <프로젝트>
목록에 제가 넣은 것이 필드도 순서도 맞게 들어 있었습니다. 그런데도 화면은 그대로였고, 이쯤 되니 색인이 원인이 아닌 것 같다는 생각이 들기 시작했습니다.
그래서 보안 규칙을 의심해보고, 쿼리 필드 이름을 다시 읽어보고, 앱이 옛 번들을 물고 있는 건 아닌지도 확인했습니다. 전부 아니었습니다.
Firestore 인덱스는 목록에 떠도 아직 못 쓸 수 있습니다
firestore:indexes 가 보여주는 건 정의일 뿐이라, 그게 지금 쿼리를 받을 수 있는 상태인지까지는 알려주지 않습니다.
상태를 알려면 Admin REST 를 직접 불러야 했습니다.
GET https://firestore.googleapis.com/v1/projects/<pid>/databases/(default)/collectionGroups/<컬렉션>/indexes
여기서는 항목마다 state 가 같이 나오는데, 제 것은 CREATING 이었습니다.
조금 기다렸다가 다시 읽어보니 READY 로 바뀌어 있었고, 그 상태에서 앱을 켜자 토스트가 사라지면서 같은 시간대 로그에서 그 에러가 한 건도 안 잡혔습니다.
결국 배포는 처음부터 정상이었고, 저는 빌드가 끝나기도 전에 결과를 보러 간 것뿐이었습니다.
만드는 중과 없음이 같은 문구를 냅니다
이게 헷갈리는 이유는 단순합니다. 색인이 만들어지는 동안 쿼리가 내놓는 에러가, 색인이 아예 없을 때와 글자 하나 다르지 않기 때문입니다.
[firestore/failed-precondition] The query requires an index.
You can create it here: https://console.firebase.google.com/...
“만드는 중입니다” 가 아니라 “만들어야 합니다” 라고 말하니, 화면만 봐서는 배포가 반영이 안 된 것과 구분할 방법이 없습니다. 게다가 콘솔 링크까지 친절하게 붙어 있어서 방금 만들고 온 사람에게는 더더욱 안 만들어졌다는 뜻으로 읽힙니다.
저도 딱 그렇게 읽었습니다.
비어 있는 컬렉션인데 8분이 걸렸습니다
제 경우는 문서가 사실상 하나도 없는 컬렉션이었는데도 CREATING 에서 READY 까지 약 8분이 걸렸습니다.
물론 이 숫자는 제 프로젝트에서 그날 한 번 잰 값이고, 문서화된 보장 시간도 아닙니다. 데이터가 많으면 당연히 더 걸릴 테니 그대로 믿을 값은 아닙니다.
다만 “비었으니까 금방 되겠지” 라는 제 짐작이 틀렸다는 것만큼은 분명합니다. 빌드 시간이 무엇에 비례하는지는 저도 모르겠고, 문서가 0에 가까운데 8분이 걸린 이유도 설명하지 못하겠습니다.
확인하는 순서를 바꿨습니다
전에는 배포하고 나서 앱을 켜서 눈으로 확인했는데, 지금은 배포한 뒤에 state 부터 읽습니다.
READY 를 본 다음에 앱을 켜야, 화면에 남아 있는 에러가 색인 이야기가 아니라는 게 확실해지기 때문입니다.
폴링이라고 해봐야 서비스 계정으로 몇 줄이면 끝납니다.
const url = `https://firestore.googleapis.com/v1/projects/${pid}` +
`/databases/(default)/collectionGroups/${col}/indexes`;
const r = await fetch(url, {headers: {Authorization: `Bearer ${token}`}});
const j = await r.json();
console.log(j.indexes.map(i => i.state).join(','));
파일에 없는 색인은 함부로 지우지 마세요
같이 알아두면 좋은 게 하나 더 있습니다. 파일에 없는 항목이 프로젝트에 남아 있으면 배포 명령이 이렇게 말해줍니다.
i firestore: there are 16 indexes defined in your project that are not
present in your firestore indexes file. To delete them, run this
command with the --force flag.
저는 --force 를 붙이지 않았습니다.
그 16개가 지금 어디서 쓰이는지 모르는 상태였으니까요.
쓰이고 있는 걸 지워버리면 그 쿼리가 방금 제가 본 것과 똑같은 에러를 내기 시작하고, 다시 만들어도 그 8분을 또 기다려야 합니다. 지우는 건 플래그 하나지만 되돌리는 건 그렇지 않습니다.
잘린 에러를 다 읽는 법
토스트는 문구를 잘라서 보여주기 때문에 전문은 시뮬레이터 로그에서 읽었습니다.
xcrun simctl spawn <device> log show --last 5m --style compact \
| grep -i "bloodSugar"
한 가지 주의할 게 있는데, iOS 네이티브 로그에 남는 건 console.error 뿐이고 console.warn 은 안 남습니다.
그래서 리스너 실패처럼 조용히 지나가는 것들은 처음부터 error 로 찍게 해두는 편이 낫습니다.
어디까지 확인한 것인가
2026년 8월 15일 기준으로 firebase-tools 15.19.0 과 네이티브 모드 프로젝트 하나에서 확인했습니다. 컬렉션 하나에 필드 두 개짜리 복합 색인이었고, 빌드에 약 8분이 걸렸습니다.
state 에 CREATING 과 READY 말고 어떤 값이 더 있는지는 이번에 안 봤고, 빌드가 실패하면 무엇이 오는지도 모릅니다.
그건 그럴 일이 생기면 그때 재보겠습니다.