포스트

조건 없는 검색은 전체인가 빈 값인가

검색어가 비었을 때 전체를 다 긁는 위험과, 기본 스코프·필수 조건·상한으로 자신을 보호하는 검색 설계를 다룬다.

조건 없는 검색은 전체인가 빈 값인가

검색 기능을 만들 때 가장 먼저 결정해야 하는 것은 “조건이 하나도 없을 때 무엇을 반환할 것인가”다. 사소해 보이지만 이 한 줄의 정책이 데이터가 백만 건으로 불어난 어느 날 서비스를 멈춘다.

빈 검색어의 두 가지 해석

검색어가 비어 있을 때 시스템은 두 갈래로 갈린다.

  1. 전체 반환(match-all): 조건이 없으니 모든 행을 돌려준다. 직관적이지만 위험하다.
  2. 빈 결과 또는 거부: 조건이 없으면 아무것도 주지 않거나 입력을 요구한다.

문제는 동적 쿼리에서 이 분기가 암묵적으로 결정된다는 점이다. 흔한 패턴을 보자.

1
2
3
4
5
6
7
8
9
10
11
<select id="searchUsers" resultType="User">
  SELECT id, user_name, email FROM users
  <where>
    <if test="keyword != null and keyword != ''">
      AND user_name LIKE CONCAT('%', #{keyword}, '%')
    </if>
    <if test="status != null">
      AND status = #{status}
    </if>
  </where>
</select>

모든 <if>가 거짓이면 <where>는 비고, 쿼리는 SELECT ... FROM users가 된다. 즉 조건 없는 검색이 자동으로 전체 풀스캔이 된다. 개발 환경의 수십 건에서는 멀쩡하다가, 운영에서 테이블이 커지면 한 번의 빈 검색이 DB를 마비시킨다.

왜 위험한가 — 내부 메커니즘

옵티마이저 입장에서 WHERE 절이 없는 조회는 “전부 다 읽어라”는 명령이다. 인덱스를 탈 이유가 없으니 풀 테이블 스캔이 발생하고, 정렬(ORDER BY)까지 붙으면 전체를 메모리/디스크에서 정렬한다. 게다가 LIKE 검색 결과를 페이징 없이 통째로 반환하면, DB → 애플리케이션 → 직렬화 → 네트워크 전 구간이 동시에 압박받는다. 한 사용자의 빈 검색이 커넥션 풀을 점유하고, 그 사이 다른 요청들이 대기하다 타임아웃으로 연쇄 실패한다.

방어 설계

빈 검색을 무해하게 만드는 세 가지 장치.

1
2
3
4
5
6
7
8
9
10
public PageResult<User> search(SearchCond cond) {
    // 1) 기본 스코프 강제: 조건이 비면 좁은 기본 범위로
    if (cond.isEmpty()) {
        cond.setStatus(Status.ACTIVE);          // 또는 최근 N일
    }
    // 2) 페이지 크기 상한 (클라이언트가 size=100000 보내도 방어)
    int size = Math.min(cond.getSize(), MAX_PAGE_SIZE);
    // 3) 항상 페이징 + 정렬 키 고정
    return repo.search(cond, cond.getPage() * size, size);
}
  • 기본 스코프: 조건이 없으면 “전체”가 아니라 의미 있는 좁은 기본값(활성 상태, 최근 기간)으로 치환한다.
  • 상한: size는 서버가 강제하는 최대치를 넘지 못한다. 클라이언트 값을 믿지 않는다.
  • 항상 페이징: 검색 결과는 무조건 LIMIT을 건다. “전체 반환”이라는 경로 자체를 제거한다.

운영 함정

관리자 화면은 안전하다는 착각. 내부용 검색이라며 상한을 안 거는 경우가 많은데, 데이터가 쌓이면 내부 화면도 똑같이 멈춘다. 사용자 수가 적어 동시성은 낮아도, 한 번의 무거운 쿼리가 DB 자원을 독점하면 외부 서비스까지 영향받는다. 신뢰 경계와 무관하게 상한은 항상 건다.

핵심 요약

  • 빈 검색어를 “전체 반환”으로 두면, 동적 쿼리가 조용히 풀스캔으로 변한다.
  • 기본 스코프·페이지 상한·강제 페이징 세 장치로 “전체 반환” 경로 자체를 없앤다.
  • 클라이언트가 보낸 페이지 크기를 믿지 말고 서버에서 상한을 강제한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.