TIL - 20260624

juni·2026년 6월 24일

TIL

목록 보기
386/468

0624 백엔드 실무 심화 (4/N): 검색, 필터링과 인덱스 설계


✅ 1. 검색 기능이란 무엇인가?

  • 검색 기능은 사용자가 원하는 데이터를 빠르게 찾을 수 있도록 조건에 맞는 데이터를 조회하는 기능입니다.
  • 백엔드에서는 검색어, 카테고리, 상태값, 날짜, 정렬, 페이지네이션 같은 조건을 조합해서 DB에서 데이터를 가져옵니다.
  • 고객 화면에서는 상품 검색, FAQ 검색, 공지 검색에 사용되고, 관리자 화면에서는 주문 검색, 상담 신청 검색, 고객 연락처 검색, 상태별 필터링 등에 사용됩니다.

➕ 1-1. 검색 기능이 중요한 이유

  • 사용자 경험 개선

    • 고객이 원하는 상품이나 정보를 빠르게 찾을 수 있습니다.
  • 관리자 업무 효율 개선

    • 상담 신청, 주문, 고객 정보를 빠르게 찾을 수 있습니다.
  • 운영 속도 향상

    • 특정 상태, 특정 기간, 특정 상품의 데이터를 빠르게 확인할 수 있습니다.
  • 데이터 분석 기반 마련

    • 검색 조건과 필터 기준이 잘 잡혀 있으면 통계와 리포트 기능으로 확장하기 쉽습니다.
예시:
관리자가 "아이폰 / 신청완료 / 최근 7일 / 네이버 유입" 조건으로 상담 신청 목록을 검색
  ↓
조건에 맞는 신청 데이터만 빠르게 조회
  ↓
상담, 엑셀 다운로드, 광고 성과 확인에 활용

✅ 2. 검색과 필터링의 차이

  • 검색과 필터링은 비슷해 보이지만 실무에서는 구분해서 생각하는 것이 좋습니다.
구분의미예시
검색사용자가 입력한 키워드로 찾기이름, 전화번호, 상품명 검색
필터링정해진 조건으로 걸러내기상태, 카테고리, 날짜, 유입경로
정렬결과 순서를 바꾸기최신순, 가격순, 조회수순
페이지네이션결과를 나눠서 가져오기1페이지 20개

➕ 2-1. 실무 예시

검색:
keyword=아이폰

필터:
status=PENDING
source=NAVER
startDate=2026-06-01
endDate=2026-06-24

정렬:
createdAt DESC

페이지네이션:
page=1
limit=20
  • 검색 기능을 만들 때는 검색어 하나만 생각하면 부족합니다.
  • 실제 관리자 페이지에서는 검색어, 상태, 날짜, 정렬, 페이지네이션이 거의 항상 함께 들어갑니다.

✅ 3. 고객 화면 검색과 관리자 화면 검색의 차이

➕ 3-1. 고객 화면 검색

  • 고객 화면 검색은 사용자가 원하는 상품이나 정보를 찾는 목적입니다.
  • 빠른 응답과 쉬운 UX가 중요합니다.
고객 화면 검색 예시:
상품명 검색
카테고리 검색
요금제 검색
FAQ 검색
이벤트 검색
  • 고객 화면에서는 검색 결과가 너무 느리면 이탈률이 올라갑니다.
  • 검색 결과가 없을 때 대체 문구나 추천 상품을 보여주는 것도 중요합니다.

➕ 3-2. 관리자 화면 검색

  • 관리자 화면 검색은 운영자가 데이터를 처리하기 위한 목적입니다.
  • 정확성, 필터 조건, 엑셀 다운로드, 상태 변경과 연결되는 경우가 많습니다.
관리자 화면 검색 예시:
전화번호 검색
고객명 검색
상담 상태 필터
담당자 필터
유입경로 필터
날짜 범위 검색
중복 신청 검색
IP 기반 검색
  • 관리자 검색은 고객 검색보다 조건이 복잡합니다.
  • 특히 주문, 상담 신청, 사전예약처럼 데이터가 계속 쌓이는 테이블은 인덱스 설계가 중요합니다.

✅ 4. 검색 API 기본 설계

  • 검색 API는 보통 Query Parameter를 사용합니다.
  • 검색 조건이 많아질수록 명확한 이름과 기본값이 중요합니다.

➕ 4-1. 상품 검색 API 예시

GET /api/products?keyword=아이폰&category=phone&page=1&limit=20&sort=latest

➕ 4-2. 관리자 상담 신청 검색 API 예시

GET /api/admin/consults?keyword=0101234&status=PENDING&source=NAVER&startDate=2026-06-01&endDate=2026-06-24&page=1&limit=50

➕ 4-3. Query Parameter 설계 기준

파라미터의미
keyword검색어
status상태 필터
category카테고리 필터
source유입경로 필터
startDate시작 날짜
endDate종료 날짜
page페이지 번호
limit한 페이지 개수
sort정렬 기준
orderASC/DESC
  • 파라미터 이름은 프론트엔드와 백엔드가 같이 이해하기 쉽게 정해야 합니다.
  • 관리자 페이지에서 나중에 엑셀 다운로드까지 연결할 수 있도록 조건 구조를 일관되게 유지하는 것이 좋습니다.

✅ 5. 페이지네이션

  • 페이지네이션(Pagination)은 많은 데이터를 한 번에 가져오지 않고 페이지 단위로 나눠서 가져오는 방식입니다.
  • 데이터가 많아질수록 페이지네이션은 필수입니다.

➕ 5-1. 페이지네이션이 필요한 이유

상담 신청 데이터 100,000건
  ↓
한 번에 전체 조회
  ↓
DB 부하 증가
응답 지연
브라우저 렌더링 느려짐
엑셀 기능과 충돌 가능
  • 일반 목록 화면에서는 한 번에 20개, 50개, 100개 정도만 조회하는 것이 안전합니다.

➕ 5-2. 기본 응답 구조

{
  "items": [],
  "page": 1,
  "limit": 20,
  "totalCount": 135,
  "totalPages": 7
}
  • items에는 현재 페이지 데이터가 들어갑니다.
  • totalCount는 전체 개수입니다.
  • totalPages는 전체 페이지 수입니다.

✅ 6. Offset Pagination

  • 가장 흔한 페이지네이션 방식입니다.
  • skip, take 또는 offset, limit을 사용합니다.

➕ 6-1. Prisma 예시

async getConsults(page: number, limit: number) {
  const skip = (page - 1) * limit;

  const [items, totalCount] = await this.prisma.$transaction([
    this.prisma.consult.findMany({
      skip,
      take: limit,
      orderBy: {
        createdAt: 'desc',
      },
    }),
    this.prisma.consult.count(),
  ]);

  return {
    items,
    page,
    limit,
    totalCount,
    totalPages: Math.ceil(totalCount / limit),
  };
}

➕ 6-2. 장점

  • 구현이 쉽습니다.
  • 페이지 번호 UI와 잘 맞습니다.
  • 관리자 페이지에서 사용하기 좋습니다.

➕ 6-3. 단점

  • 뒤 페이지로 갈수록 느려질 수 있습니다.
  • 데이터가 추가/삭제되면 페이지 결과가 흔들릴 수 있습니다.
  • 수십만 건 이상 데이터에서는 성능 문제가 생길 수 있습니다.
page=1000, limit=50
  ↓
앞의 49,950개를 건너뛰고 조회
  ↓
데이터가 많을수록 부담 증가

✅ 7. Cursor Pagination

  • Cursor Pagination은 특정 기준값 이후의 데이터를 가져오는 방식입니다.
  • 무한 스크롤이나 대량 데이터 목록에서 유리합니다.

➕ 7-1. Cursor 방식 예시

GET /api/products?cursor=123&limit=20
  • cursor=123은 id 123 이후 데이터를 가져오라는 의미로 사용할 수 있습니다.

➕ 7-2. Prisma 예시

async getProducts(cursor?: number, limit = 20) {
  return this.prisma.product.findMany({
    take: limit,
    ...(cursor && {
      cursor: {
        id: cursor,
      },
      skip: 1,
    }),
    orderBy: {
      id: 'desc',
    },
  });
}

➕ 7-3. 장점

  • 뒤 페이지로 가도 성능이 안정적입니다.
  • 무한 스크롤에 적합합니다.
  • 데이터가 많은 목록에 유리합니다.

➕ 7-4. 단점

  • 특정 페이지 번호로 바로 이동하기 어렵습니다.
  • 관리자 페이지의 “3페이지로 이동” 같은 UI에는 덜 적합합니다.
  • 정렬 기준이 명확해야 합니다.

✅ 8. 검색 조건 조합하기

  • 실무에서는 검색 조건이 여러 개 동시에 들어옵니다.
  • 백엔드에서는 조건이 있을 때만 where에 추가하는 방식으로 구성합니다.

➕ 8-1. Prisma where 조건 예시

async searchConsults(query: SearchConsultDto) {
  const {
    keyword,
    status,
    source,
    startDate,
    endDate,
    page = 1,
    limit = 50,
  } = query;

  const where: Prisma.ConsultWhereInput = {
    ...(status && {
      status,
    }),
    ...(source && {
      source,
    }),
    ...(startDate || endDate
      ? {
          createdAt: {
            ...(startDate && { gte: new Date(startDate) }),
            ...(endDate && { lte: new Date(endDate) }),
          },
        }
      : {}),
    ...(keyword && {
      OR: [
        { name: { contains: keyword } },
        { phone: { contains: keyword } },
        { productName: { contains: keyword } },
      ],
    }),
  };

  const skip = (page - 1) * limit;

  const [items, totalCount] = await this.prisma.$transaction([
    this.prisma.consult.findMany({
      where,
      skip,
      take: limit,
      orderBy: {
        createdAt: 'desc',
      },
    }),
    this.prisma.consult.count({
      where,
    }),
  ]);

  return {
    items,
    totalCount,
    page,
    limit,
    totalPages: Math.ceil(totalCount / limit),
  };
}
  • 검색 조건이 없으면 전체 목록을 조회합니다.
  • 조건이 있으면 해당 조건만 where에 들어갑니다.
  • findMany와 count에 같은 where를 사용해야 페이지 정보가 맞습니다.

✅ 9. DTO와 검색 조건 검증

  • 검색 조건도 DTO로 검증하는 것이 좋습니다.
  • page, limit, 날짜, 상태값을 검증하지 않으면 잘못된 요청이 DB에 부담을 줄 수 있습니다.

➕ 9-1. Search DTO 예시

export class SearchConsultDto {
  @IsOptional()
  @IsString()
  keyword?: string;

  @IsOptional()
  @IsEnum(ConsultStatus)
  status?: ConsultStatus;

  @IsOptional()
  @IsString()
  source?: string;

  @IsOptional()
  @IsDateString()
  startDate?: string;

  @IsOptional()
  @IsDateString()
  endDate?: string;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  page = 1;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(100)
  limit = 50;
}

➕ 9-2. limit 제한이 필요한 이유

나쁜 요청:
GET /api/admin/consults?limit=100000

문제:
DB 부하 증가
응답 지연
서버 메모리 사용 증가
브라우저 렌더링 부담
  • 일반 목록 API에서는 limit의 최대값을 제한해야 합니다.
  • 대량 데이터가 필요하면 목록 API가 아니라 별도 엑셀 생성 API나 Queue 작업으로 분리하는 것이 좋습니다.

✅ 10. 인덱스란 무엇인가?

  • 인덱스(Index)는 DB에서 데이터를 빠르게 찾기 위한 자료구조입니다.
  • 책의 목차나 색인처럼, DB가 전체 데이터를 처음부터 끝까지 훑지 않고 필요한 데이터를 빠르게 찾도록 도와줍니다.

➕ 10-1. 인덱스가 없는 경우

SELECT * FROM consults
WHERE phone = '01012345678';
consults 테이블 100,000건
  ↓
DB가 모든 row를 검사
  ↓
조건에 맞는 phone 찾기
  • 데이터가 적을 때는 괜찮지만, 데이터가 많아지면 느려집니다.

➕ 10-2. 인덱스가 있는 경우

CREATE INDEX idx_consults_phone ON consults(phone);
phone 인덱스 확인
  ↓
조건에 맞는 위치를 빠르게 찾음
  ↓
조회 속도 개선

✅ 11. 인덱스를 걸기 좋은 컬럼

  • 자주 검색, 필터링, 정렬, JOIN에 사용되는 컬럼이 인덱스 후보입니다.

➕ 11-1. 관리자 상담 신청 테이블 기준

컬럼인덱스 검토 이유
phone전화번호 검색
status상태 필터
source유입경로 필터
createdAt날짜 검색, 최신순 정렬
productId상품별 신청 조회
managerId담당자별 조회
ipAddress중복/IP 분석
visitorId유입 추적

➕ 11-2. 상품 테이블 기준

컬럼인덱스 검토 이유
categoryId카테고리별 상품 목록
isVisible노출 상품 필터
createdAt최신순 정렬
sortOrder관리자 지정 정렬
slug상세 페이지 URL 조회
brand브랜드 필터

✅ 12. 복합 인덱스

  • 복합 인덱스(Composite Index)는 여러 컬럼을 묶어서 만드는 인덱스입니다.
  • 실무에서는 단일 조건보다 여러 조건이 함께 쓰이는 경우가 많기 때문에 복합 인덱스가 중요합니다.

➕ 12-1. 예시

CREATE INDEX idx_consults_status_created_at
ON consults(status, createdAt);
  • 상태별로 최신 상담 신청 목록을 자주 조회한다면 유용할 수 있습니다.
WHERE status = 'PENDING'
ORDER BY createdAt DESC

➕ 12-2. Prisma 예시

model Consult {
  id        Int      @id @default(autoincrement())
  phone     String
  status    String
  source    String?
  createdAt DateTime @default(now())

  @@index([status, createdAt])
  @@index([source, createdAt])
}
  • @@index([status, createdAt])는 상태 필터 + 날짜 정렬에 도움을 줄 수 있습니다.
  • 자주 같이 쓰는 조건을 기준으로 설계해야 합니다.

✅ 13. 인덱스를 너무 많이 만들면 안 되는 이유

  • 인덱스는 조회를 빠르게 하지만, 쓰기 작업에는 부담이 됩니다.
  • INSERT, UPDATE, DELETE가 발생할 때 인덱스도 함께 갱신해야 하기 때문입니다.

➕ 13-1. 인덱스가 많을 때 생기는 문제

  • 데이터 저장 속도 저하
  • 수정/삭제 속도 저하
  • DB 저장 용량 증가
  • 마이그레이션 시간 증가
  • 쓰기 작업이 많은 테이블에서 성능 저하
좋지 않은 방식:
검색할 수도 있을 것 같은 모든 컬럼에 인덱스 생성

좋은 방식:
실제 자주 쓰는 쿼리 기준으로 필요한 인덱스만 생성

✅ 14. LIKE 검색과 인덱스

  • 문자열 검색에서는 LIKE 또는 contains를 자주 사용합니다.
  • 하지만 검색 방식에 따라 인덱스를 제대로 사용하지 못할 수 있습니다.

➕ 14-1. 앞부분 검색

SELECT * FROM products
WHERE name LIKE '아이폰%';
  • 앞부분이 고정된 검색은 인덱스를 활용할 가능성이 있습니다.

➕ 14-2. 중간 포함 검색

SELECT * FROM products
WHERE name LIKE '%아이폰%';
  • 앞에 %가 붙으면 일반 B-Tree 인덱스를 제대로 활용하기 어렵습니다.
  • 데이터가 많아지면 느려질 수 있습니다.

➕ 14-3. 실무 대응

  • 데이터가 적으면 contains로 시작해도 됩니다.
  • 데이터가 많아지면 검색 전용 인덱스, Full Text Search, Elasticsearch, OpenSearch 등을 검토할 수 있습니다.
  • 관리자 전화번호 검색처럼 정확한 값 검색은 contains보다 정규화된 값 + 인덱스가 더 좋습니다.

  • Full Text Search는 긴 텍스트나 자연어 검색에 특화된 검색 방식입니다.
  • 상품명, 설명, FAQ, 게시글, 공지사항 검색에서 사용할 수 있습니다.

➕ 15-1. Full Text Search가 필요한 경우

  • 상품명과 설명을 함께 검색
  • 띄어쓰기나 단어 단위 검색 필요
  • 긴 본문 검색
  • 검색 정확도와 관련도 정렬 필요
  • 오타나 유사어 대응 필요
  • 검색 데이터가 많아짐

➕ 15-2. 단계별 접근

초기:
DB contains 검색

데이터 증가:
DB Full Text Search 검토

검색 품질 중요:
OpenSearch / Elasticsearch 검토
  • 처음부터 검색 엔진을 붙이면 운영 복잡도가 커집니다.
  • 데이터 규모와 검색 품질 요구에 맞춰 단계적으로 도입하는 것이 좋습니다.

✅ 16. 정렬과 인덱스

  • 정렬도 성능에 영향을 줍니다.
  • 특히 ORDER BY createdAt DESC는 목록 API에서 매우 자주 사용됩니다.

➕ 16-1. 자주 쓰는 정렬 기준

  • 최신순
  • 오래된순
  • 가격 낮은순
  • 가격 높은순
  • 조회수순
  • 관리자 지정 순서
  • 상태 변경일순
  • 신청일순

➕ 16-2. 예시

SELECT * FROM consults
WHERE status = 'PENDING'
ORDER BY createdAt DESC
LIMIT 50;
  • 이 쿼리가 자주 사용된다면 (status, createdAt) 복합 인덱스를 고려할 수 있습니다.
  • 단순히 createdAt만 인덱스를 걸었을 때보다 필터 조건과 함께 보는 것이 중요합니다.

✅ 17. 검색 성능 확인

  • 인덱스는 감으로 만드는 것이 아니라 실제 쿼리를 보고 판단해야 합니다.
  • PostgreSQL에서는 EXPLAIN, EXPLAIN ANALYZE로 쿼리 실행 계획을 볼 수 있습니다.

➕ 17-1. EXPLAIN 예시

EXPLAIN ANALYZE
SELECT * FROM consults
WHERE status = 'PENDING'
ORDER BY createdAt DESC
LIMIT 50;

➕ 17-2. 확인할 것

  • 전체 테이블을 스캔하는지
  • 인덱스를 사용하는지
  • 정렬 비용이 큰지
  • 실제 실행 시간이 어느 정도인지
  • row 수 예측과 실제 row 수가 크게 다른지
Seq Scan:
테이블 전체를 읽는 방식

Index Scan:
인덱스를 사용하는 방식

Sort:
정렬 비용 발생
  • Seq Scan이 무조건 나쁜 것은 아닙니다.
  • 데이터가 적거나 대부분의 row를 읽어야 하는 경우에는 전체 스캔이 더 나을 수도 있습니다.
  • 중요한 것은 실제 데이터량과 실행 시간을 보고 판단하는 것입니다.

✅ 18. 실무 체크리스트

➕ 18-1. 검색 API 체크리스트

  1. 검색어와 필터 조건을 구분했는가?
  2. Query Parameter 이름이 명확한가?
  3. page, limit 기본값이 있는가?
  4. limit 최대값을 제한했는가?
  5. 날짜 검색에서 시작일/종료일 처리가 정확한가?
  6. findMany와 count에 같은 where 조건을 쓰는가?
  7. 응답에 totalCount, totalPages를 포함하는가?
  8. 검색 조건이 엑셀 다운로드 API와도 재사용 가능한가?

➕ 18-2. 인덱스 설계 체크리스트

  1. 자주 검색하는 컬럼에 인덱스가 있는가?
  2. 자주 필터링하는 상태값과 날짜 조건을 고려했는가?
  3. 자주 정렬하는 컬럼을 확인했는가?
  4. JOIN에 사용되는 외래키 컬럼을 확인했는가?
  5. 복합 인덱스의 컬럼 순서가 실제 쿼리와 맞는가?
  6. 사용하지 않는 인덱스가 너무 많지 않은가?
  7. INSERT/UPDATE가 많은 테이블에 인덱스를 과하게 만들지 않았는가?
  8. EXPLAIN으로 실제 실행 계획을 확인했는가?

➕ 18-3. 관리자 검색 체크리스트

  1. 전화번호 검색이 빠른가?
  2. 상태별 필터가 빠른가?
  3. 날짜 범위 검색이 빠른가?
  4. 유입경로별 검색이 가능한가?
  5. 담당자별 검색이 가능한가?
  6. 중복 신청/IP 검색이 가능한가?
  7. 엑셀 다운로드와 같은 조건을 공유하는가?
  8. 데이터가 10만 건 이상 쌓여도 버틸 수 있는가?

✅ 19. AI를 활용해 검색/인덱스를 설계할 때 질문법

  • 검색과 인덱스는 “이 컬럼에 인덱스 걸어줘”라고만 하면 부족합니다.
  • 어떤 쿼리를 자주 쓰는지, 데이터량이 어느 정도인지, 정렬과 필터 조건이 무엇인지 함께 알려줘야 합니다.

➕ 19-1. 좋은 질문 예시

NestJS + Prisma + PostgreSQL로 관리자 상담 신청 목록 검색 API를 만들고 있어.

상황:
1. consults 테이블에는 상담 신청 데이터가 계속 쌓임
2. 예상 데이터는 1년 기준 10만 건 이상
3. 관리자는 전화번호, 이름, 상품명으로 검색함
4. 상태값 PENDING, DONE, CANCELLED로 필터함
5. 유입경로 NAVER, DANGGEUN, DIRECT로 필터함
6. 날짜 범위 검색을 자주 사용함
7. 기본 정렬은 createdAt DESC
8. page/limit 기반 페이지네이션을 사용함
9. 같은 검색 조건으로 엑셀 다운로드도 해야 함

요청:
- API Query Parameter 설계
- Prisma where 조건 구성
- 페이지네이션 응답 구조
- 필요한 인덱스 후보
- 복합 인덱스 설계 기준
- contains 검색의 한계
- 성능 확인 방법
을 설명해줘.

➕ 19-2. AI 답변 검증 기준

  1. 검색어와 필터 조건을 구분하는가?
  2. page/limit 최대값 제한을 설명하는가?
  3. count와 findMany의 where 조건 일치를 설명하는가?
  4. 실제 쿼리 패턴 기준으로 인덱스를 추천하는가?
  5. 모든 컬럼에 인덱스를 걸라고 하지 않는가?
  6. 복합 인덱스의 컬럼 순서를 고려하는가?
  7. contains 검색과 Full Text Search의 차이를 설명하는가?
  8. EXPLAIN ANALYZE로 확인하라고 하는가?

📌 요약

  • 검색 기능은 사용자가 원하는 데이터를 빠르게 찾도록 검색어, 필터, 정렬, 페이지네이션을 조합해 조회하는 기능입니다.
  • 고객 화면 검색은 빠른 응답과 UX가 중요하고, 관리자 화면 검색은 정확한 조건 필터링과 운영 효율이 중요합니다.
  • 검색 API는 keyword, status, source, startDate, endDate, page, limit, sort 같은 Query Parameter를 명확하게 설계해야 합니다.
  • 데이터가 많은 목록 API에는 페이지네이션이 필수이며, 일반 관리자 목록은 Offset 방식, 무한 스크롤이나 대량 데이터는 Cursor 방식을 고려할 수 있습니다.
  • 인덱스는 DB가 데이터를 빠르게 찾도록 도와주는 구조이며, 자주 검색/필터/정렬/JOIN에 쓰이는 컬럼에 적용합니다.
  • 인덱스는 많을수록 좋은 것이 아니라, 실제 쿼리 패턴에 맞게 필요한 것만 만들어야 합니다.
  • LIKE '%검색어%' 또는 Prisma contains 검색은 데이터가 많아지면 느려질 수 있으므로 Full Text Search나 검색 엔진 도입을 검토할 수 있습니다.
  • 인덱스 설계는 감으로 하지 말고 EXPLAIN ANALYZE로 실제 실행 계획과 속도를 확인해야 합니다.

0개의 댓글