약 18분 읽기
색인 91개가 거절되고 있었다
블로그 글이 검색에 노출되지 않는 문제에 직면했다. 처음에는 단순한 지연이라고 생각했지만, 시간이 지나도 상황은 나아지지 않았다. 그래서 직접 원인을 조사해보기로 했다. 서치콘솔을 살펴본 결과 ‘발견됨 – 현재 색인이 생성되지 않음’이라는 상태가 91개나 쌓여 있었다. 이 문제를 해결하지 못하면 블로그의 검색 유입은 기대할 수 없었다.
탐색을 시작하며 발견한 첫 번째 문제는 바로 사이트맵에 포함된 탐색 페이지들이었다. 이 페이지들은 본문이 아닌 다른 글 목록만 제공하는 구조였는데, 구글이 이를 크롤링하면서 색인을 거절하고 있었다. 또한, /home-front/ 페이지는 홈으로 301 리다이렉트되는 설정이었는데, 리다이렉트된 URL이 사이트맵에 있으면 색인 거절의 이유가 된다. 이런 점들이 색인 거절의 직접적인 원인임을 알게 되었다.
두 번째 문제는 서버 오류였는데, 이는 특정 URL에서 발생하는 5xx 에러였다. PHP 메모리 초과로 인한 문제였으며, 관련글 위젯이 전체 포스트를 불러오는 방식의 쿼리 때문에 발생했다. 이는 서버의 부하를 가중시키고 있었고, 색인 과정에 악영향을 미쳤다. 결국 색인된 글이 적절히 검색 결과에 나타나지 못하게 했다.
이러한 문제들을 확인한 후, 나는 즉시 대응책을 마련하기로 했다. 탐색 페이지를 noindex 처리하고, 사이트맵에서 제외하는 작업을 진행했다. 이렇게 해서 불필요한 URL이 색인되지 않도록 했다. 또한, PHP 메모리 초과 문제는 쿼리를 최적화하는 방식으로 해결하기로 했다. 이렇게 하여 색인 거절 문제를 해결할 수 있었다.
내가 만든 탐색 페이지가 원인이었다
탐색 페이지를 만들 때는 다양한 글이 잘 노출될 수 있도록 하려는 의도가 있었다. 그러나 이 페이지들이 구글 검색 색인에 걸리면서 문제가 발생했다. 구글이 탐색 페이지를 크롤링했을 때, 중요한 본문보다는 다른 글 목록만 나오는 것이 문제였다. 이로 인해 색인 생성이 수월하게 진행되지 않았고, 검색 유입에 차질이 생겼다.
사이트맵에 탐색 페이지가 포함된 것도 또 다른 문제였다. 특히 /home-front/라는 페이지는 홈으로 301 리다이렉트가 설정되어 있었는데, 구글은 리다이렉트되는 URL을 색인 거절 사유로 본다. 이런 점에서 탐색 페이지는 검색 엔진에 혼란을 줄 수밖에 없었다. 사이트맵에서 이러한 페이지들을 제거하는 것이 우선이었다.
이 문제를 해결하기 위해 탐색 페이지에 noindex를 추가하고, 사이트맵에서도 제외해야 했다. 이렇게 해서 구글이 불필요한 페이지를 색인하지 않도록 조치했다. Yoast 메타 필드를 REST API를 통해 직접 업데이트하여 작업을 수월하게 마무리할 수 있었다. 일일이 관리자 화면에서 설정을 변경하는 수고를 덜 수 있었다.
이러한 조치로 색인 거절 문제는 해결의 실마리를 찾았다. 구글이 더 이상 탐색 페이지를 크롤링하며 색인 거절을 발생시키지 않았기 때문이다. 이렇게 함으로써 사이트의 구조가 보다 깔끔해졌고, 검색 유입 역시 정상적으로 이루어질 수 있는 기반을 마련할 수 있었다.
리다이렉트되는 URL을 사이트맵에 두면 안 된다
사이트맵에 리다이렉트되는 URL을 포함시키는 것은 구글 색인에서 문제를 발생시킬 수 있다. 리다이렉트는 웹 페이지가 다른 URL로 이동하게 만드는 역할을 한다. 따라서 구글 크롤러가 사이트맵에 포함된 리다이렉트되는 URL을 크롤링하면, 실제 대상 페이지로 이동하게 되면서 중복되거나 불필요한 크롤링이 발생할 수 있다. 이는 색인 거절의 주요 원인 중 하나로 작용한다.
내가 작업하던 프로젝트의 경우, 홈 페이지로 리다이렉트되는 URL이 사이트맵에 포함되어 있었다. 이것이 문제가 되어 구글 검색 결과에서 색인되기를 거부당한 것이다. 사이트맵은 검색 엔진이 웹사이트의 페이지 구조를 이해하는 데 중요한 역할을 하기 때문에, 잘못된 URL이 포함되면 검색 엔진의 색인 과정에 혼란을 초래할 수 있다. 결과적으로 이는 검색 유입의 감소로 이어졌다.
이 문제를 해결하기 위해 리다이렉트되는 URL을 사이트맵에서 제거하는 작업이 필요했다. 사이트맵에서 이러한 URL들을 삭제함으로써, 구글 크롤러가 해당 페이지를 무의미하게 방문하지 않도록 할 수 있었다. 이는 색인 프로세스를 보다 원활하게 만들었고, 검색 엔진이 사이트의 주요 콘텐츠에 집중할 수 있는 환경을 조성했다.
리다이렉트 문제를 해결한 후, 사이트의 검색 가시성이 눈에 띄게 개선되었다. 색인 거절 문제가 해결되면서, 검색 유입이 자연스럽게 증가하는 것을 확인할 수 있었다. 이는 결국 웹사이트의 운영에 긍정적인 영향을 미쳤다.
메타 필드를 API로 일괄 처리
탐색 페이지와 관련된 문제를 해결하기 위해 Yoast 메타 필드를 REST API로 일괄 처리하기로 했다. 탐색 페이지가 사이트맵에 포함되어 있었고, 그로 인해 불필요하게 구글봇이 크롤링하는 상황이었다. 이를 수작업으로 해결하기에는 너무 많은 시간이 소요되므로, 자동화된 스크립트를 만들어야 했다. 특히 여러 페이지에 noindex를 설정하고 사이트맵에서 제외하는 작업은 수동으로 처리하기에는 시간이 너무 걸렸다.
기존 방식대로 관리자 화면에서 하나씩 설정을 변경하는 것은 상당한 시간 낭비였다. 이를 해결하기 위해 Yoast 메타 필드를 직접 수정하는 스크립트를 작성했다. 이 스크립트는 _yoast_wpseo_meta-robots-noindex를 1로, _yoast_wpseo_sitemap-include를 never로 설정하여 자동으로 필요한 값을 업데이트한다. 이러한 접근 방식으로 인해 불필요한 클릭을 줄이고, 실수를 방지할 수 있었다.
import json, base64, urllib.request
WP = "https://example.com/wp-json/wp/v2"
NAV_PAGES = [812, 813, 810, 809] # home-front, all, popular, new
def auth_header(user, app_pw):
token = base64.b64encode(f"{user}:{app_pw}".encode()).decode()
return {"Authorization": f"Basic {token}",
"Content-Type": "application/json"}
def set_noindex(page_id: int, hdr: dict):
body = json.dumps({
"meta": {
# 이 두 개가 핵심이다
"_yoast_wpseo_meta-robots-noindex": "1",
"_yoast_wpseo_sitemap-include": "never",
}
}).encode()
req = urllib.request.Request(f"{WP}/pages/{page_id}",
data=body, headers=hdr, method="POST")
with urllib.request.urlopen(req, timeout=30) as r:
d = json.loads(r.read())
return d["id"], d["meta"].get("_yoast_wpseo_meta-robots-noindex")
hdr = auth_header(WP_USER, WP_APP_PASSWORD)
for pid in NAV_PAGES:
print(set_noindex(pid, hdr))
# 사이트맵에서 빠졌는지 확인
# curl -s https://example.com/page-sitemap.xml | grep -c home-front → 0
Yoast 메타를 REST API로 일괄 처리 — 화면에서 클릭할 일이 아니다
REST API로 일괄 처리하게 되면서 코드의 강력한 유연성을 활용할 수 있었다. 특히 Yoast 메타 필드를 업데이트하는 과정을 자동화함으로써, 관리자 화면에 일일이 접속할 필요가 없었다. 이렇게 개선된 프로세스를 통해 빠르게 변경 사항을 적용할 수 있었고, 결과적으로 구글봇이 올바른 페이지를 크롤링하도록 유도할 수 있었다. 새로운 시스템이 문제없이 작동하면서 색인 거절 문제도 해결됐다.
5xx는 메모리 초과였다
서치콘솔에서 발견된 두 번째 문제는 ‘500 Internal Server Error’였다. 로그를 확인해보니 PHP 메모리 초과로 인한 오류였다. 관련글을 표시하는 위젯에서 전체 포스트를 한 번에 불러오는 쿼리를 사용했기 때문에 발생한 문제였다. 전체 포스트를 불러오면서 메모리 한계를 초과했고, 이는 결국 서버가 정상적으로 응답하지 못하는 상황을 초래했다. 이 부분은 사이트 성능에 직접적인 영향을 주기 때문에 신속히 해결해야 했다.
그래서 쿼리를 제한적으로 변경해 필요한 데이터만 불러오도록 수정했다. 특정 조건에 맞는 포스트만을 선택적으로 가져오도록 쿼리를 최적화했다. 이렇게 해서 불필요한 메모리 사용을 줄일 수 있었다. 메모리 사용량을 줄이면 성능 개선과 함께 서버 안정성도 높일 수 있다. 따라서 관련글 위젯 기능을 유지하면서도 서버 부하를 줄이는 방향으로 코드 구조를 조정했다.
<?php
// X 전체 포스트를 다 불러온다. 글이 늘어나면 메모리 초과로 500
$all = get_posts([
'post_type' => 'post',
'posts_per_page' => -1, // 이게 문제였다
]);
$related = array_slice(array_filter($all, fn($p) =>
has_category(get_the_category($post->ID)[0]->term_id, $p)), 0, 5);
// O 쿼리에서 끊고, ID 만 받아온다
$related = get_posts([
'post_type' => 'post',
'posts_per_page' => 5,
'post__not_in' => [$post->ID],
'category__in' => wp_get_post_categories($post->ID),
'orderby' => 'rand',
'fields' => 'ids', // 객체 전체를 안 받는다
'no_found_rows' => true, // 페이지네이션 계산 생략
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]);
메모리 초과로 500이 나던 관련글 쿼리
쿼리를 개선한 이후에는 더 이상 500 오류가 발생하지 않았다. 메모리 사용량이 줄어들면서 사이트의 반응 속도도 빨라졌다. PHP 스크립트가 메모리 한계를 넘지 않도록 주의를 기울여야 한다는 교훈을 얻었다. 코드를 개선함으로써 사이트의 안정성과 성능 두 마리 토끼를 잡을 수 있었다. 이 경험을 통해 성능 최적화의 필요성을 다시금 인식하게 되었다.
새 글을 즉시 제출하게 만들기
새로 작성한 글이 검색엔진에 빠르게 반영되지 않아 방문자 수가 줄어드는 문제가 있었다. 이 문제를 해결하기 위해, 새 글이 발행되면 즉시 구글에 색인 요청을 보내는 방식을 구상했다. 이를 위해 선택한 방법은 서비스 계정의 JWT(JSON Web Token)를 직접 서명하여 Indexing API를 호출하는 것이었다. google 라이브러리를 사용하지 않고 표준 라이브러리만으로 구현할 수 있어 복잡성을 줄일 수 있었다. JWT를 통해 인증을 수행하면, 구글 API에 직접적으로 접근하여 URL 업데이트를 요청할 수 있게 된다.
import json, time, base64, urllib.request
from pathlib import Path
# google-auth 없이 표준 라이브러리 + cryptography 로 된다
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
SA = json.loads(Path("service-account.json").read_text())
SCOPE = "https://www.googleapis.com/auth/indexing"
def b64(d: bytes) -> str:
return base64.urlsafe_b64encode(d).rstrip(b"=").decode()
def get_access_token() -> str:
now = int(time.time())
header = {"alg": "RS256", "typ": "JWT"}
payload = {
"iss": SA["client_email"],
"scope": SCOPE,
"aud": "https://oauth2.googleapis.com/token",
"iat": now,
"exp": now + 3600,
}
signing_input = f"{b64(json.dumps(header).encode())}." \
f"{b64(json.dumps(payload).encode())}".encode()
key = serialization.load_pem_private_key(
SA["private_key"].encode(), password=None)
sig = key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
assertion = f"{signing_input.decode()}.{b64(sig)}"
body = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode()
req = urllib.request.Request("https://oauth2.googleapis.com/token",
data=body,
headers={"Content-Type": "application/x-www-form-urlencoded"},
method="POST")
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())["access_token"]
def submit_url(url: str, token: str):
body = json.dumps({"url": url, "type": "URL_UPDATED"}).encode()
req = urllib.request.Request(
"https://indexing.googleapis.com/v3/urlNotifications:publish",
data=body,
headers={"Authorization": f"Bearer {token}",
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())
# 서비스 계정 이메일을 서치콘솔 속성에 '소유자'로 추가해야 한다.
# 권한만 주면 403 Permission denied 가 난다.
서비스 계정 JWT를 직접 서명해서 Indexing API 호출
구현 과정에서 짚어둘 건 JWT를 생성하고 이를 서명하는 부분이었다. 이때, 서명을 위해 필요한 개인 키를 미리 준비해 두어야 했다. 또한, 구글 클라우드 콘솔에서 서비스 계정을 생성하고, 서치콘솔 속성의 소유자로 추가하는 과정도 필요했다. JWT 서명 후, 받아온 토큰을 사용하여 Indexing API에 URL_UPDATED 요청을 전송하는 방식으로 동작한다. 이 과정을 통해 새 글이 작성될 때마다 검색엔진에 즉시 반영되도록 자동화할 수 있었다. 그 결과, 구글에서 즉각적으로 색인을 생성하여 검색 유입 증가로 이어졌다.
8월 17일에 온 메일
구글에서 온 메일은 내가 여러 날 동안 애써온 결과를 확인하는 순간이었다. 새벽에 메일함을 열었을 때, “91개의 문제 해결됨”이라는 제목을 본 순간 안도감이 밀려왔다. 그간 서치콘솔에 쌓였던 수많은 오류 메시지가 하나하나 해결되었다는 사실이 눈앞에 펼쳐졌다. 메일 내용은 구체적으로 사이트맵 문제와 5xx 오류가 모두 정리되었음을 알려주었다. 내가 처리한 작업들이 효과적으로 적용된 것을 보는 경험은 짜릿했다.
사실 색인 문제 해결을 위해 여러 가지 조치를 취했지만, 결과를 확신할 수 없었다. 그러나 이 메일은 내가 내린 결정들이 올바른 방향이었음을 확인시켜주었다. 메일의 내용을 상세히 읽어보니, 내가 수정한 부분들이 각각 어떻게 영향을 미쳤는지 간접적으로 알 수 있었다. 예를 들어, 리다이렉트 문제와 사이트맵에 없애야 했던 URL들의 제거는 색인 거절 이유가 사라졌다는 의미였다.
이 작업의 결과로 검색 유입이 눈에 띄게 증가한 것도 바로 체감할 수 있었다. 이전에는 검색 유입이 거의 없거나 미미하게 느껴질 정도였는데, 이제는 일정한 트래픽이 사이트로 유입되고 있었다. 이는 단순히 색인 문제의 해결에 그치지 않고, 실제 사용자가 내 사이트를 찾고 방문하게 만드는 데까지 이어졌다. 나의 개발 작업이 사용자 경험에도 긍정적인 영향을 미쳤다는 점에서 큰 보람을 느꼈다.
한편으로는 매번 느끼는 것이지만, 개발자라면 검색 엔진 최적화와 관련된 작업도 능숙하게 다룰 줄 알아야 한다는 생각이 든다. 코드 작성이나 버그 수정은 물론이고, 색인과 크롤링에 대한 이해도 필요하다. 구글 메일은 단순한 시스템 알림이 아니었다. 그것은 내가 개발자로서 성장하고 있다는 또 다른 증거였다.
함께 볼 글
이 글과 이어지는 내용은 아래에 정리해뒀다.