캐시 용도로 memcached 를 하나 넣었다. 얼마 뒤 열어 보니 거기에 세션과 임시 파일 목록까지 들어가 있었다.
Table of contents
Open Table of contents
증상 — 다섯 가지가 섞여 있었다
무엇이 담겨 있는지부터 봤다.
$ echo "stats items" | nc cache-01 11211
STAT items:1:number 4821
키를 살펴봤다.
cache:product:1042
sess_a8f2c1...
tmpfiles:20140217
lock:batch:stat_daily
counter:visit:20140217
다섯 가지 성격이 섞여 있었고 없어졌을 때 벌어지는 일이 전부 달랐다.
캐시 없어져도 된다. 다시 만들면 된다
세션 없어지면 로그아웃된다
임시 목록 없어지면 파일이 안 지워진다
잠금 없어지면 배치가 두 번 돈다
카운터 없어지면 숫자가 틀린다
memcached 는 자리가 모자라면 LRU 로 오래된 것부터 버린다. 캐시에는 맞는 동작이고 나머지에는 맞지 않는 동작이다. 없어져도 되는 것과 안 되는 것을 같은 자리에 둔 것이 문제였다.
실제 폐기 건수
실제로 버려지는지 숫자로 봤다.
$ echo "stats" | nc cache-01 11211 | grep evict
STAT evictions 18422
evictions 는 만료된 것이 아니라 아직 유효한 항목을 자리 확보로 지운 수다. 만 팔천 건 중에 세션도 섞여 있었을 것이다. 가끔 로그아웃된다는 문의가 있었는데 원인이 여기였다.
성격이 다른 것을 섞으면 한쪽에 맞는 동작이 다른 쪽을 깬다. 그것도 오류 없이 조용히 깬다. 그래서 문의가 오기 전까지 아무도 몰랐다.
없어지면 안 되는 것을 옮겼다
어디에 무엇을 둘지 정했다.
캐시 memcached
세션 DB 테이블
임시 목록 DB 테이블
잠금 DB 테이블 (유일 인덱스)
카운터 DB 테이블 (배치로 집계)
MySQL 이 느릴 것이 걱정돼 먼저 재 봤다.
세션 읽기 memcached 0.4ms DB 1.2ms
0.8밀리초 차이였다. 요청 하나에 한 번 읽는 것이라 문제가 되지 않았다.
잠금은 옮기면서 방식도 바꿨다.
CREATE TABLE batch_lock (
name varchar(64) NOT NULL,
acquired_at datetime NOT NULL,
PRIMARY KEY (name)
);
try {
$db->insert('batch_lock', ['name' => 'stat_daily', 'acquired_at' => date('Y-m-d H:i:s')]);
} catch (PDOException $e) {
exit('이미 돌고 있습니다');
}
name 이 기본 키라 겹치면 INSERT 가 실패하고 그게 잠금이 된다. 끝나면 지우는데 중간에 죽으면 안 지워지므로 오래된 것을 정리하는 것도 뒀다.
DELETE FROM batch_lock WHERE acquired_at < NOW() - INTERVAL 2 HOUR;
느릴 것 같으면 재 보는 것이 걱정만 하는 것보다 빨랐다.
남은 캐시에 규칙을 붙였다
캐시만 남기고 나서 담는 규칙도 적었다.
- 다시 만들 수 있는 것만 담는다
- 키에 무엇이 들어가는지 정한다 (조회 조건이 다 들어가야 한다)
- 만료 시간을 반드시 준다
- 담기 전에 크기를 본다 (1MB 를 넘는 것은 안 담는다)
TTL 없이 담은 것이 있어서 자료가 바뀌어도 옛것이 계속 나가고 있었다.
키 이름도 cache: 로 시작하게 했다.
$ echo "stats cachedump 1 100" | nc cache-01 11211 | grep -v "^ITEM cache:"
가끔 돌려서 다른 것이 들어왔는지 봤다. 옮긴 뒤 다시 stats 를 봤다.
$ echo "stats" | nc cache-01 11211 | grep -E "evict|curr_items"
STAT curr_items 2104
STAT evictions 3821
담긴 개수가 4,821에서 2,104로 줄었다. 버려지는 것은 여전히 있었지만 전부 캐시라 상관이 없었고 로그아웃 문의도 사라졌다. 원인을 고쳤다는 것은 그 증상이 없어진 것으로 확인했다.
정리
- 성격이 다른 것을 같은 자리에 두면 한쪽 동작이 다른 쪽을 깬다
LRU로 버리는 저장소에 잃으면 안 되는 것을 두지 않는다- 조용히 깨지므로 문의가 오기 전까지 모른다
evictions로 실제 버려진 수를 확인한다- 느릴 것 같으면 재 본다. 차이가 문제가 안 되는 경우가 있다
- 무엇을 어디에 둘지 정해서 적는다
- 잠금은 기본 키 충돌로 만들고 남은 것을 정리하는 것도 같이 둔다
- 캐시에는
TTL을 반드시 준다 - 키 이름 규칙이 있으면 섞인 것이 보인다