앞 편에서 사이트맵의 발견 URL 수를 실제 글 수와 대조하라고 했다. 이번 편은 내가 그 대조를 하다가 실제로 터뜨린 사건이다. 어느 날 숫자를 맞춰보니, GSC가 사이트맵에서 발견했다는 URL은 약 3,910개인데 라이브 사이트맵에 실제로 들어 있는 글은 약 4,349개였다. 439개가 붕 떠 있었다. 구글이 내 글 439개의 존재 자체를 아예 모르고 있었다는 뜻이다.
더 섬뜩한 건, 이 439개가 어디에도 에러로 잡히지 않았다는 점이다. GSC 어느 화면에도 빨간불 하나 없었다. 그냥 구글의 발견 목록에서 조용히 빠져 있었을 뿐이다. 색인 문제 중 가장 위험한 부류가 바로 이렇게 아무 경고 없이 소리 없이 진행되는 것들이다. 에러는 알려주기라도 하지만, 이런 누락은 스스로 세어보지 않으면 영영 모른다. 원인을 추적한 과정을 그대로 남긴다.
![[실전 SEO 10] 글 439개가 구글에서 사라졌던 이유, lastmod와 재제출](https://img.thenullpage.com/posts/5564/5564_1_ab9d61.webp)
1. 숫자가 안 맞는다는 감각
이 사건은 사실 숫자가 좀 비네 하는 사소한 위화감에서 시작됐다. GSC 사이트맵 화면의 발견됨 수치와, 라이브 사이트맵을 직접 열어 센 URL 수가 400개 넘게 차이 났다. 처음엔 오차겠거니 싶었지만 규모가 오차라기엔 컸다.
SEO에서 이런 숫자 불일치는 절대 그냥 넘기면 안 되는 신호다. 대부분의 색인 사고는 어느 두 지표가 서로 안 맞는 데서 처음 꼬리를 드러낸다. 감으로 괜찮겠지 하는 대신, 두 숫자를 실제로 뽑아 비교하는 습관이 사고를 조기에 잡는다. 나도 이때 그냥 3,910이면 충분하지 하고 넘어갔다면 439개는 몇 달을 더 사라진 채로 있었을 것이다. 위화감을 무시하지 않은 게 이 사건을 푼 유일한 열쇠였다.
이 사건 이후 나는 지표 대조를 감이 아니라 스크립트로 자동화했다. 사이트맵의 실제 URL 수를 세는 스크립트를 만들어 두고, GSC 화면 숫자와 주기적으로 맞춰본다. 사람은 숫자가 좀 비네 하는 위화감을 놓치기 쉽지만, 매번 두 숫자의 차이를 뽑아 보여주는 자동 점검은 그걸 놓치지 않는다.
2. 원인은 하위 사이트맵 재읽기 지연
추적해보니 구조는 이랬다. 나는 사이트맵 인덱스와 그 아래 여러 하위 사이트맵으로 나눠 운영하고 있었는데, 구글이 최상위 인덱스는 최근에 다시 읽었지만 하위 사이트맵의 본문, 즉 실제 URL 목록은 특정 날짜 이후로 재수집을 하지 않고 있었다.
인덱스는 하위 사이트맵의 주소만 가리킬 뿐, 그 안에 URL이 몇 개로 늘었는지는 하위를 직접 다시 읽어야 아는데 그걸 안 한 것이다. 그 결과 그 날짜 이후에 추가된 439개 글이 구글의 발견 목록에 영영 들어가지 못했다. 하필 그 시점이 크롤이 급감하던 시기와 정확히 겹쳤다. 크롤 예산이 쪼그라들자 구글이 하위 사이트맵 재읽기의 우선순위부터 낮춘 것으로 보인다. 예산이 줄면 이렇게 티 안 나는 곳부터 소리 없이 밀린다.
숫자 불일치를 조기에 잡는 습관은 사실 SEO를 넘어 운영 전반의 안전장치다. 발행한 글 수, 사이트맵 URL 수, GSC 발견 수, 색인 수. 이 네 숫자가 대략 같은 흐름으로 움직여야 정상이다. 어느 하나가 유독 뒤처지거나 튀면 거기서 뭔가 새고 있다는 뜻이다. 나는 이 네 숫자를 한 표에 놓고 보는 대시보드를 만들어 두었다.
이런 조용한 누락을 막는 가장 확실한 방법은 발행 파이프라인이 사이트맵과 한 몸으로 움직이게 하는 것이다. 새 글이 저장되는 순간 사이트맵 생성 로직이 그 글을 자동으로 포함하고 lastmod도 그때 실제 시각으로 갱신되게 묶어두면 사람이 개입할 여지가 없어진다. 439개가 샜던 건 결국 발행과 사이트맵이 따로 놀았기 때문이다.
3. lastmod를 현재시각으로 두는 안티패턴
재읽기 지연의 배후엔 lastmod 문제가 있었다. 사이트맵 인덱스가 하위 사이트맵을 가리킬 때 각 하위의 lastmod, 즉 마지막 수정 시각을 붙이는데, 이걸 요청할 때마다 현재시각(now)으로 동적으로 찍고 있었다. 코드상으론 편했지만 이게 독이었다.
언뜻 항상 최신이라 좋아 보이지만 정반대다. 원칙상 lastmod는 그 사이트맵 안에서 실제로 가장 최근에 바뀐 글의 시각이어야 한다. 매번 지금 시각으로 찍으면, 구글이 어제 읽었는데 오늘도 방금 바뀌었다 나오고 내일도 그러니, 결국 이 사이트는 lastmod를 신뢰할 수 없다고 학습하고 오히려 재읽기 우선순위를 낮춘다. freshness 신호를 스스로 망가뜨리는 전형적인 안티패턴이다. 항상 최신이라는 신호는 결국 아무 신호도 없는 것과 같아진다. 양치기 소년의 사이트맵인 셈이다.
재읽기 지연이 크롤 급감기와 겹쳤다는 점은 중요한 교훈을 남긴다. 크롤 예산이 넉넉할 땐 티가 안 나던 구조적 약점이, 예산이 쪼그라들면 가장 약한 고리부터 터진다. 그래서 신생 도메인은 예산이 빠듯하다는 전제 아래 사이트맵 구조를 처음부터 튼튼히 짜두는 게, 나중에 급할 때 덜 무너지는 길이다.
반대 극단도 조심해야 한다. lastmod를 아예 안 붙이는 것이다. 그러면 구글은 언제 바뀌었는지 알 길이 없어 매번 전체를 다시 읽거나 재읽기를 미룬다. 정답은 없애는 것도 아무 때나 찍는 것도 아니라, 실제로 바뀐 시각을 정확히 담는 것이다. 정확한 lastmod 하나가 크롤 예산을 아껴 준다.
4. 조치, 재제출과 lastmod 정정
조치는 두 갈래였다. 즉각적으로는 GSC 사이트맵 메뉴에서 하위 사이트맵을 강제로 재제출해서 구글이 다시 읽게 만들었다. 이 한 번의 재제출만으로 미발견 상태이던 439개가 픽업되기 시작한다. 응급 처치는 이걸로 끝난다.
근본적으로는 lastmod를 현재시각이 아니라 실제 최종 수정 시각으로 바꿨다. 이제 사이트맵이 진짜로 바뀐 날에만 새 lastmod를 내보내니, 구글이 그 신호를 믿고 효율적으로 재읽기를 한다. 여기에 IndexNow로 신규 정식 글만 소량씩 알리는 것도 병행할 수 있다. 단, 신생 도메인이 옛 URL 수천 건을 IndexNow로 일괄 핑 하는 건 API 남용으로 잡혀 역효과가 나니, 오직 살아 있는 신규 글만 소량으로 보내야 한다. 응급과 근본을 함께 처리해야 재발이 안 난다.
lastmod를 실제 값으로 바꾸는 건 생각보다 품이 든다. 각 하위 사이트맵이 포함한 글들의 최종 수정 시각 중 가장 최근 값을 계산해 넣어야 하기 때문이다. 하지만 이 정확한 lastmod가 결국 구글의 재읽기를 효율화해서, 새 글이 더 빨리 발견되게 만든다. 귀찮아도 now()로 얼버무리지 말아야 하는 이유다.
재제출은 만능이 아니라는 점도 짚고 싶다. 재제출로 발견은 시켜도 그 글들이 실제로 색인되기까지는 또 시간이 걸린다. 발견과 색인은 다른 단계이기 때문이다. 그래서 재제출 뒤에는 발견 수가 실제 글 수를 따라잡는지, 그다음 색인 수가 따라 오르는지를 순서대로 확인해야 한다. 한 번 눌렀다고 끝난 게 아니라 두 단계를 다 지켜봐야 진짜 회복이다.
5. 사이트맵은 살아 있는 문서다
이 사건의 교훈은 하나로 모인다. 사이트맵은 한 번 만들고 잊는 정적 파일이 아니라, 사이트와 함께 살아 움직여야 하는 문서라는 것이다. 새 글이 올라오면 자동으로 반영되고, lastmod는 정확한 실제 값을 담고, 발견 수와 실제 글 수를 정기적으로 대조해야 한다. 이 셋이 지켜지면 이런 종류의 조용한 누락은 거의 사라진다.
신생 도메인일수록 이 지도의 신뢰도가 곧 크롤 예산의 효율을 좌우한다. 지금까지 열 편에 걸쳐 진단부터 URL, 죽은 주소, 중복 파라미터, 해시, 사이트맵까지 크롤과 색인의 기술적 토대를 훑었다. 요지는 결국 하나로 모인다. 신생 사이트의 SEO는 화려한 기술이 아니라, 구글봇이 내 사이트에서 길을 잃지 않도록 동선을 깨끗이 치워주는 일이라는 것이다. 다음 시리즈에서는 이 토대 위에서 콘텐츠와 구조화 데이터, 그리고 검색엔진별 대응으로 한 단계 더 깊이 들어가 보겠다.
열 편을 관통하는 하나의 원칙으로 이 시리즈를 닫고 싶다. 신생 사이트의 SEO는 구글을 이기는 기술이 아니라, 구글이 내 사이트를 오해하지 않게 돕는 배려에 가깝다. 주소를 안 바꾸고, 죽은 건 죽었다고 말하고, 중복은 감추고, 지도는 정확히 그린다. 화려한 비법은 없다. 이 정직한 정리정돈이 쌓여 신뢰가 되고, 그 신뢰가 결국 검색 노출로 돌아온다.