Skip to content
isdnetworks
Go back

검색 결과와 DB가 다를 때

검색 결과에 이미 삭제된 상품이 나왔다. 클릭하면 없는 페이지로 간다.

검색은 별도 색인에서 하고 상세는 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 에서 읽으므로 둘 이유가 없었다.

색인에 있는 필드 수가 곧 어긋날 수 있는 지점의 수다. 줄이는 것 자체가 대조 대상을 줄이는 일이었다.

정리


Share this post on:

Previous Post
평균이 감추는 것
Next Post
복사한 블록은 갈라진다