Skip to content
isdnetworks
Go back

상태 변경 하나가 만든 수만 건

(수정됨)

특정 판매자의 상품 1만 2천 건을 전부 SUSPENDED 로 바꾸는 artisan 명령을 만들어 돌렸다. 상태는 잘 바뀌었는데 channel_jobs 에 12만 건이 쌓였다.

Table of contents

Open Table of contents

원인 — 저장마다 도는 관찰자

명령은 대상을 chunk(500) 으로 나눠 돌면서 상태를 바꾸고 save() 하는 형태였다. ProductObserver 의 updated 가 상태 변경을 감지하면 연결된 채널 수만큼 UPDATE_PRODUCT 작업을 만들고 있었다.

상품 하나당 연결 채널이 평균 열 개이므로 곱하면 12만 건이 된다. 화면에서 한 건을 바꿀 때는 맞는 동작인데 그 설계가 대량 처리에서도 그대로 돌면서 건수만큼 반복된 것이다.

배경 — 만들어진 것과 필요한 것

이 작업에서 필요한 것은 각 채널에 SUSPEND 를 알리는 것이었다. 관찰자가 만든 것은 UPDATE_PRODUCT 였다.

유형도 다르고 갱신 작업은 상품 정보를 다시 밀어 넣으므로 판매중지 의도와 맞지도 않았다. 정리하면 필요 없는 것이 대량으로 생기고 필요한 것은 안 생긴 상태였다.

조치 — 조용히 저장하고 직접 만들기

두 가지 방법이 있었는데 건별로 saveQuietly() 를 쓰는 것과 쿼리 빌더의 update() 한 문장으로 바꾸는 것이다. 뒤쪽을 골랐다. 이유는 Laravel 공식 문서대로 대량 갱신에서는 모델을 조회하지 않아 saved 와 updated 이벤트가 아예 안 나가고 1만 2천 번의 저장이 한 문장이 돼서다. 대신 이벤트가 하던 일을 전부 직접 해야 하는 것을 감수했다.

필요한 작업은 조건을 걸어 INSERT ... SELECT 한 문장으로 만들었다. channel_product_id IS NOT NULL 인 것만 대상으로 했는데 채널 식별자가 없으면 상대에 등록된 적이 없어 중지할 대상 자체가 없기 때문이다.

주의 — 조용히 사라지는 이력

관찰자를 건너뛰면 이력도 안 남는데 이것이 조용한 손실이라 더 위험하다. 나중에 누가 언제 이것을 내렸는지에 답할 수 없게 된다.

그래서 product_histories 를 직접 삽입하되 admin_id 는 NULL 로 비워 뒀다. 실제로 사람이 한 것이 아닌데 특정 담당자 번호를 넣으면 감사 기록이 왜곡되기 때문이다.

검증 — 배치를 만들 때 보는 것

이 일 뒤로 대량 처리 명령을 만들 때 두 가지를 먼저 본다. 모델에 관찰자와 booted() 와 이벤트 리스너와 트레이트 중 무엇이 붙어 있는지를 하나씩 열어 저장 시 무엇이 일어나는지를 확인한다.

그리고 건별 저장을 한 문장으로 바꿀 수 있는지를 보는데 바꿀 수 있으면 모델 이벤트 문제가 함께 사라진다. 대신 이벤트가 하던 일 중 필요한 것을 관찰자 코드를 그대로 읽어 목록으로 만들어야 하고 기억으로 옮기면 빠진다. 실제로 처음에는 이력 삽입을 빠뜨렸다가 리뷰에서 지적받았다.

정리


Share this post on:

Previous Post
원인을 확정한 시각 뭉침
Next Post
원인이 아니었던 회귀