지금까지의 캐시는 전부 PHP 안에서 동작했습니다. 페이지 캐시는 다릅니다 — 완성된 HTML 응답을 통째로 보관했다가 다음 방문자에게 그대로 돌려줍니다. 워드프레스를 부팅하지도, DB 에 연결하지도 않습니다.
그래서 이 계층은 PHP 앞에 있을 때만 제값을 합니다. nginx 의 FastCGI 캐시나 앞단의 CDN 이 그 자리입니다. 플러그인으로 만든 페이지 캐시는 이미 PHP 를 부팅하고 플러그인을 로드한 뒤에 응답하므로, 아낄 수 있는 것의 상당 부분을 이미 써 버린 상태입니다.
두 겹으로 쌓으면 생기는 일
서버에 이미 페이지 캐시가 있는데 플러그인 캐시를 추가하면, 같은 응답에 수명이 다른 사본이 두 개 생깁니다. 글을 발행하면 플러그인은 자기 사본을 지우지만 서버 캐시는 자기 만료까지 옛 HTML 을 계속 돌려줍니다.
증상은 늘 같은 모양입니다. 관리자에게는 새 화면, 방문자에게는 옛 화면. 관리자는 로그인 상태라 캐시를 우회하고 있으니 “내 화면은 멀쩡한데” 가 되고, 원인을 코드에서 찾기 시작하면 며칠이 갑니다.
어느 층이 옛것을 쥐고 있는지는 헤더로 가릅니다. 서버 캐시가 자기 상태를 헤더로 내보내게 해 두면 확인이 1초입니다.
curl -sI https://example.com/ | grep -i '^x-cache'
# HIT → 서버 캐시가 응답 중
# MISS → PHP 까지 갔다는 뜻
우회 조건을 반드시 명시합니다
페이지 캐시의 사고는 대부분 “캐시하면 안 되는 것을 캐시해서” 생깁니다. 최소한 네 가지는 반드시 우회시킵니다.
set $skip 0;
if ( $request_method = POST ) { set $skip 1; }
if ( $http_cookie ~* "wordpress_logged_in" ) { set $skip 1; }
if ( $request_uri ~* "^/wp-admin/|^/wp-json/acme-payments/" ) { set $skip 1; }
fastcgi_cache_bypass $skip;
fastcgi_no_cache $skip;
add_header X-Cache $upstream_cache_status always;
세 번째 줄의 결제 · 웹훅 라우트가 특히 중요합니다. 외부 서버가 호출하는 웹훅 응답이 캐시되면 결제 상태가 갱신되지 않고, 그 사실은 주문이 밀리고 나서야 드러납니다. 로그인 쿠키 조건도 마찬가지로 필수입니다 — 빠뜨리면 한 사용자의 개인 화면이 다른 사람에게 그대로 나갈 수 있습니다.
무효화 규칙은 좁게 시작합니다
글을 하나 고칠 때마다 전체 캐시를 비우면 캐시가 있는 의미가 옅어집니다. 반대로 너무 좁게 지우면 목록 화면에 옛 카드가 남습니다. 실용적인 출발점은 바뀐 글 · 그 글이 속한 아카이브 · 홈 세 곳을 지우는 것이고, 여기서 부족한 경우가 나타날 때 범위를 넓힙니다.
검증 방법도 정해 둡니다. 발행 직후 같은 URL 을 로그인하지 않은 상태로 두 번 요청해, 첫 요청이 새 내용이고 두 번째가 캐시 히트인지 봅니다. 이 확인 없이 넘어가면 무효화 규칙이 실제로 도는지 아무도 모르는 채로 운영하게 됩니다.
서버 캐시 구성과 하드닝은 서버 · 인프라 아카이브에서 더 다루고, nginx · Redis 구성을 포함한 작업 범위는 최적화 지원 사업에 정리돼 있습니다.
다음 회차
캐시를 다 쌓았는데도 모든 요청이 조금씩 무거운 경우가 있습니다. 다음 회차는 autoload 옵션 — 매 요청에 실려 나가는 데이터를 찾아내는 방법입니다.