Skip to content
isdnetworks
Go back

계산값이던 화면의 날짜

관리 화면 목록에 기간 검색을 추가해 달라는 요청을 받았다. 검색할 날짜는 화면에 이미 보이는 값이라 간단해 보였다.

Table of contents

Open Table of contents

상황 — 컬럼이 없었다

order_returns 부터 봤다.

DESC order_returns;

요청일에 해당하는 컬럼이 없었다.

비슷한 이름이 하나 있었다.

claim_reward_hold_at

claim_reward_hold_at 의 값을 보니 화면의 요청일과 다른 승인일이었다.

이름이 비슷하다고 같은 값으로 보고 조건에 넣었으면 조회는 되는데 결과가 틀린다. 오류가 안 나므로 그 틀림이 오래 남았을 것이다.

원인 — 계산으로 붙는 속성

화면이 어디서 값을 받는지 모델을 봤다.

protected $appends = ['return_hold_request_date'];

$appends 에 든 return_hold_request_date 는 컬럼이 아니라 계산해서 붙이는 속성이었다.

계산자를 찾아 읽었다.

public function getReturnHoldRequestDateAttribute()
{
    return $this->order->histories()
        ->where('log', 'LIKE', '%보상/보류 요청%')
        ->value('created_at');
}

histories 에서 특정 log 패턴을 찾아 그 created_at 을 쓴다.

컬럼이 없다고 그런 값이 없다고 끝냈으면 요청 자체를 반려했을 것이다. 화면에 보이는 이상 어디선가는 오고 있으니 그 출처를 찾아야 했다.

제약 — 정렬도 조건도 못 쓰는 값

계산 속성은 조회 결과를 받은 뒤에 만들어진다.

ORDER BY return_hold_request_date    -- 없는 컬럼
WHERE return_hold_request_date >= ?  -- 안 됨

MySQL 쪽에서는 그 이름의 존재를 모르므로 ORDER BYWHERE 도 안 된다.

함께 요청받은 정렬 기능도 같은 이유로 불가능했다. 화면에 보인다는 것과 검색할 수 있다는 것이 서로 다른 문제라는 것을 먼저 설명해야 했다.

보이는 값의 출처가 그 값으로 무엇을 할 수 있는지를 정하고 있었다.

조치 — 원본을 조인한다

기간 검색을 하려면 계산의 원본을 조인해야 한다.

$query->whereHas('order.histories', function ($q) use ($from, $to) {
    $q->where('log', 'LIKE', '%보상/보류 요청%')
      ->whereBetween('created_at', [$from, $to]);
});

whereHashistories 에 조건을 직접 건다.

계산자가 하던 일을 조회 쪽으로 옮긴 셈이고 보상/보류 요청 이라는 문자열이 바뀌면 양쪽을 함께 고쳐야 한다.

검증 — 실행 계획

이력 표가 아주 커서 걱정이 됐다.

histories 는 폴리모픽이라 온갖 이력이 다 들어 있고 거기에 LIKE 를 걸면 느릴 것으로 보였다.

복합 인덱스 (historiable_id, historiable_type) 사용
type: ref

type: ref 로 인덱스를 타고 있었다.

historiable_id 로 먼저 좁히고 그 안에서 log 패턴을 거른다. 선행 컬럼으로 좁힌 뒤의 부분 일치는 남은 행 수가 적어서 부담이 안 됐다.

크다는 인상만으로 포기하지 않고 계획을 본 것이 이 판단의 근거였다. 봤더니 정상이라 다른 설계로 돌아갈 이유가 없었다.

변경 내용 — 허용 목록과 분기

기간 검색은 허용 목록 방식이었다.

$allowedPeriodFields = ['created_at', 'completed_at', ...];

allowedPeriodFields 에 넣어야 조건으로 받아들인다.

created_at 쪽은 컬럼 조건인데 이것만 조인이라 분기가 하나 생긴다.

if ($field === 'return_hold_request_date') {
    // 이력 조인 경로
} else {
    // 컬럼 조건 경로
}

허용 목록은 임의 조건이 들어오는 것을 막아 주는 대신 예외가 하나씩 는다.

이 구조가 왜 나왔는지도 짐작이 된다. 처음에는 요청일이 필요 없어 승인일만 기록했고 나중에 요청일을 보게 됐는데 컬럼을 더해도 지나간 자료를 채울 수 없었다.

컬럼을 더하고 이력에서 채우는 길도 있다. 그러면 정렬과 검색이 정상이 되는데 이력이 없는 오래된 건은 여전히 비고 이후로는 두 곳을 같이 관리하게 된다.

이번 요청의 범위를 넘는 일이라 별도 제안으로 적어 뒀다.

정리


Share this post on:

Previous Post
캐시가 개발과 운영에서 반대로 굴었다
Next Post
한 상대에 인증 체계 셋