사이트맵 쪼개고 어드민 밀었더니 밤
목차
콘텐츠 사이트맵이 GSC에서 계속 튕겨 나오길래 들여다봤더니 50MB를 넘고 있었다. 사이트맵 파일 하나당 50MB, URL 수 5만 개 한도는 공식 문서에 나와 있긴 한데, 실제로 걸리기 전까진 체감이 없음. 처음엔 URL 개수 문제인 줄 알았는데 카운트해보니 개수는 여유 있었고 바이트가 문제였다. <lastmod>, <changefreq>, <priority> 같은 선택 필드를 다 채우면 URL 하나당 무게가 꽤 달라진다. 드물게 쓰는 필드 빼면 절반 이하로 줄일 수 있는 경우도 있으니, 사이즈가 아슬아슬해질 것 같으면 필드 취사선택도 고려할 만하다.
해결은 청크 분할. 주제 도메인 단위로 쪼개는 게 제일 깔끔하다. 특정 카테고리만 다시 제출하고 싶을 때 전체를 올릴 필요가 없어지고, 에러 추적도 훨씬 쉬워짐.
<!-- sitemap_index.xml 예시 -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemaps/sitemap_articles.xml</loc>
<lastmod>2026-06-29</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemaps/sitemap_members.xml</loc>
<lastmod>2026-06-29</lastmod>
</sitemap>
</sitemapindex>
청크 분할 후 순서대로 해야 할 것들:
- 인덱스 파일 URL을
robots.txt에Sitemap:라인으로 명시 - GSC에서 인덱스 파일 직접 제출
- 각 청크가 50MB / 5만 URL 이하인지 재확인
- 기존 사이트맵 URL이 GSC에 등록되어 있다면 교체 또는 제거
GSC 직접 제출은 단순히 "봇한테 알린다"는 의미가 아니라 처리 상태를 추적할 창구를 여는 거다. 제출 없이도 크롤러는 결국 찾아오지만, 피드백 루프가 없으면 뭔가 잘못됐을 때 한참 뒤에야 안다. 일단 제출까지 마쳤고, 실제 크롤이 붙는지는 며칠 지켜봐야 함. 이 대기 시간이 SEO 작업에서 제일 답답한 부분이다.
예약어 함정, 알고 나면 허무하다
mod를 DB 쿼리 alias로 썼다가 502가 터졌다. 쿼리 실행 시점에 죽으니까 처음엔 연결 문제나 타임아웃으로 봤다. 배포 직후였으면 배포 원인을 먼저 의심했을 텐데, 로그를 타고 올라가다 결국 쿼리 자체가 문제였다는 걸 알았다.
mod는 SQL에서 나머지 연산자(MOD(a, b) 혹은 a MOD b)로 예약되어 있다. alias로 쓰면 파서가 헷갈리고 동작이 DB 구현마다 다를 수 있는데, 서버가 죽어버리는 쪽이 제일 나쁜 경우다. lm으로 바꾸고 나서야 해결됐다.
이런 버그는 재현하기는 쉬운데 원인 추적이 느리다. 에러 메시지가 직관적으로 나오지 않는 경우가 많아서 예약어 충돌일 거라고 의심하기까지 시간을 그냥 씀. 짧은 단어를 alias로 쓸 때는 한 번 더 체크하는 습관이 필요하다는 걸 체감했다. 기록해뒀다.
크론 권한 - 배포 전 체크리스트에 들어가야 할 항목
특정 봇이 /var/log 아래에 새 파일을 만들려다 권한이 없어서 죽었다. 서비스 유저는 기존 파일에 append하는 건 되는데, 없는 파일을 create하는 건 권한이 다름. 디렉토리에 대한 write 권한이 없으면 새 파일을 만들 수 없다. 배포 전에 touch로 미리 만들어두지 않아서 생긴 일이다.
# 배포 전 로그 파일 미리 생성
sudo touch /var/log/bot-name/worker.log
sudo chown service-user:service-group /var/log/bot-name/worker.log
# logrotate 쓴다면 create 지시어로 자동 생성 위임
# /etc/logrotate.d/bot-name
# create 0644 service-user service-group
배포할 때 한 번만 확인하면 되는 일인데, 빠뜨리면 크론이 조용히 죽는다. 크론은 실패해도 노이즈가 적어서 알아채는 데 시간이 걸리는 경우가 있음. 이번엔 빠르게 잡았지만, 배포 체크리스트에 "서비스 유저로 로그 파일 생성 가능 여부 테스트" 항목 하나 박아두는 게 맞다.
vtuber 봇은 두 군데 고쳤다. DB 연결이 스캔 중에 wait_timeout으로 끊기는 건 장시간 배치 작업에서 자주 나오는 패턴이다. 서버 쪽에서 연결을 끊어버려도 클라이언트는 살아있다고 생각하는 상태가 되는데, 해결 방향은 대략 둘이다. 연결 풀에서 ping/reconnect 옵션을 켜거나, 배치 루프 안에서 청크 단위로 연결을 명시적으로 닫고 다시 여는 것. 어느 쪽이 낫냐는 배치 주기와 쿼리 패턴에 따라 달라서 일반론은 없다.
디스코드 알림 노이즈는 실제 조치가 없는 날에도 알림이 나가던 거였는데, 조건 한 줄 추가로 막혔다. 코드 자체는 간단했는데 알림 로직 흐름을 다시 읽는 데 시간이 걸렸음. 조건이 여러 레이어에 퍼져 있으면 "이 알림이 왜 나갔지?"를 추적하기 어렵다. 알림 발화 조건은 가능하면 한 곳에 모아두는 게 나중에 덜 고생한다는 생각이 든다.
어드민 UI는 쌓아두던 걸 한꺼번에 밀었다. 명암비 전수 점검, 모바일 레이아웃, PageHeader 통일에 이모지 제거까지. 14개 페이지를 일일이 열어서 눈으로 확인하는 건 머리가 아니라 눈이 피로한 작업이다. 명암비는 WCAG AA 기준 4.5:1을 최소선으로 봤는데, 어드민은 내부 툴이라 기준을 낮게 봐도 된다는 시각도 있지만 이 화면을 제일 오래 보는 게 우리 팀이라 대충 넘길 이유가 없다. 끝내고 나니 화면이 좀 더 정돈된 느낌이라 기분은 좋다.
여기에 크롤 예산 복구를 위한 lastmod 추가, kpopdex 홈 트렌딩 멤버 섹션, i18n 영문 노출 수정, 티켓따까 전환 기능까지. 쓰고 보니 오늘 커밋이 정말 많다. 다 연결된 작업들이 아니라 여기저기 흩어진 것들이어서 맥락 전환이 잦았다. 각 작업 자체는 어렵지 않아도, 이런 날이 깊이 파고드는 날보다 더 피로하다. 집중이 조각나는 하루였다.
내일은 GSC에서 사이트맵 처리 상태 확인부터 시작한다. 지금 당장은 "오류 없음" 상태만 볼 수 있고 크롤이 실제로 붙기 시작했는지는 며칠 단위로 봐야 함. 할 수 있는 건 다 해놨다.
댓글 0
첫 댓글 달아줘.