관리자가 PHPExcel 로 읽는 표 파일로 상품을 한 번에 올리는 기능이 있었다. 수백 건을 올렸는데 중간에 죽었고 확인해 보니 절반쯤 들어가 있었다.
Table of contents
Open Table of contents
증상 — 앞의 것은 남고 멈췄다
로그를 보니 한 행에서 오류가 났다.
가격 컬럼에 문자가 들어 있음
코드는 이랬다.
foreach ($rows as $row) {
$model->insert($row); // 여기서 오류
}
$model->insert() 가 오류를 내면 거기서 끝인데 앞의 200건은 이미 들어간 뒤다. 되돌아가지 않는다.
고쳐서 다시 올리니 앞의 200건이 또 들어갔다. 그래서 관리자가 앞의 것을 손으로 지우고 다시 올리고 있었고 올리다 실패할 때마다 그 작업이 반복됐다.
트랜잭션으로 묶었다
먼저 중간에 죽으면 되돌아가게 했다.
$this->db->trans_start();
foreach ($rows as $row) { $model->insert($row); }
$this->db->trans_complete();
trans_start 와 trans_complete 사이에서 하나라도 실패하면 전부 안 들어간다. 그 전에 대상 테이블이 InnoDB 인지부터 봤는데 MyISAM 이면 묶어도 안 되돌아간다.
전부 들어가거나 아무것도 안 들어가는 두 상태만 남고 중간 상태가 없어졌다. 앞의 것을 손으로 지우는 일도 그것으로 없어졌다.
그런데 불편이 남았다. 한 번 올리면 한 오류만 알려 주고 그것을 고쳐 다시 올리면 다음 오류가 나온다. 파일에 오류가 열 개면 열 번을 왕복한다.
넣기 전에 전부 검사했다
저장하기 전에 전체를 먼저 검사하게 했다.
$errors = [];
foreach ($rows as $i => $row) {
$e = validate($row);
if ($e) $errors[$i + 2] = $e; // 엑셀 행 번호
}
if ($errors) { 화면에 표시; return; }
validate 가 돌려준 것을 $errors 에 모아 한 번에 보여 준다.
3행: 가격이 숫자가 아닙니다
17행: 카테고리를 찾을 수 없습니다
42행: 상품코드가 중복입니다
$errors 의 키가 곧 엑셀 행 번호라 바로 그 행을 열어 고친다.
행 번호를 맞추는 데 걸린 것이 있었다. 배열은 0부터 시작하고 엑셀은 1부터인데 첫 줄이 머리글이라 +2 를 해야 맞았다. 이 보정을 안 하면 관리자가 엉뚱한 줄을 고친다.
고치고 다시 올리는 왕복이 한 번으로 줄었다.
중복과 큰 파일과 결과 건수
검사에 중복도 넣었다. 파일 안에서 같은 상품코드가 두 번 나오는 것이 있었다.
$seen = [];
foreach ($rows as $i => $row) {
if (isset($seen[$row['code']])) { 중복 오류; }
$seen[$row['code']] = true;
}
이미 MySQL 에 있는 코드인지도 봤다.
$existing = $model->get_codes_in($codes);
get_codes_in 으로 한 번에 조회해서 대조했다. 한 건씩 조회하면 수백 번이 된다. 파일 안에서만 보면 UNIQUE 에 걸릴 것을 못 잡으니 두 가지를 다 봐야 저장 단계에서 안 걸린다.
파일이 크면 전부 읽어 두는 방식이 memory_limit 에 걸렸다. 한 행씩 읽는 방식으로 바꾸니 검사와 저장에서 파일을 두 번 읽게 됐다. 두 번 읽는 비용보다 왕복이 사라진 이득이 크다고 봤다.
끝나면 결과를 보여줬다.
성공 342건
실패 0건
소요 12초
특히 건수였다. PHPExcel 이 읽은 행 수와 실제 저장 건수를 나란히 두면 올린 파일과 대조할 수 있다.
실제로 안 맞은 적이 있었다. 엑셀은 450행인데 처리는 342건이었고 빈 행이 섞여 있어 건너뛴 것이었다. 건수를 안 보여줬으면 몰랐다.
정리
- 반복 중에 오류가 나면 앞의 것은 들어간 채로 멈춘다
- 다시 올리면 중복이 된다
InnoDB에서 트랜잭션으로 묶으면 전부 아니면 아무것도가 된다- 그래도 오류를 하나씩 찾는 왕복은 남는다
- 넣기 전에 전부 검사해 한 번에 보여 준다
- 행 번호를 붙이되 머리글과 시작 번호 때문에 보정이 필요하다
- 파일 안 중복과
MySQL에 있는 것과의 중복을 둘 다 본다 - 대조는 한 번에 조회해서 한다. 한 건씩 하면 수백 번이다
- 큰 파일은
memory_limit에 걸리므로 한 행씩 읽는다 - 결과에 건수를 넣으면 올린 파일과 대조된다