Skip to content
isdnetworks
Go back

지운 캐시가 그 캐시가 아니었다

(수정됨)

설정을 바꿨는데 화면이 안 바뀌어서 memcached 를 비웠는데 여전히 그대로였다.

한 번 더 비워도 그대로였는데 같은 조치를 두 번 해서 같은 결과가 나오면 그 조치가 안 맞는 것이다.

Table of contents

Open Table of contents

캐시가 몇 겹인지 세어 봤다

세 번째로 같은 것을 하는 대신 요청 경로를 따라가며 캐시를 전부 적어 봤다. memcached 하나만 보고 있었던 것이다.

브라우저 캐시
  → CDN 캐시
    → 웹서버 캐시 (정적 파일)
      → 애플리케이션 캐시 (파일/메모리)
        → ORM 쿼리 캐시
          → DB 쿼리 캐시

여섯 겹이었고 내가 지운 것은 네 번째였다. 한 요청이 화면까지 오는 사이에 값을 담아 두는 자리가 하나뿐이라는 보장이 없었다.

여섯 중 어느 것이 이 화면에 관여하는지는 세어 본 뒤에야 물을 수 있었다. 세기 전에는 memcached 하나만 머릿속에 있었다.

비운 것이 안 먹혔다는 사실만 두 번 확인한 셈인데 같은 명령을 또 치는 것보다 경로를 그리는 쪽이 빨랐다.

캐시를 지웠다는 말이 어느 캐시를 지웠다는 뜻인지가 정해져 있지 않았다. 여섯을 적어 놓고 나서야 내가 무엇을 안 지웠는지가 보였다.

판단 기준 — 어느 계층인지 가르는 법

각 계층은 다르게 확인한다.

브라우저    개발자 도구에서 캐시 끄고 새로고침 — 되면 브라우저다
CDN         응답 헤더의 X-Cache 를 본다. 원본 서버로 직접 요청해 대조한다
웹서버      설정을 보고 캐시 디렉터리를 확인한다
애플리케이션  캐시 저장소를 직접 조회한다. 키가 있는지 값이 뭔지
ORM·DB      대개 요청 단위라 오래 안 남는다

Cache-Control 헤더와 직접 요청으로 앞의 셋은 금방 갈린다. curl 로 원본 서버에 바로 쏴서 옛 값이 오면 앞쪽 셋은 아니다.

이번에도 그렇게 해서 앞의 셋을 지웠다. 남은 것은 애플리케이션 캐시와 프로세스 메모리 둘이었다.

각 계층을 확인하는 방법이 다르다는 것도 정리됐다. 헤더를 보는 것과 memcached 조회는 다른 작업이다.

방법을 모르면 그 계층이 의심 목록에서 그냥 빠지고 빠진 자리에 답이 있으면 못 찾는다.

원인 — 프로세스 메모리

memcached 는 제대로 비워져 있었다. 그런데 php-fpm 워커가 시작할 때 설정을 메모리에 올려 두고 있었다.

class Config {
    private static $cache = null;
    public static function get($key) {
        if (self::$cache === null) {
            self::$cache = json_decode(file_get_contents(CONFIG_FILE), true);
        }
        return self::$cache[$key] ?? null;
    }
}

self::$cache 가 한 번 채워지면 프로세스가 사는 동안 CONFIG_FILE 을 다시 안 읽는다. 파일을 바꿔도 이미 떠 있는 프로세스는 옛 값을 들고 있다.

지우는 명령으로는 절대 안 없어지는 종류였다. 워커를 재기동하니 그제야 반영됐다.

memcached 처럼 이름이 붙은 것만 캐시가 아니다. 한 번 읽고 안 버리는 static 변수도 같은 일을 한다.

self::$cache 에는 지우는 명령이 없고 프로세스를 끝내는 것이 유일한 방법이다. 이름이 없어 목록에도 안 올라간다.

전체 흐름 — 확인 순서

설정을 바꿨는데 반영이 안 된다는 말을 들으면 이 순서로 본다.

1. 값이 실제로 바뀌었나 — 파일이나 DB에서 직접 읽는다
2. 프로세스가 다시 읽나 — 시작할 때만 읽으면 재기동이 필요하다
3. 캐시 계층을 뒤에서부터 지운다
4. 지웠는지 확인한다 — 키를 조회해 없는지 본다

셋째의 방향이 중요했는데 앞에서부터 지우면 memcached 에 남은 옛 값이 올라와 다시 채워진다.

넷째는 여러 번 겪어서 넣었다. 삭제 명령이 다른 memcached 를 가리키거나 키 접두사가 달라 안 지워지기도 했다.

명령이 성공했다는 것과 지워졌다는 것은 다른 확인이라 memcached 에서 그 키를 다시 꺼내 봐야 갈린다.

재기동이 필요한지 알려주는 게 낫다

근본적으로는 설정을 바꾸면 프로세스가 알아채는 것이 맞다. 방법이 셋 있었다.

파일 수정 시각 확인   요청마다 stat 한 번. 바뀌었으면 다시 읽는다
TTL              60초마다 다시 읽는다. 즉시는 아니지만 재기동이 필요 없다
신호 수신         특정 시그널을 받으면 다시 읽는다. 재기동보다 가볍다

셋 중 TTL 쪽을 골랐는데 이유는 즉시성이 중요하지 않아서고 설정 변경이 최대 1분 뒤 반영된다는 것을 문서에 적었다.

적어 두지 않으면 다음 사람이 또 안 바뀐다고 한다. 이번에 내가 한 것과 똑같은 조사를 처음부터 다시 하게 된다.

반영이 늦는 것 자체는 문제가 아니고 얼마나 늦는지를 모르는 것이 문제였다.

한 줄을 적어 두면 바꾼 사람이 1분을 기다리는데 안 적어 두면 그 1분 동안 조사를 시작한다.

제약 — 캐시를 늘리기 전에

이 일 이후로 캐시를 추가할 때 하나를 더 묻는다. 이것을 어떻게 무효화하느냐다.

넣는 것은 쉽고 지우는 것이 어려운데 무효화 방법 없이 넣으면 나중에 반드시 겪는다.

키를 알면 지울 수 있나
관련된 것을 한꺼번에 지울 수 있나 (접두사 삭제)
최악의 경우 전체를 비울 수 있나

셋 다 안 되면 TTL 을 짧게 두는 쪽이 낫고 못 지우는 캐시는 언젠가 문제가 된다.

정리


Share this post on:

Previous Post
세지 않아도 되는 것을 세고 있었다
Next Post
평균이 감추는 것