sgkstudio.
엔지니어링

네이버 검색 API 정렬 — 수집은 되는데 쓸 후보가 0건이었던 이유

네이버 검색 API 정렬 옵션 하나가 답변 후보 선정 0건의 원인이었다. 같은 키워드를 최신순으로 조회하면 10건 중 3건만 질문과 관련이 있었는데, 관련도순으로 바꾸자 10건 중 10건이 관련 질문이었다. 손도 대지 않은 후보가 1,414건 쌓여 있었고, 그때까지 누적 수집한 631건의 2.2배였다.

2026-09-07처음 잰 숫자 — 하루치 후보 14건 중 13건이 질문 내용과 무관, 답변 초안까지 간 선정 0건, 대기 큐 3건, 그때까지 누적 수집 631건반증된 가설 — 후보 공급량이 부족하다는 가설. 앞서 키워드를 13개에서 21개로 늘리고 씨앗 검색어를 13개에서 34개로 늘려 원자료 수확을 5,978건에서 7,935건까지 올렸지만 실제 후보 순증은 +1이었다. 정렬만 관련도순으로 바꿔 조회하니 관련도가 3/10에서 10/10으로 뒤집혔고 미사용 후보가 1,414건 쌓여 있었다진짜 원인 — 최신순 정렬이 질문 작성일순이 아니라 답변 최신순이라, 답변 본문에 키워드 조각만 있어도 결과에 걸린다. 이 함정은 8월 3일에 이미 기록해 뒀는데 수집기는 그 정렬 하나만 쓰고 있었다재측정 — 한 회차에 수집 1,390건, 신규 59건, 선정 10건(상한 도달), 초안 10건 중 10건 성공, 대기 큐 3건에서 10건. 대가는 회차 실행 시간 1분 42초와 회차당 최대 96회 늘어난 API 호출(하루 한도 25,000회)

무엇이 문제였나 — 매일 도는데 답변할 질문이 안 나왔다

검색으로 회사를 찾게 만들려고 네이버 지식iN에 올라온 실제 질문에 답변을 단다. 사람이 매번 질문을 찾아다니지는 않는다. 정해진 시각에 자동으로 도는 작업이 키워드 목록을 들고 네이버 검색 API를 두드려 질문을 긁어 오고, 그중 답변할 만한 것을 골라 초안까지 만들어 둔다.

그 작업은 매일 돌았다. 실패로 끝난 적도 거의 없다. 그런데 답변 초안 단계까지 도달하는 질문이 나오지 않았다. 어느 날 뽑힌 후보 14건을 하나씩 열어 봤더니 13건이 질문 내용과 무관했다. 검색 결과에는 걸렸지만 우리가 답을 달 수 있는 질문이 아니었다.

이 작업을 굴리는 이유는 광고가 아니라 검색이다. 우리 서비스와 맞닿은 질문에 실제로 쓸모 있는 답을 달아 두면, 그 답이 검색 결과에 남아 몇 달 뒤에도 사람을 데려온다. 그러려면 두 조건이 동시에 맞아야 한다. 질문이 우리 키워드와 관련 있어야 하고, 그 질문에 아직 답을 달 수 있어야 한다.

이런 형태의 고장은 알아채기가 늦다. 작업이 오류를 내며 죽는 게 아니라 정상으로 끝나기 때문이다. 수집 건수도 매 회차 찍힌다. 눈에 보이는 지표는 다 살아 있고, 사람이 실제로 쓸 수 있는 질문만 안 나온다.

비용 쪽에서 보면 더 나쁘다. 검색 API 호출과 페이지 열람은 회차마다 일어나고, 그 뒤 후보를 점수 매기는 단계는 언어모델을 부른다. 즉 결과가 0인 동안에도 앞단은 계속 돈다.

처음 잰 숫자 — 후보 14건 중 13건이 무관했다

고치기 전에 남아 있던 숫자는 넷이다. 전부 파이프라인이 스스로 남긴 기록에서 꺼냈고, 따로 계측을 새로 붙이지 않았다.

  • 하루치 후보 14건 중 질문 내용과 무관 13건
  • 답변 초안까지 간 선정 0건
  • 답변 대기 큐 3건
  • 그때까지 누적 수집 631건

전날 이 파이프라인을 점검한 기록에는 같은 관찰이 이미 적혀 있었다. 후보 14건 중 13건이 무관하다는 문장까지는 같은데, 결론이 «네이버 검색 API가 키워드와 느슨하게 매칭하는 구조적 문제»였다. 관찰에서 바로 결론으로 건너뛰었고 병목을 쪼개지 않았다.

이 파이프라인은 그 전에도 한 번 크게 흔들린 적이 있다. 7월 26일에는 신규 질문 유입이 하루 15건에서 2건으로 떨어졌고, 그때 원인은 키워드 목록 안에 이미 판매를 접은 서비스를 겨냥한 키워드가 남아 있던 것이었다. 키워드를 13개에서 21개로 늘려 고쳤고, 그 회차에 신규 후보가 2건에서 92건으로 올라왔다.

그 사고를 한 번 겪었기 때문에 이번 증상도 같은 자리로 읽혔다. 키워드 목록이 잘못됐거나 부족하다는 자리다. 실제로 그때는 목록을 고치자 그 회차에 신규 후보가 46배로 뛰었으니, 같은 처방을 다시 꺼내는 게 자연스러웠다.

그 사고에는 이번 이야기와 겹치는 부분이 하나 더 있다. 목록에 남아 있던 죽은 키워드 하나가 긁어 온 46건은 수집 숫자에는 그대로 잡혔지만 뒷단 기준에서 전량 버려지고 있었다. 앞단 건수와 뒷단 건수가 따로 논다는 신호는 그때 이미 나와 있었다.

그래서 이번에도 같은 처방이 먼저 떠올랐다. 후보가 안 나오면 후보를 더 긁어 오면 된다는 처방이다.

세운 가설 — 후보 공급이 모자란다고 봤다

회사 대표가 물었다. «후보를 더 많이 뽑아 놔야 하는 거 아니야?» 표면만 보면 맞는 말이다. 선정이 0건이면 뽑을 재료가 없다는 뜻이고, 재료를 늘리면 그중 몇 개는 살아남는다.

가설을 한 줄로 적으면 이렇다. 병목은 공급량이고, 검색 API가 키워드를 느슨하게 매칭하니 그 손실을 물량으로 메운다.

처방도 명확하다. 키워드를 늘리고 씨앗 검색어를 늘려 원자료 수확을 키운다. 실제로 그 실험은 이미 한 번 돌려 본 적이 있었다.

가설을 세울 때 우리가 안 한 일이 하나 있다. 이미 들어와 있는 후보가 어디서 몇 건씩 버려지는지 층별로 세지 않았다. 입구를 넓히는 처방은 그 계산 없이도 세울 수 있고, 그래서 먼저 떠오른다.

여기서 이 가설의 수명이 끝났다는 걸 미리 적어 둔다. 다만 검증에 쓴 재료가 새로 만든 계측이 아니라 이전 실험이 남긴 숫자였다는 점이 중요하다. 물량을 늘려 본 기록이 이미 있었다.

가설이 틀린 지점 — 물량을 늘려도 순증이 +1이었다

앞선 실험에서 씨앗 검색어를 13개에서 34개로 늘렸다. 원자료 수확은 5,978건에서 7,935건으로 올라갔다. 그런데 실제로 쓸 수 있는 후보의 순증은 +1이었다.

들어오는 양을 5,978건에서 7,935건으로 늘렸는데 끝에서 1건이 늘었다면, 병목은 입구가 아니다. 어딘가에서 들어온 것 대부분을 버리고 있다는 뜻이다.

입구를 넓히는 처방이 매력적인 이유는 결과가 눈에 잘 보이기 때문이다. 씨앗 검색어를 늘리면 수확 건수는 그날로 올라간다. 그 숫자가 올라가는 동안 끝단이 그대로여도, 무언가 나아지고 있다는 느낌은 남는다. 이번에는 그 느낌과 실제 순증을 나란히 놓고 볼 수 있었던 게 다행이었다.

그래서 방향을 바꿔 같은 키워드를 정렬만 다르게 해서 조회했다. 네이버 검색 API는 정렬 옵션을 두 개 준다. 최신순과 관련도순이다. 우리 수집기는 최신순 하나만 쓰고 있었다.

같은 키워드를 최신순과 관련도순으로 각각 10건 조회한 결과 막대그래프. 영업 관리 프로그램은 최신순 3건 관련도순 10건, 홈페이지 제작 비용도 최신순 3건 관련도순 10건
정렬 옵션 하나만 바꿨는데 관련 질문 비율이 뒤집혔다 — 키워드도 코드도 그대로다

«영업 관리 프로그램»으로 조회하면 최신순 10건 중 3건만 관련 질문이었는데, 관련도순 10건은 10건 전부 관련 질문이었다. «홈페이지 제작 비용»도 똑같이 3건에서 10건으로 뒤집혔다.

그다음에 센 숫자가 결정적이었다. 관련도순으로 긁으니 우리가 손도 대지 않은 관련 질문이 1,414건 나왔다. 그때까지 이 파이프라인이 누적으로 수집한 631건의 2.2배다.

미사용 후보 1,414건과 누적 수집 631건을 나란히 보여 주는 수치 카드. 배수는 2.2배
공급이 마른 게 아니라 이미 쌓여 있었다 — 늘려야 할 것은 물량이 아니라 조회 방식이었다

공급량 가설은 여기서 죽었다. 후보는 부족하지 않았다. 우리가 안 보고 있었을 뿐이다.

진짜 원인 — 최신순은 질문 작성일순이 아니었다

네이버 검색 API의 최신순 정렬은 질문이 올라온 날짜순이 아니다. 답변이 달린 순서를 기준으로 정렬한다. 답변 최신순이다.

이 차이가 결과를 오염시키는 경로는 단순하다. 몇 년 전에 올라온 질문이라도 어제 누군가 답변을 달았고 그 답변 본문에 «홈페이지 제작» 같은 조각이 섞여 있으면 최신순 상위에 올라온다. 질문 자체는 우리 키워드와 아무 상관이 없어도 걸린다.

최신순으로 정렬하면 질문이 아니라 답변이 정렬된다. 답변 본문에 키워드 조각이 있으면 질문 내용과 무관해도 상위에 뜬다.

후보 14건 중 13건이 무관했던 이유가 이것이다. 검색 API가 느슨해서가 아니라, 우리가 질문이 아니라 답변을 기준으로 줄을 세워 달라고 요청하고 있었다.

정렬 옵션 이름이 이 오해를 거든다. 최신순이라는 말을 보면 질문이 새로 올라온 순서라고 읽게 된다. 문서를 다시 읽지 않으면 코드만 봐서는 무엇을 기준으로 줄을 세우는지 알 수 없고, 결과가 그럴듯하게 나오면 더 확인하지 않는다.

그리고 이 오염은 조용하다. 검색 결과는 매번 가득 차서 돌아온다. 수집 건수도 정상이다. 무관한 질문이 섞여 있다는 사실은 사람이 결과를 하나씩 열어 봐야만 드러나고, 자동화는 정확히 그 확인을 사람에게서 걷어 간 자리다.

더 뼈아픈 건 이 함정을 이미 알고 있었다는 점이다. 8월 3일 기록에 최신순 정렬의 이 성질이 그대로 적혀 있었다. 아는 것과 돌고 있는 것이 달랐고, 그 사이를 이어 주는 검사는 없었다.

«구조적 한계»로 결론 내리면 조사가 거기서 멈춘다. 실제로는 한 줄짜리 매개변수 선택이었다.

정렬축 하나를 잘못 고르면 그 뒤 단계가 아무리 정교해도 소용이 없다. 점수를 매기는 단계는 들어온 후보 중에서 고를 뿐이고, 들어온 14건이 전부 무관하면 가장 덜 무관한 것을 고를 따름이다. 앞단이 오염되면 뒷단은 그 오염을 정제하지 못한다.

무엇을 고쳤나? — 두 정렬을 따로 돌려 합쳤다

관련도순 하나로 갈아타는 안을 먼저 검토하고 기각했다. 관련도순은 순위가 안정적이라 매일 거의 같은 상위를 돌려준다. 그것만 쓰면 새로 올라온 질문이 들어올 통로가 끊긴다. 신선한 질문은 최신순이 가져오고, 관련 있는 질문은 관련도순이 가져온다. 둘 다 필요하다.

그래서 키워드마다 두 축을 각각 조회한 뒤 질문 고유번호로 중복을 제거해 합쳤다. 최신순은 키워드당 20건, 관련도순은 3페이지에 걸쳐 30건씩 가져온다.

조회 방식 전후 비교 코드 그림. 이전에는 키워드당 최신순 20건 한 번만 조회했고, 이후에는 최신순 20건과 관련도순 3페이지 곱하기 30건을 따로 조회해 질문 고유번호로 합친다
두 축을 한 목록으로 묶지 않고 따로 도는 게 핵심이다 — 묶으면 질문이 적은 키워드에서 관련도순이 통째로 빠진다

두 축을 따로 도는 이유가 이 변경에서 제일 미묘한 부분이다. 한 목록으로 묶어 순회하면 최신순 결과가 20건을 못 채우고 소진될 때 그 자리에서 반복이 멈춘다. 그러면 그 키워드의 관련도순 조회까지 통째로 건너뛴다.

하필 그 키워드가 관련도순이 가장 필요한 자리다. 질문이 적은 키워드일수록 최신순으로는 몇 건 못 건지고, 정확히 거기서 관련도순이 안 돌게 된다. 지금 잘 도는 키워드는 어느 쪽으로 짜도 잘 돌기 때문에 이 결함은 시험할 때 안 보인다.

한 축을 더 켜는 변경은 코드로 보면 반복문 하나가 늘어난 정도다. 그 반복문을 어디에 두느냐가 질문이 적은 키워드 전부의 운명을 갈랐다.

페이지를 열기 전에 무엇을 거를 수 있나?

관련도순을 켜면 반대쪽 문제가 따라온다. 관련도가 높은 질문은 이미 답변이 많이 달린 질문이기도 하다. 사람들이 많이 답한 질문이 검색에서도 관련도 상위에 오른다.

네이버 지식iN은 답변이 8개 이상 달린 질문에는 답변 에디터를 안 내준다. 즉 답변이 쌓인 질문은 우리가 고르더라도 결국 못 쓴다. 문제는 답변 수를 확인하려면 보통 질문 페이지를 열어 봐야 한다는 점이다.

그런데 검색 결과가 돌려주는 주소 안에 이미 답이 있었다. 각 결과 주소에는 그 답변이 몇 번째 답변인지 나타내는 번호가 붙는다. 이 번호가 7이면 그 질문에는 최소 7개의 답변이 있다는 뜻이다. 실제로 한 질문이 3·4·5·6·7·9·10번 답변으로 검색 결과에 7번 등장한 사례를 확인했다.

이 번호로 8 이상을 미리 걸러 내면 페이지를 한 번도 열지 않고 탈락시킬 수 있다. 관련도순으로 새로 들어온 1,313건 중 12%가 여기에 걸렸다.

관련도순을 켜기 전이었다면 이 게이트는 없어도 그만이었다. 최신순 후보는 답변이 적은 질문 쪽에 흩어져 있었기 때문이다. 새 축을 열면서 그 축이 데려오는 편향까지 같이 받았고, 그 편향을 막는 장치가 같은 변경 안에 들어가야 했다.

점수를 매기는 단계에서 답변 수를 판단하게 하는 안은 기각했다. 그 단계는 언어모델을 부르므로 호출마다 돈이 든다. 답변 번호는 이미 손에 쥔 검색 결과에서 정규식 한 줄로 뽑는다. 비용 0인 필터를 비용 드는 단계 뒤에 두는 건 순서가 거꾸로다.

같이 손본 게 두 가지 더 있다. 한 회차에 페이지를 여는 예산을 250건으로 묶었고, 초과분은 이미 본 질문 기록에 적지 않고 다음 회차로 넘긴다. 그리고 탈락이 확정된 질문도 그 기록 파일에 적기 시작했다.

이전에는 통과한 질문만 적었다. 최신순 열 건씩 돌던 시절에는 티가 안 났지만, 신규 후보가 천 단위로 들어오는 지금은 매 회차 같은 질문의 페이지를 다시 여는 낭비가 커진다.

예산 250건이라는 숫자에도 근거가 있다. 한 회차가 감당할 페이지 열람 수를 정해 두면, 후보가 갑자기 천 단위로 들어와도 회차 실행이 무한정 길어지지 않는다. 넘친 몫은 버리는 게 아니라 다음 회차로 넘어간다. 잔여 후보 967건을 회차마다 250건씩 소화하는 구조가 여기서 나왔다.

90일이라는 상한은 무엇을 지키고 있었나?

수집기에는 질문 작성일 상한이 90일로 박혀 있었다. 90일보다 오래된 질문은 후보에서 뺐다.

이 값이 지키려던 건 채택이었다. 질문자가 답변을 채택하면 답변자 등급이 오르고, 오래된 질문은 질문자가 돌아오지 않아 채택될 가능성이 낮다는 논리다.

실제 숫자를 확인했다. 채택은 누적 0건이었고 등급도 그대로였다. 이 파이프라인이 실제로 버는 건 채택이 아니라 검색 노출이고, 검색 노출에는 질문이 언제 올라왔는지가 상관없다. 지키던 대상이 처음부터 없었다.

상한을 365일로 올리면 몇 건이 더 통과하는지 표본 60건으로 셌다.

작성일 상한별 통과 건수 표. 관련도순 미사용 후보 1,160건 기준으로 90일 상한은 193건, 365일 상한은 502건이 통과한다
지키던 값이 채택 0건이라면 상한을 유지할 이유가 없다 — 같은 후보 풀에서 통과분이 193건에서 502건으로 올라간다

관련도순으로 쌓인 후보 1,160건 가운데 90일 상한을 유지하면 193건이 남고, 365일이면 502건이 남는다. 숫자 하나를 바꾸는 것만으로 193건이던 통과분이 502건이 됐다.

재측정 결과 — 수집 1,390건에서 선정 10건

고친 뒤 한 회차를 그대로 돌려 층마다 다시 셌다. 측정 방법은 바꾸지 않았다. 파이프라인이 원래 남기던 단계별 건수를 그대로 읽었다.

재측정 층별 표. 수집 1,390건, 신규 59건, 선정 10건으로 상한 도달, 초안 성공 10건 중 10건, 대기 큐는 3건에서 10건
선정이 0에서 10으로 올라왔고 그 10은 후보가 없어서가 아니라 회차 상한에 걸려 멈춘 숫자다

선정 10건은 후보가 모자라 멈춘 숫자가 아니라 회차 상한에 닿아 멈춘 숫자다. 고치기 전에는 0이었고, 그때는 상한 근처에도 못 갔다. 답변 초안은 10건 중 10건이 성공했고 대기 큐는 3건에서 10건이 됐다.

대가도 적어 둔다. 페이지 예산 250건 때문에 회차 실행 시간이 1분 42초로 늘었다. 최신순만 돌던 시절에는 수 초에 끝났다. 관련도순 3페이지 조회로 API 호출은 회차당 최대 96회 늘었는데, 하루 한도가 25,000회라 여유는 크다.

이 파이프라인에서 실행 시간은 사람을 기다리게 하는 값이 아니다. 정해진 시각에 혼자 도는 작업이라 몇 초가 1분 42초로 늘어도 누구도 그 앞에 앉아 있지 않다. 반대로 API 호출 한도는 넘기는 순간 회차 전체가 죽으므로, 늘어난 96회가 하루 한도 25,000회에서 얼마나 떨어져 있는지를 같이 봤다.

이 변경이 치른 대가를 보여 주는 수치 카드. 회차 실행 시간 1분 42초, 회차당 늘어난 API 호출 96회, 하루 호출 한도 25,000회
느려진 건 사실이지만 한도에서 멀다 — 이 정도 대가로 살 수 있는 결과였는지가 판단 기준이다

구현 중에 스스로 찾은 오류도 하나 있다. 초판은 답변 번호가 작은 것부터 페이지를 열도록 순서를 잡았다. 답변이 적은 질문이 좋은 질문이니 먼저 보자는 생각이었는데, 그 순서로 돌리자 예산 150건 중 111건이 작성일 초과로 증발했다.

답변 번호와 질문 나이는 서로 다른 축이다. 답변이 적은 질문을 먼저 보면 오래된 질문부터 열어 보게 되고, 그 대부분이 작성일 상한에 걸려 버려진다. 최신순 축을 먼저 보도록 순서를 되돌리고 예산을 250건으로 늘려 해소했다.

이 오류를 시험 단계에서 못 잡은 이유도 적어 둔다. 예산 150건이 다 소진됐고 작업은 정상으로 끝났다. 실패한 것처럼 보이지 않았다. 111건이 사라진 사실은 층별 건수를 다시 세고 나서야 나왔고, 그건 이 글의 나머지 부분과 정확히 같은 종류의 고장이다.

남는 한계 — 백로그가 마르면 다시 0으로 갈 수 있다

이번 회차의 10건은 쌓여 있던 재고에서 나왔다. 잔여 후보 967건을 회차마다 250건씩 소화하는 구조라, 재고를 다 쓴 뒤에도 선정 건수가 유지되는지가 아직 확인되지 않은 축이다. 8월 19일을 확인 시점으로 잡아 뒀다.

재고가 마른 뒤의 회차가 진짜 시험이다. 그때는 최신순과 관련도순이 매일 새로 데려오는 몫만 남고, 관련도순은 순위가 안정적이라 같은 상위를 반복해서 준다.

선정 10건이 실제 발행까지 통과하는지도 별개 문제다. 선정과 초안 생성은 발행이 아니다. 이 파이프라인은 뒤쪽에 품질 기준이 몇 겹 더 있고, 앞단 숫자가 올라갔다고 뒷단이 따라 오른다는 보장은 없다.

가장 마음에 걸리는 건 발견 경로다. 정렬축이 오염된 상태로 얼마나 오래 돌았는지 우리는 모른다. 자동으로 잡힌 게 아니라 사람이 같은 키워드를 두 방식으로 나란히 조회해 보고서야 드러났다. 판매를 접은 서비스의 키워드가 목록에 남아 있던 사고도 같은 방식으로, 손으로 나란히 읽다가 발견했다.

사람이 나란히 읽어야 드러나는 결함에는 공통점이 있다. 어느 쪽도 오류를 내지 않는다는 점이다. 최신순 조회는 정상 응답을 돌려줬고, 접힌 서비스를 겨냥한 키워드도 검색 결과를 정상으로 가져왔다. 둘 다 잘못된 게 아니라 쓸모없는 결과를 성공으로 보고했다.

두 번 같은 방식으로 발견했다면 다음에도 그럴 확률이 높다. 정기 대조 장치는 아직 없고, 이 글을 쓰는 시점에도 그대로다.

한 가지는 분명해졌다. 자동화가 매번 정상으로 끝나는데 결과가 0이면 재료를 더 넣기 전에 이미 들어온 재료가 어디서 버려지는지부터 세야 한다. 같은 함정을 콘텐츠 발행 쪽에서도 만난 적이 있고, 그 기록은 콘텐츠 자동화 파이프라인이 0편을 낸 이야기에 따로 정리해 뒀다.

비슷한 자동화를 굴리다가 «분명히 도는데 결과만 없다»는 지점에 서 있다면 상담으로 상황을 알려 주시면 된다. 우리가 어디를 먼저 재는지 그대로 알려 드린다.

글쓴이 — SGK 스튜디오. 이 글의 숫자는 전부 파이프라인이 스스로 남긴 회차 기록에서 꺼냈다.

같은 방식으로 우리 업무를 재보고 싶다면

이 글에 쓴 계측·판정·자동화는 저희가 실제로 매일 돌리는 방식 그대로입니다. 어떤 작업을 자동화할 수 있고 어디를 사람이 잡아야 하는지, 현재 업무 흐름을 놓고 같이 짚어 드립니다.

새 글이 올라오면 알려 드립니다

이런 계측 기록을 새로 쓰면 메일로 한 통 보냅니다. 광고나 판촉은 보내지 않습니다.