정적 콘텐츠 사이트 일곱 개가 서브도메인마다 흩어져 있었습니다. 그중 넷은 예전에 apex 서브폴더로 옮겨 두고 서브도메인엔 301만 남겨 놨는데, 나머지 셋이 제각각이었습니다. 검색 권위를 한 도메인에 모으는 게 목적이니, 규칙 하나로 통일하는 30분짜리 작업이라고 봤습니다.
먼저 실제 상태부터 찍어 봤습니다.
herhormones -> 301 ootssu.com/herhormones/
toolkit -> 301 ootssu.com/toolkit/
mindshift -> 301 ootssu.com/mindshift/
longevitylab -> 301 ootssu.com/longevitylab/
koreahub -> 200 (서브도메인에서 직접 서빙)
foodclarity -> 200 (서브도메인에서 직접 서빙)
gme -> 301 ootssu.com/herhormones/ ← ?마지막 줄이 눈에 걸렸습니다. gme는 여성 건강 사이트와 아무 관계가 없는, 어떤 앱의 지원·법적고지 페이지입니다. 그게 전혀 다른 사이트로 리다이렉트되고 있었습니다.
원인은 공유 도크루트였다
서버를 열어 보니 gme 서브도메인이 herhormones와 같은 디렉터리를 도크루트로 쓰고 있었습니다. 그 디렉터리엔 리다이렉트 규칙 한 줄만 들어 있었고, 그 규칙이 호스트를 안 가리고 전부 /herhormones/로 보내고 있었습니다.
고치는 방법은 간단합니다. 호스트로 갈라 주면 됩니다.
RewriteCond %{HTTP_HOST} ^gme\. [NC]
RewriteRule ^(.*)$ https://ootssu.com/gme/$1 [R=301,L]
RewriteRule ^(.*)$ https://ootssu.com/herhormones/$1 [R=301,L]그리고 보낼 곳이 있어야 하니, 서버에 남아 있던 백업 폴더에서 그 앱의 지원 페이지 다섯 장을 꺼내 /gme/에 복원했습니다. 상대경로로 걸린 스타일시트 참조를 apex 절대경로로 고치고, 배포하고, 확인했습니다. 리다이렉트 정상, 페이지 정상.
여기까지가 제가 만족스러워하던 지점입니다.
리뷰에서 걸린 것
작업을 마무리하며 복원한 페이지들의 메타 태그를 훑었습니다. 그중 한 장에 이런 게 있었습니다.
<link rel="canonical" href="https://ootssu.com/hiddengem/terms.html" />/hiddengem/. 제가 방금 만든 /gme/가 아니라.
서버를 확인했습니다. 있었습니다. 같은 다섯 장이, 이미.
hiddengem 내가 만든 gme
index.html 27,531 B 1,086 B
privacy.html 79,397 B 2,962 B
support.html 72,257 B 3,162 B
terms.html 69,617 B 4,960 B정본은 이미 apex의 다른 경로에 살아 있었고, 훨씬 크고 최신이었습니다. 제가 백업에서 꺼낸 건 그 이전 세대의 파일이었습니다. 중복을 없애러 들어가서, 25배 작은 구버전 중복을 하나 더 만들어 배포한 것입니다.
그것도 정확히 제가 문제 삼던 종류의 중복 — 같은 내용이 두 URL에서 서빙되면서 서로 순위를 갉아먹는 그 구조를요.
되돌리는 건 1분이었습니다. /gme/를 지우고, 리다이렉트 목적지를 /hiddengem/으로 바꿨습니다.
# 정본은 이미 apex /hiddengem/ 에 있으므로 그쪽으로 보낸다(사본 만들지 말 것).
RewriteCond %{HTTP_HOST} ^gme\. [NC]
RewriteRule ^(.*)$ https://ootssu.com/hiddengem/$1 [R=301,L]당신이라면?
여기서 여쭙고 싶습니다. 백업에서 뭔가를 복원하기 직전, 당신은 "이게 이미 어딘가에 살아 있지 않은지" 먼저 확인합니까?
저는 안 했습니다. 백업 폴더를 발견했고, 서브도메인이 잘못된 곳을 가리키고 있었고, 둘을 연결하면 문제가 풀리는 그림이 너무 자연스러웠습니다. 그 자연스러움이 확인 단계를 통째로 건너뛰게 만들었습니다.
제가 이걸 잡을 수 있었던 유일한 이유는, 작업을 끝내고 나서 결과물의 메타 태그를 한 줄씩 읽었기 때문입니다. canonical 태그가 자기 위치와 다른 곳을 가리키고 있다는 건, 그 파일이 원래 다른 데 살았다는 뜻입니다. 파일이 제 출신지를 자백한 셈입니다.
덤으로 밟은 것: 셸이 안 쪼개 준다
같은 작업 중에 남은 두 사이트를 로컬로 내려받으려고 이런 걸 돌렸습니다.
for pair in "koreahub website_2f37e00c" "foodclarity website_7273fd9e"; do
set -- $pair; n=$1; d=$2
rsync ... :~/public_html/$d/ ./$n/
donebash라면 동작합니다. zsh는 변수를 단어로 쪼개지 않습니다. $1에 "koreahub website_2f37e00c" 전체가 들어갔고, $2는 비었습니다. 결과적으로 공백이 들어간 디렉터리가 생기고, 원격 경로가 비면서 홈 디렉터리 전체를 끌어왔습니다. 70MB짜리 쓰레기 디렉터리 두 개.
그리고 그게 커밋까지 들어갔습니다. git show --stat이 1,591 files changed, 220,446 insertions를 뱉는 걸 보고서야 알았습니다. 지우고 amend해서 55파일 2,136줄로 정리했습니다.
이것도 앞의 실수와 같은 모양입니다. 명령이 성공을 반환했고, 결과물을 안 봤습니다. rsync는 정상 종료했습니다. 잘못된 일을 성공적으로 했을 뿐입니다.
자가진단 3개
- 복원 전에 정본을 찾아봤습니까? 백업이 존재한다는 건 원본이 없다는 증거가 아닙니다. 옮겨졌을 뿐일 수 있습니다.
- 파일의 canonical·메타가 지금 위치와 일치합니까? 불일치는 그 파일이 다른 곳에서 왔다는 신호입니다.
- 결과물의 크기를 확인했습니까? 1,591 파일과 55 파일의 차이는 커밋 통계 한 줄이면 보입니다.
솔직한 부분
최종 결과 자체는 깔끔합니다. 일곱 개 서브도메인이 전부 정확한 apex 경로로 301되고, 두 사이트는 링크·canonical·사이트맵까지 재작성돼 옮겨졌고, 빠져 있던 사이트맵 세 개도 robots에 등록했습니다.
그런데 그 30분짜리 작업 안에서 제가 만든 문제가 두 개였습니다. 하나는 배포까지 나갔고, 하나는 커밋까지 들어갔습니다. 둘 다 작업 후 리뷰에서만 잡혔습니다.
정리 작업이 위험한 이유가 여기 있는 것 같습니다. 목표가 "중복 제거"처럼 명확할수록, 자기가 방금 만든 중복은 안 보입니다.
같은 날 API가 성공을 반환하고 값을 버린 이야기는 API가 200을 돌려주고 제 값을 버렸습니다에 따로 적었습니다.
지금 서버에 백업 폴더 하나 있으시죠. 그 내용이 이미 라이브 어딘가에 더 새 버전으로 살아 있지 않은지, 복원 버튼 누르기 전에 한 번만 확인해 보세요.