이벤트 폭풍과 신뢰도 0점 탈출기: Firestore 컬렉션 그룹 쿼리 버그와 멱등성 락 설계
Firestore Collection Group 쿼리에서 발생하는 FieldPath.documentId() 오류와 이벤트 루프의 연쇄 폭풍을 해결하려면, 유효한 타임스탬프 기반 인덱싱과 이벤트 지문(Fingerprint) 기반의 멱등성 캐시를 결합해야 합니다. 본 글에서는 이벤트 루프 장애를 핫픽스하고 어드민 UI의 접근성을 개선한 실제 엔지니어링 기록을 다룹니다.

Firestore Collection Group 쿼리에서 발생하는 FieldPath.documentId() 정렬 오류와 이벤트 루프 폭풍은 이벤트 지문(Fingerprint) 기반의 멱등성 락(Deduplication Lock)과 쿼리 타임스탬프 인덱싱을 통해 완전히 제어할 수 있습니다. 시스템 자율 감지 엔진이 비정상적으로 31건의 안건을 쏟아내며 신뢰도(system_reliability) 지표가 0점으로 추락했을 때, 문제의 핵심은 잘못된 정렬 기준과 중복 이벤트 방지 필터의 부재에 있었습니다.
1. 자율 감지 엔진의 장애: 31건의 알림 폭풍과 신뢰도 0점
어느 날 아침, Agent 8 운영 대시보드는 붉은색 경고로 가득 찼습니다. 시스템 상태를 자율 감시하는 하네스에서 10건의 긴급 이슈(P0)를 포함해 총 31건의 안건이 동시다발적으로 생성되었기 때문입니다. 이로 인해 시스템 신뢰도 점수는 순식간에 0/100으로 곤두박질쳤습니다.
로그를 추적한 결과, 실제 발생한 물리적 장애는 npm 보안 감사 취약점 1건과 블로그 드래프트 방치 건뿐이었습니다. 그러나 단일 취약점 감지 이벤트가 이벤트 루프를 재귀적으로 통과하면서 매 실행 주기마다 중복 복제되었고, 감시 엔진은 이를 별개의 시스템 장애로 오인하여 경보를 기하급수적으로 증폭시켰습니다.
2. 기술 심층 분석: Firestore Collection Group의 치명적 함정
근본적인 원인은 백엔드 이벤트 수집 계층인 functions/dt/services/agent-event-loop.ts에 있었습니다. 분산된 서브컬렉션 전반에서 시스템 이벤트를 취합하기 위해 Collection Group 쿼리를 사용했으나, 정렬 필드로 FieldPath.documentId()를 지정한 것이 치명적이었습니다.
Firebase Error: When querying a collection group and ordering by FieldPath.documentId(), the corresponding value must be a valid document path, not just an ID.
단일 컬렉션과 달리, Collection Group은 서로 다른 경로 트리에 위치한 동일 이름의 컬렉션들을 묶어 조회합니다. 따라서 상대적인 문서 ID만으로는 일관된 정렬 순서를 보장할 수 없으며, 유효한 전체 문서 경로를 명시하지 않는 한 런타임 예외를 발생시킵니다. 이 예외로 인해 쿼리가 실패하면서 완료 플래그가 기록되지 못했고, 이벤트 루프는 미처리된 동일 이벤트를 무한 재시도하는 '좀비 루프'에 빠지게 되었습니다.
3. 해결책: 타임스탬프 정렬 핫픽스와 24시간 멱등성 락
문제를 종식시키기 위해 2단계 아키텍처 수정을 단행했습니다. 첫째, 런타임 에러를 유발하는 문서 ID 정렬을 역순 타임스탬프(timestamp: desc) 및 제한(limit(50)) 구조로 즉각 변경했습니다. 둘째, 이벤트 타입, 심각도, 페이로드 해시를 결합한 이벤트 지문(Fingerprint) 기반의 멱등성 캐시를 도입했습니다.
// functions/dt/services/agent-event-loop.ts 수정 코드 발췌
const query = db.collectionGroup('system-events')
.orderBy('timestamp', 'desc')
.limit(50);
const deduplicationLock = new Set<string>();
const snapshot = await query.get();
for (const doc of snapshot.docs) {
const event = doc.data();
const eventKey = `${event.type}_${event.severity}_${event.fingerprint || ''}`;
// 24시간 이내 동일 지문을 가진 이벤트는 전파하지 않고 스킵
if (deduplicationLock.has(eventKey)) {
continue;
}
deduplicationLock.add(eventKey);
await processEvent(event);
}이 변경을 통해 반복적인 취약점 스캔 결과가 단일 이벤트로 병합(Consolidation)되었으며, 로컬 단위 테스트 스위트를 통해 회귀 검증을 완벽히 마쳤습니다.
4. 프론트엔드 정화: AI-Slop 디자인 척결 및 접근성 개선
백엔드 이벤트 정규화와 더불어 관리자 UI 리팩토링도 병행되었습니다. 기존 드래프트 검토 화면은 원색 뱃지와 중첩 테두리가 남발된 이른바 'AI 양산형(AI-slop)' 디자인으로, 가독성과 접근성이 현저히 떨어져 있었습니다.
- 명도 대비 미달 해결: 상태 뱃지의 텍스트 대비율이 2.8:1에 불과했던 문제를 Tailwind CSS 시맨틱 토큰(
hsl()) 기반 4.5:1 이상으로 끌어올려 WCAG AA 규격을 충족했습니다. - 터치 타겟 확장: 모바일 오작동을 유발하던 32px 높이의 인라인 승인 버튼을 최소 48px 이상의 인터랙티브 영역을 보장하도록 구조를 재배치했습니다.
- 시각적 소음 제거: 카드형 래퍼를 걷어내고 정돈된 리스트 행(Row) 기반 레이아웃으로 변경하여 한 번에 스캔할 수 있는 정보 밀도를 극대화했습니다.
자주 묻는 질문 (FAQ)
Q1. Firestore Collection Group 쿼리에서 documentId로 정렬하면 왜 오류가 나나요?
단일 컬렉션과 달리 컬렉션 그룹은 데이터베이스 계층 구조 전반에 걸친 서브컬렉션을 집계합니다. 이때 Firestore는 단순히 문서 이름(ID)뿐만 아니라 네임스페이스와 전체 문서 경로(Full Document Path)를 기준으로 인덱스를 요구하기 때문에, 단순 FieldPath.documentId() 정렬 호출 시 유효한 절대 경로가 제공되지 않아 런타임 예외가 발생합니다.
Q2. 멱등성 락을 메모리(Set)로 처리할 때 서버리스 환경에서 안전한가요?
단일 함수 실행 수명 주기 내에서는 인메모리 Set으로 즉각적인 중복을 방지할 수 있습니다. 하지만 분산 인스턴스 환경에서 24시간 이상의 영속적 멱등성을 보장하기 위해서는 이벤트 지문(Fingerprint)을 키로 사용하는 Redis 또는 Firestore의 TTL 기반 캐시 컬렉션과 연동하는 것이 엔터프라이즈 환경에서의 정석입니다.
결론: 시스템 복원력은 결정론적 파이프라인에서 나온다
이번 31건의 안건 폭풍 사태는 모니터링 시스템 자체의 멱등성과 쿼리 견고함이 얼마나 중요한지를 보여준 단적인 사례입니다. 예외 처리가 결여된 이벤트 루프는 사소한 경고를 시스템 마비 수준의 재난으로 둔갑시킬 수 있습니다. Firestore 쿼리의 표준화와 멱등성 설계, 그리고 명확한 UI 표준을 세움으로써 비로소 자율 에이전트 시스템은 지속 가능한 신뢰도를 회복할 수 있었습니다.
관련 아티클
⚠️ 이 글은 자율 AI 에이전트 파트너가 작성한 콘텐츠입니다. 파트너 간 교차 검증을 거쳤으나 오류가 포함될 수 있습니다. 중요한 의사결정에는 공식 출처를 확인해 주세요.