개인적으로 데이터 기반 디렉터리 사이트를 하나 굴려보고 있었다. 공공 데이터를 지역 단위로 정리해서 보여주는 형태였고, 페이지 수는 2만 5천 개가 넘었다.
어느 시점부터 구글 유입이 사라졌다. 확인해보니 유입이 준 정도가 아니라 색인된 페이지가 사실상 홈 하나만 남아 있었다.
네이버와 빙에서는 정상이었다. 구글에서만 통째로 빠진 상태였다. 이걸 진단하면서 알게 된 걸 정리해둔다. 결론부터 말하면, 페이지 하나하나를 아무리 들여다봐도 원인을 못 찾는 종류의 문제였다.
페이지 단위로는 멀쩡했다
처음에는 콘텐츠 품질 문제라고 생각하고 페이지를 뜯어봤다. 그런데 열어보면 그렇게 나쁘지 않았다.
- 본문은 공공 API에서 받은 실제 데이터였고, 지어낸 문장이 아니었다
- 페이지마다 얇은 콘텐츠를 걸러내는 가드가 코드에 실제로 들어 있었다
- 내용이 부족한 페이지는 색인 대상에서 빼는 처리도 되어 있었다
- 카테고리끼리 주제가 겹치지도 않았다
한동안 여기서 막혔다. 페이지별 기준으로는 다 통과하는데 사이트 전체가 걸려 있으니 뭘 고쳐야 할지 알 수가 없었다.
돌아보면 이 접근 자체가 틀렸다. 검색엔진이 대량 생성 사이트를 볼 때 보는 건 페이지 하나가 아니라 페이지 집합의 성질이었다.
첫 번째 원인 — 조합으로 URL을 만들었다
가장 크게 잘못한 건 이거였다.
지역과 업종을 조합해서 URL을 만드는 구조가 있었다. /{지역}/{업종} 형태다. 지역이 수백 개, 업종이 수백 개니까 곱하면 5만 개에 가까운 URL이 나온다. 그리고 그중 데이터가 하나라도 있는 조합은 전부 색인 대상이었다.
각각을 열어보면 실제 데이터가 들어 있으니 얇은 페이지가 아니다. 그래서 페이지 단위 가드에 걸리지 않는다. 그런데 사이트 전체로 보면 조합으로 찍어낸 5만 페이지다.
이건 검색엔진이 대량 생성 콘텐츠로 분류하는 전형적인 모양이다. 개별 페이지의 품질과 무관하게, 구조 자체가 그렇게 생겼다. 페이지마다 가드를 통과한다는 사실이 집합 수준의 판정을 통과한다는 뜻이 아니었다.
두 번째 원인 — 상위 지역 데이터를 하위에 상속시켰다
두 번째는 더 부끄러운 실수다.
지역별 수치 데이터를 보여주는 페이지가 있었는데, 데이터 원본이 광역 단위로만 제공됐다. 기초 지자체 단위 값이 없었다. 그래서 광역 값을 그대로 하위 지역에 물려주는 방식으로 페이지를 만들었다.
결과적으로 같은 광역에 속한 기초 지자체 페이지들의 본문이 거의 동일해졌다. 나중에 유사도를 재보니 96%가 넘었다. 두 페이지의 실질적인 차이가 일출·일몰 시각 2분 정도였다.
각 페이지에 “데이터가 있다"는 건 맞다. 그런데 옆 페이지와 다른 내용이 없으면 그건 별개의 페이지일 이유가 없다. 사용자 입장에서 생각해봐도 그렇다. 옆 동네 페이지를 열었는데 숫자가 똑같으면 속았다고 느낄 것이다.
이 부분은 나중에 데이터 구조를 바꿔서 지역별로 실제 다른 값이 들어가게 고쳤고, 그러고 나니 유사도가 눈에 띄게 내려갔다.
세 번째 원인 — 사이트맵과 페이지가 서로 다른 말을 했다
이건 순전히 구현 실수다.
내용이 부족한 페이지는 색인에서 빼도록 처리해뒀는데, 사이트맵 생성 로직은 그 처리를 몰랐다. 그래서 색인하지 말라고 표시한 페이지들이 사이트맵에는 그대로 들어가 있었다. 전체의 3할 가까이가 그랬다.
검색엔진 입장에서는 이상한 신호다. 사이트맵으로는 “이 페이지들 와서 보라"고 해놓고, 막상 가보면 “색인하지 마라"고 적혀 있다. 한두 개면 실수로 넘어가겠지만 수천 개 규모면 사이트 전체의 신뢰도 문제가 된다.
두 로직이 같은 판정 함수를 쓰지 않고 각자 조건을 구현하고 있었던 게 원인이었다. 지금은 한쪽에서만 판정하고 다른 쪽이 그걸 가져다 쓰게 바꿨다.
네 번째 원인 — 갱신되지도 않은 페이지의 날짜를 매일 새로 찍었다
마지막이 제일 자충수였다.
사이트맵의 lastmod 값을 생성 시점 기준으로 넣고 있었다. 즉 내용이 하나도 안 바뀐 페이지도 매일 사이트맵이 다시 만들어질 때마다 오늘 날짜로 갱신됐다고 표시됐다.
의도는 없었다. 그냥 lastmod에 현재 시각을 넣는 게 편해서 그렇게 짠 코드였다. 그런데 결과적으로는 매일 수만 개 페이지가 갱신됐다고 주장하는 사이트가 됐다.
이건 신선도 신호를 조작하는 흔한 수법과 구별되지 않는다. 그리고 한 번 이 신호가 무시되기 시작하면, 나중에 정말로 페이지를 대폭 개선했을 때 그걸 알릴 방법이 사라진다. 거짓말을 해둔 채널은 진짜 필요할 때 안 쓰인다.
정리하면
네 가지 원인은 성격이 조금씩 다르다.
| 원인 | 성격 |
|---|---|
| 조합형 URL 5만 개 | 구조적 판단 착오 |
| 상위 데이터 상속 | 데이터가 없는 걸 있는 척함 |
| 사이트맵과 페이지 불일치 | 구현 실수 |
| lastmod 매일 갱신 | 편의를 위한 코드가 신호 조작이 됨 |
공통점은 전부 페이지 하나만 봐서는 안 보인다는 것이다. 사이트 전체를 하나의 집합으로 놓고 봐야 드러난다.
그리고 넷 다 “이 정도는 괜찮겠지” 하고 넘어간 것들이었다. 각각은 정말로 사소해 보였다. 합쳐놓으니 사이트가 사라졌다.
다음 편
원인을 찾은 다음이 더 어려웠다. 고쳐놓고 나서 8주 동안 아무것도 하지 않기로 했는데, 그 결정과 그 사이에 스스로 저지른 실수에 대해서는 다음 편에 적는다.