Skip to content
isdnetworks
Go back

먼저 읽는 쪽을 바꾸니 조회가 줄었다

동기화 배치가 오래 걸려서 product 8만 건을 도는 데 40분이 걸렸다. 그런데 나가는 SELECT 하나하나는 전부 빨랐다.

Table of contents

Open Table of contents

큰 쪽을 기준으로 돌고 있었다

코드는 이랬다.

$products = $this->db->get('product')->result();      // 8만 건

foreach ($products as $p) {
    $target = $this->db->where('product_no', $p->product_no)
                       ->get('sync_target')->row();
    if (!$target) continue;                            // 대상 아님
    $this->sync($p, $target);
}

product 전체를 훑으면서 각각이 동기화 대상인지 확인한다. SELECT 하나의 문제가 아니라 조회 횟수의 문제였다.

대상 표를 세어 봤다.

SELECT COUNT(*) FROM sync_target;
-- 1,240

sync_target 이 1,240건이다. 8만 번 조회해서 1,240건을 찾고 있었고 작은 쪽을 기준으로 돌면 1,240번이면 된다.

기준을 바꿨다

$targets = $this->db->get('sync_target')->result();   // 1,240건

foreach ($targets as $t) {
    $p = $this->db->where('product_no', $t->product_no)
                  ->get('product')->row();
    if (!$p) continue;
    $this->sync($p, $t);
}

sync_target 을 바깥으로 두니 40분이 40초가 됐다. 같은 결과인데 훑는 방향만 달라진 것이다.

조인으로 더 줄였다

1,240번도 줄일 수 있었다.

SELECT p.*, t.last_sync, t.channel
FROM sync_target t
JOIN product p ON p.product_no = t.product_no

한 번에 가져오니 40초가 3초가 됐다.

JOIN 이 항상 나은 것은 아니어서 가져올 컬럼이 많고 행이 많으면 메모리를 쓴다. 한쪽에 여러 건이 달리는 관계를 이으면 행 자체가 곱해져서 나온다. 여기서는 sync_target 이 1,240건이라 괜찮았다.

판단 기준 — 어느 쪽이 작은가

이 일 뒤에 foreach 를 만들 때 양쪽 개수를 먼저 확인했다.

SELECT COUNT(*) FROM product;      -- 82,041
SELECT COUNT(*) FROM sync_target;  -- 1,240

두 줄이면 어느 쪽을 기준으로 돌지 정해진다. 개수 차이가 크면 작은 쪽이 명확히 낫고 비슷하면 JOIN 을 본다.

전체 개수만 보면 안 되는 경우도 있었다.

$products = $this->db->where('status', 'normal')
                     ->where('update_date >=', $since)
                     ->get('product')->result();

조건이 붙으면 8만이 아니라 몇백 건일 수 있어서 조건을 넣고 세어 봤다.

SELECT COUNT(*) FROM product WHERE status='normal' AND update_date >= '2016-07-10';
-- 340

340건이면 product 쪽을 기준으로 도는 것이 낫다. sync_target 1,240건보다 적으니 실제 조건을 넣고 세야 맞는 판단이 나온다.

검증 — 조치마다 시간 재기

340건을 가져오는 SELECT 자체가 느리면 소용없다.

EXPLAIN SELECT * FROM product WHERE status='normal' AND update_date >= '2016-07-10';
type: ALL   rows: 82041

typeALL 이라 전체를 훑는데 update_date 에 인덱스가 없었다.

ALTER TABLE product ADD INDEX idx_update (update_date);

다시 확인했다.

type: range   key: idx_update   rows: 412

추정 412건이고 실제 340건과 비슷하다. 매뉴얼도 InnoDB 에서 이 숫자가 추정이라고 적고 있으니 실제 건수는 따로 세는 편이 맞다.

여러 곳을 고쳤으니 어디서 얼마나 줄었는지도 정리했다.

처음                    40분 12초
작은 쪽 기준으로 변경     0분 41초
조인으로 묶음            0분 03초
인덱스 추가(조건 조회)     0분 02초

첫 조치가 대부분을 줄였고 나머지는 소폭이다. 가장 큰 것을 먼저 찾으면 나머지는 안 해도 될 수 있다. 시간이 없으면 첫 번째만 해도 충분했다.

정리


Share this post on:

Previous Post
지울 수 없는 데이터를 다루기
Next Post
도메인을 옮겨 다녔다