Skip to content
isdnetworks
Go back

조건이 겹쳐서 진단이 어려웠다

한 달에 두세 번 나는 오류가 있었는데 어떻게 해도 시험 환경에서 재현이 되지 않았다.

Table of contents

Open Table of contents

공통점 수집

재현이 안 되니 그동안 난 것들의 공통점부터 찾기로 했다. 넉 달치 발생 시각을 늘어놓고 보니 하루 중 두 시간대에 몰려 있었다.

09~10시   2건
14~15시   2건

그 시간대에 무엇이 도는지 확인하니 정산 배치와 색인 재생성이었고 둘 다 부하가 큰 배치였다. 드물게 나는 것은 발생 시각만 모아 놔도 후보가 좁혀진다.

세 조건의 겹침

발생 건들을 더 들여다보니 조건이 셋이었다. 무거운 배치가 돌아 DB에 부하가 있고 그 시간에 대량 주문이 들어오며 옵션이 많은 특정 상품이 그 주문에 포함되는 것이다.

셋이 겹쳐야 났고 하나나 둘만으로는 나지 않았다. 그래서 그동안 재현이 안 됐던 것이다. 조건이 셋이면 하나씩 시험하는 방식으로는 영원히 안 나온다.

인위적 재현

시험 환경에서 세 조건을 동시에 만들었다. 무거운 배치를 돌리면서 동시 주문을 보내고 옵션이 많은 상품을 포함시켰더니 잠금 대기 시간 초과가 그대로 재현됐다.

왜 그 조합에서만 나는지도 확인했다. 배치가 상품 테이블을 오래 잠그는 동안 주문이 그 테이블을 읽어야 하는데 옵션이 많으면 읽는 행이 많아져 대기가 길어진다. 셋이 겹치면 대기가 제한 시간을 넘고 옵션이 적은 상품은 대기가 짧아 그대로 통과한다.

조건을 빼 보며 고칠 자리 찾기

재현한 뒤에 정말 셋이 다 필요한지를 하나씩 빼면서 확인했다. 셋 다 있을 때만 재현됐고 어느 하나를 빼도 나지 않았다.

빼 보는 과정에서 어느 조건이 핵심인지가 갈렸는데 배치를 뺐을 때 안 난다는 것이 배치 쪽이 핵심이라는 뜻이었다. 고치는 방법으로 배치가 잠그는 범위를 줄이는 것과 주문 쪽 대기 시간을 늘리는 것과 배치 시간을 옮기는 것 셋이 있었는데 첫 번째가 근본이고 나머지는 미루거나 옮기는 것이었다.

foreach ($this->chunks() as $c) {
    DB::transaction(fn() => $this->reindexChunk($c));
}

전체를 한 트랜잭션으로 묶던 것을 여러 덩어리로 나누니 한 번에 잠그는 시간이 짧아졌다. 재현되고 나서 조건을 빼 보는 것이 고칠 자리를 정해 줬다. 재현 조건은 시험으로도 남겼다.

날 때 남기는 상태

이번에는 발생 건 넷에서 공통점을 찾아냈지만 못 찾는 경우도 얼마든지 있을 수 있다. 그래서 다음을 위해 오류가 날 때 주변 상태를 함께 남기도록 고쳤다.

catch (QueryException $e) {
    Log::error('쿼리 실패', [
        'sql' => $e->getSql(),
        'running_batches' => $this->runningBatches(),
        'db_connections' => DB::selectOne("SHOW STATUS LIKE 'Threads_connected'"),
        'lock_waits' => DB::select("SELECT * FROM performance_schema.data_locks LIMIT 10"),
    ]);
    throw $e;
}

이러면 한 번만 나도 그때 상황을 알 수 있어 여러 번 기다릴 필요가 없다. 드물게 나는 것일수록 날 때 많이 남겨야 한다. 다음 발생까지 한 달을 기다리는 비용이 크기 때문이다.

안 나는 것의 확인 기간

고치고 나서 오류가 안 났는데 원래 한 달에 두세 번 나던 것이라 안 나는 것이 우연일 수도 있었다. 그 오류를 세는 지표를 두고 0이 유지되는지 봤다.

2026-06  4건
2026-07  3건
2026-08  0건
2026-09  0건
2026-10  0건

석 달치 지표가 0으로 유지되는 것을 보고 나서 고쳐진 것으로 판단했다. 한 달만 보고 끝냈으면 우연히 안 난 것과 구분되지 않았을 것이다. 드물게 나는 것은 안 나게 됐다는 확인에도 그만큼 시간이 걸린다.

정리


Share this post on:

Previous Post
전부를 멈추는 한 건
Next Post
중지했는데 팔린다