상품을 ElasticSearch 2.x에 넣는 PHP 배치가 있었다. 조회하고 걸러 내고 변환하고 색인하는 네 단계로 돌았다.
Table of contents
Open Table of contents
증상 — 끝에만 숫자가 있었다
배치가 끝나고 로그에 남는 것은 마지막 줄 하나뿐이었다. indexed: 48,213 같은 형태였다.
색인된 수가 MySQL의 COUNT(*)보다 적을 때 어디서 빠졌는지 알 수 없었다. 걸러 낸 것인지 변환에서 죽은 것인지 색인이 거절한 것인지를 구분할 방법이 없었다.
가능한 방법들
어떻게 볼지를 두고 방법이 둘 보였다. 하나는 단계마다 들어온 수와 나간 수를 로그에 남기는 것이고, 다른 하나는 상품별 처리 상태를 테이블에 적어 두고 나중에 대사하는 것이었다.
MySQL 테이블 방식은 어느 상품이 어느 단계에서 빠졌는지까지 그대로 남길 수 있다. 대신 5만 건마다 쓰기가 생기고 테이블이 하나 늘어난다.
Monolog로 남기는 쪽은 붙이기 쉽고 배치에 주는 부하도 거의 없다. 대신 어느 상품인지는 모르고 몇 건인지만 안다.
고른 것과 이유
두 방법 중 로그 방식을 골라 먼저 붙여 보기로 했다. 지금 알고 싶은 것이 「어느 단계에서 새는가」이지 「어느 상품인가」가 아니었다.
CodeIgniter 배치가 매일 도는데 MySQL에 매번 5만 행을 쓰는 것도 걸렸다. 단계를 특정한 뒤에 필요하면 그때 상세를 붙이기로 했다.
검증 — 앞뒤 숫자를 맞춰 본다
PHP 배치의 각 단계에 들어온 수와 나간 수를 찍게 했다. 앞 단계의 나간 수와 뒤 단계의 들어온 수가 같아야 한다.
fetch in=52,410 out=52,410
filter in=52,410 out=50,102
transform in=50,102 out=49,980
index in=49,980 out=48,213
한 번 돌려 보니 transform 단계에서 122건, index 단계에서 1,767건이 사라지고 있었다. filter는 의도한 제외라 정상이었다.
두 곳 중 index 쪽에서 사라지는 양이 훨씬 컸다. _bulk 응답의 errors: true와 항목별 error를 안 보고 넘기고 있었다.
실패분의 식별자와 사유
건수만으로는 왜 실패했는지 몰라서 실패 항목의 상품 ID와 사유를 Guzzle 응답에서 뽑아 로그에 남겼다. 전체가 아니라 실패한 것만 남기므로 Monolog 파일 양이 크게 늘지 않았다.
사유를 모아 보니 대부분 mapper_parsing_exception이었다. price가 long 매핑이었는데 coerce가 기본으로 켜져 있어서 "12345" 같은 숫자 문자열은 그대로 통과하고, 쉼표가 든 값이나 빈 문자열처럼 숫자로 못 읽는 것만 걸리고 있었다.
정상일 때 각 단계의 숫자가 어떤 모양인지도 README에 적어 뒀다. 그것이 없으면 다음 사람이 48,213을 보고 정상인지 판단할 수 없다.
정리
- 끝 숫자 하나만으로는 어느 단계에서 새는지 모른다
- 단계별 건수 로그와 상품별 대사 테이블 두 방법이 있었다
- 테이블은 어느 상품인지까지 알지만 매일 5만 행을 쓴다
- 알고 싶은 것이 「어느 단계」였으므로 로그 쪽을 골랐다
- 앞 단계의 나간 수와 뒤 단계의 들어온 수를 맞춰 본다
- ElasticSearch bulk 응답의 실패 항목을 안 보면 그만큼 조용히 사라진다
long매핑에coerce가 켜져 있으면 숫자 문자열은 통과하고 못 읽는 값만 걸린다- 실패분만 식별자와 사유를 남기면 양이 적다
- 정상일 때의 숫자 모양을 함께 적어 둔다