백엔드프론트엔드트러블슈팅

SearchBox 에 옛 row 가 노출되던 문제 — confidence ≥ 0.8 인덱스 가드 + 자동수집 95 키워드/2회/Geocoding 자동화 — POP-SPOT v2.13 (+v2.13.1~v2.13.3.1)

김동현
··4분 읽기

Algolia 인덱싱에 reviewStatus/status/confidence/endDate 추가 + SearchBox 클라 이중 가드. 자동수집 04:00+16:00 2회, 키워드 50→95, Geocoding 04:30 cron 자동화, 재인덱싱 API. v2.13.1~v2.13.3.1 핫픽스 포함.

메인 상단 SearchBox 에 "이게 왜 검색 결과에 뜨지?" 싶은 오래된 row 가 조금씩 떨어져 나오고 있었음. 자동수집의 정확도 임계값은 그대로 두면서 "그 이하는 인덱스에 안 올라갈 것" 을 강제.

이 글에서 다루는 것

  • SearchBox 에 의심스러운 row 가 잡히는 구조적 이유 — Algolia 인덱스에 필요한 컬럼이 없어 가드 구성 불가
  • reviewStatus, status, confidence, endDate 를 인덱스 속성으로 추가한 이유
  • 자동수집 하루 2 회 (04:00 + 16:00) + 키워드 50 → 95개로 늘린 관점
  • Geocoding 을 04:30 cron 으로 자동화한 이유
  • v2.13.1 (게스트 모드 만료 파라미터·useEffect 깜빡), v2.13.3 (어드민 403 도배), v2.13.3.1 (admin 진입 시 인트로 튀김) 핫픽스
  • 모르는 단어 한 줄로

    용어한 줄 설명
    Algolia외부 전문 검색 서비스. "결과 미리 계산해서 빠르게 돌려주는" 구조. POP-SPOT 는 자체 검색과 함께 병행해서 쓰는 중
    confidence자동수집한 팝업이 "진짜일 확률" 을 0~1 사이 숫자로 표시한 값. 0.8 이상이면 자동 게시
    reviewStatusAUTO_PUBLISHED / APPROVED / PENDING / REJECTED 같은 운영 검수 상태
    EXPIRED종료일이 오늘보다 과거인 팝업. 05:00 cron 이 매일 일괄 처리
    Geocoding주소를 좌표 (lat/lng) 으로 변환하는 일

    무엇이 바뀌었나

    항목v2.12v2.13
    인덱스 속성name/location/category/content/imageUrl • reviewStatus/status/confidence/endDate
    인덱스 가드없음 — 모든 row 포함AUTO_PUBLISHED ∪ APPROVED ∪ null + status not EXPIRED/PENDING + confidence ≥ 0.8 강제
    SearchBox 클라추가 검증 없음<code>isVisibleHit</code> 가드로 이중 방어
    자동수집 빈도매일 04:00 1 회매일 04:00 + 16:00 2 회
    검색 키워드50 개95 개 (애니/게임 IP / 럭셔리 / 디저트 / K-pop / 지역 추가)
    Geocoding수동 호출만매일 04:30 자동 cron (1차 수집 직후)
    자동 게시 ↔ 인덱스연결 안 됨 (latent)신규/만료 즉시 Algolia 연동
    재인덱싱도구 없음<code>POST /api/admin/search/reindex</code> 신규

    왜 이렇게 했음

    이중 가드 — 인덱싱 단계에서 한 번, SearchBox 클라 렌더링 단계에서 한 번. 인덱싱만 가드하면 인덱스가 한 번 잘못 올라갔을 때 완전히 차단 불가. 양쪽 다 가드하면 운영자가 추후 수정하기 전까지는 사용자가 안 볼 수 있다.

    자동수집 2 회의 이유 — 새 팝업 게시의 상당수가 오전·정오 이후의 대량 SNS 노출 흐름을 다음날 새벽에서야 잡을 수 있음. 16:00 한 번 더 돌리는 게 당일 게시 팝업이 당일 검색 결과에 올라갈 수 있는 유일한 방법.

    95 키워드 — 50 개는 "서울 팝업 · 성수 팝업 · 강남 팝업" 수준의 최상위 키워드. 그 이하 관심사 (애니/게임 IP 점/테마 팝업/K-pop 아이돌 굿즈 팝업) 을 둘러서 수집 소스 다양성 보장. 95 개로 늘리면서 호출 횟수 증가는 LLM 호출 간격 (2.2 초) 와 Naver/Kakao 검색 간격 (800ms) 이 흡수.

    Geocoding cron 분리 — 수집하자마자 좌표 변환하면 Kakao Local API 에 주소가 부정확해서 실패한 row 가 재시도 못 함. 04:30 에 따로 돌리니 1 차에서 설익 감 자세가 수집돼 있는 주소도 2 차 완성. 실패 row 는 admin 함릹에서 수동 개입.


    코드로는 어떻게 (필요한 부분만)

    인덱싱 가드.

    파일: popspot-backend/src/main/java/com/popspot/search/AlgoliaIndexer.java

    java
    // v2.13 — AlgoliaIndexer.shouldIndex(). row 하나씩 인덱스 업로드 전 검사
    private boolean shouldIndex(PopupStore p) {
        var reviewOk = p.getReviewStatus() == null
    //      ^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   v1 이전 레코드 (구 스키마) 는 reviewStatus 컬럼 이 NULL → 허용
            || p.getReviewStatus() == ReviewStatus.AUTO_PUBLISHED
    //                                              ^^^^^^^^^^^^^^   confidence ≥0.8 에서 자동 게시된 것
            || p.getReviewStatus() == ReviewStatus.APPROVED;
    //                                              ^^^^^^^^         관리자가 수동 승인한 것
    //                                                                반대 상태 PENDING/REJECTED 는 자동 포함 불가
        var statusOk = p.getStatus() != Status.EXPIRED
                    && p.getStatus() != Status.PENDING;
    //                                  ^^^^^^^^^^^^^^   게시 상태 EXPIRED 와 PENDING 은 명확히 탈락
        var conf = p.getConfidence() == null ? 1.0 : p.getConfidence();
    //             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   null 이면 구 레코드 (수동 등록 등 confidence 가 없는 경우) 1.0 으로 간주·허용
        return reviewOk && statusOk && conf >= 0.8;
    //                                 ^^^^^^^^^^^^   세 조건 모두 true 이면 인덱스. 하나라도 안되면 제외
    }

    SearchBox 클라 이중 가드.

    파일: popspot-frontend/src/features/popup/SearchBox.tsx

    typescript
    // v2.13 — features/popup/SearchBox.tsx. 클라이언트 쪽 이중 가드
    function isVisibleHit(h: any) {
      // 위: h 는 Algolia 의 hit 객체. any 인 이유 — Algolia SDK 경계·에 한정됨 (v1.5 의 원칙)
      if (h.status === 'EXPIRED' || h.status === 'PENDING') return false;
      // 위: 서버 인덱스 가드가 뚫렸을 때의 최종 방어선
      if (typeof h.confidence === 'number' && h.confidence < 0.8) return false;
      //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   number 타입일 때만 비교 — undefined 는 구 row 의 NULL 에 해당 그대로 통과 시키면 됨
      return true;
    }
    results = hits.filter(isVisibleHit);
    //             ^^^^^^                  레이아웃 렌더링 직전 최종 필터

    cron.

    파일: popspot-backend/src/main/java/com/popspot/service/crawler/PopupCrawlOrchestrator.java (v1.3 이후 지속 수정)

    java
    // v2.13 — 자동수집 cron. 매일 04:00, 16:00 두 번 수집
    @Scheduled(cron = "0 0 4,16 * * *", zone = "Asia/Seoul")
    //                ^ ^ ^^^^ ^ ^ ^
    //                | | |    | | +-- 요일 (전체)
    //                | | |    | +---- 월
    //                | | |    +------ 일
    //                | | +----------- 시 = 4 와 16 두 개 (쿼리 컴마로 구분)
    //                | +------------- 분 = 0
    //                +--------------- 초 = 0
    public void scrapeTwice() { orchestrator.runOnce(); }
    //                          ^^^^^^^^^^^^^^^^^^^^^^^   v1.3 의 동일 메서드 재사용. 설계 변경 없이 호출 빈도만 늘림
    
    // v2.13 — Geocoding 자동화 cron. 04:30 에 NULL 좌표 row 채움
    @Scheduled(cron = "0 30 4 * * *", zone = "Asia/Seoul")
    //                ^ ^^ ^                                  30분 4시 — 1차 수집 후 30분 늦은 시점
    public void geocodePending() { geocodingService.fillMissing(); }
    //                                              ^^^^^^^^^^^^   lat/lng 가 NULL 인 row 만 골라 Kakao Local API 호출·채움
    //                                                              수집 시점에 실패한 row 가 30분 안에 보완됨

    핵심 파일: popspot-frontend/src/features/popup/SearchBox.tsx, popspot-backend/.../service/crawler/PopupCrawlOrchestrator.java, popspot-backend/.../service/geocoding/KakaoGeocodingService.java, popspot-backend/src/main/resources/application.properties


    v2.13.1~v2.13.3.1 팝스

    버전증상수정
    v2.13.1게스트 메인 진입 후 D-N 이 활성이지 않아서 잠시 깜빡. useEffect 의존성 누락게스트 상태 구독 + 의존성 추가
    v2.13.2약관 §10조의 2 이슐 (외부 검색 API 사용 형태)약관 문서 업데이트
    v2.13.3어드민 아닌 계정이 /admin 진입 시 403 로그 도배AuthorizationDeniedException 을 GlobalExceptionHandler 에서 403 한 줄로 조용 처리
    v2.13.3.1어드민 진입 시 인트로로 튀김middleware 의 admin 경로 예외 처리 추가

    직접 보는 법

    메인 상단 SearchBox 에 "팝업" 처럼 절반짜리 단어 입력 → 결과가 조용해졌다. 만료되었거나 운영자 검수 전 row 가 더 이상 안 보일 것. 권한 없는 상태에서 /admin 들어가 봐도 로그 도배·인트로 튀김이 없다.


    관련 글

  • 이전 — v2.12, 의견 메인 탭 + 등급별 부스트
  • 다음 — v2.14, Spotify 검색에서 cover · live · remix 탈락
  • 공유

    댓글