검색 결과에 이미 삭제된 상품이 나왔다. 클릭하면 없는 페이지로 간다.
검색은 별도 색인에서 하고 상세는 MySQL 에서 읽는다. 두 저장소가 서로 다른 상태를 들고 있었다.
Table of contents
Open Table of contents
원인 — 사본이 따라가는 창
색인은 MySQL 의 사본이고 사본은 원본이 바뀔 때 따라가야 하는데 그 사이에 창이 있다.
DB 변경 → (이벤트 발행) → (큐) → (색인 갱신)
각 화살표에서 실패할 수 있는데 이벤트를 발행 안 하는 경로가 있거나 큐 소비가 실패하거나 갱신 중 오류가 난다.
대량 배치가 이벤트를 안 태우는 경우도 있는데 이번 건이 그랬고 일괄 삭제를 DELETE 로 해서 이벤트가 안 나갔다.
그런데 실패했다는 사실이 어디에도 안 남아 있어서 어긋난 것보다 모른 것이 더 큰 문제였다.
완전히 막을 수는 없다
경로를 다 잡아도 어긋나는데 색인 서버가 죽거나 망이 끊기거나 재색인 도중 배포가 나갈 수 있다.
비동기로 반영하면 그 사이에는 반드시 다르고 그 창을 0으로 만드는 것은 사실상 불가능하다.
그래서 목표 자체를 바꿔서 어긋나지 않게 한다보다 어긋난 것을 안다가 현실적이라고 봤다.
조치 — 넣은 세 가지
첫째는 상세 조회에서 한 번 더 확인하는 것이다.
$product = Product::find($id);
if (!$product || $product->deleted) {
Log::warning('stale index hit', ['id' => $id]);
return redirect()->route('list');
}
find 로 실제로 있는지 보고 없으면 목록으로 돌려보내며 Log::warning 으로 그 ID를 남긴다. 사용자에게는 깨진 화면 대신 목록이 보이고 우리는 어긋난 항목을 얻는다.
둘째는 주기적으로 대조하는 것인데 밤에 cron 배치로 양쪽 ID 집합을 비교한다.
DB에 있는데 색인에 없음 → 색인 누락
색인에 있는데 DB에 없음 → 색인 잔여
전체를 비교하면 무거우니 최근 변경된 것 위주로 하고 전체 대조는 주 1회로 뒀다.
셋째는 어긋난 건수를 COUNT(*) 로 남기는 것인데 평소 몇 건인지 알아야 늘어난 것을 안다.
평소가 0이면 1건도 이상이고 수십 건이면 그것이 정상 수준이라 같은 숫자가 다른 뜻이 된다.
판단 기준 — 어느 쪽이 정본인가
대조에서 차이를 발견했을 때 어느 쪽을 믿을지가 문제여서 원칙을 하나 정했다.
MySQL 이 정본이고 색인은 파생이라고 두면 조치가 자동으로 갈린다.
DB에 있는데 색인에 없음 → 색인에 넣는다
색인에 있는데 DB에 없음 → 색인에서 뺀다
색인 쪽 정보를 MySQL 로 옮기는 일은 없고 색인은 언제든 통째로 다시 만들 수 있어야 한다.
이 원칙이 없으면 매번 판단해야 하고 사람마다 다르게 처리해서 자료가 꼬인다.
전체 재색인을 준비해 뒀다
가장 확실한 복구는 처음부터 다시 만드는 것이라 그것이 가능한지를 미리 확인해 뒀다.
재색인에 얼마나 걸리나 — 실제로 재 봤다
그동안 검색이 되나 — 새 색인을 따로 만들고 다 되면 바꾼다
부하는 어느 정도인가
미리 해 보지 않으면 급할 때 못 하고 몇 시간 걸린다는 것을 장애 중에 알면 늦다.
새 색인을 만들고 별칭을 바꾸는 방식으로 하면 바꾸는 순간이 원자적이다. 검색이 안 되는 구간이 없다.
색인에 뭘 넣을지도 다시 봤다
이 일을 계기로 색인 내용도 봤다. 상품 정보 대부분이 들어가 있었다.
검색에 안 쓰는 필드가 많았다. 상세 설명 전문과 이미지 URL 목록과 옵션 상세가 그랬다.
이런 것을 넣으면 색인이 커지고 MySQL 이 바뀔 때마다 갱신할 필드가 늘어 어긋날 여지도 는다.
검색과 목록에 필요한 것만 남기고 뺐는데 상세는 어차피 MySQL 에서 읽으므로 둘 이유가 없었다.
색인에 있는 필드 수가 곧 어긋날 수 있는 지점의 수다. 줄이는 것 자체가 대조 대상을 줄이는 일이었다.
정리
- 색인과 DB는 언젠가 어긋나고 비동기 반영이면 그 사이에는 반드시 다르다
- 실패한 사실이 안 남는 것이 어긋난 것보다 큰 문제다
- 완전히 막기보다 어긋난 것을 아는 쪽이 현실적이다
- 상세에서
find로 한 번 더 보면 깨진 화면이 안 가고 기록이 남는다 - 최근 변경분은 자주 전체는 가끔 대조한다
- 어긋난 건수를 지표로 남겨야 평소와 다른 것을 안다
- DB가 정본이고 색인은 파생이라고 미리 정하면 조치가 자동으로 갈린다
- 전체 재색인을 미리 돌려 보고 소요 시간과 무중단 방법을 알아 둔다
- 별칭을 바꾸는 방식이면 바꾸는 순간이 원자적이다
- 색인 필드 수가 곧 어긋날 지점의 수이므로 검색에 안 쓰는 것은 빼 둔다