검색 프로세스 이해

  • Browser 에서 검색을 요청하면 백엔드에서 DB 안의 데이터들 중에 요청받은 keyword(검색어)를 가지고 full-scan을 하게 됨
    *다만 이런 방식은 데이터의 양이 많아질수록 속도가 느려지는 문제 발생

  • 따라서 역색인(Inverted Index) 방식을 더 활용

    역색인 = 기존의 원본 DB 테이블에서 데이터를 단어별로 잘라내어 열을 역순으로 만들어 새로운 [검색전용 테이블]을 만듬
    (이후 검색 요청이 들어오면 원본 DB가 아닌 생성된 검색용 테이블에서 따로 찾아보게 되어 속도문제 해결가능)



    Elastic search = 자동으로 원본 DB의 데이터들을 각 토큰을 잘라내어 새롭게 검색용 테이블을 생성해 주는 도구
    (따로 도구를 사용하지 않을 경우 백엔드에서 직접 제작)


    Redis (임시 저장소) = Redis 라는 임시 저장소에 객체형태로 저장해 두었다가 키워드 요청 시 Redis에서 데이터를 우선 찾아보는 방식도 많이 활용
    *검색 요청 시 우선적으로 redis에 키워드가 있는지 체크해 본 후 있으면 해당 데이터를 바로 가져오고, 데이터가 없다면 DB로 가서 테이블에서 찾아봄


    *Redis를 이용할 경우 굳이 DB까지 가서 키워드를 찾아 볼 필요가 없기에 훨씬 빠른 속도로 검색이 가능 [이렇게 데이터를 임시로 저장해두는 것을 캐싱이라고 함]







1. 검색버튼 누를 시 검색

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int, $search: String) {
    fetchBoards(page: $page, search: $search) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function StaticRoutingPage(): JSX.Element {
  const [search, setSearch] = useState("");

  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
  >(FETCH_BOARDS);

  //
  //

  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
    setSearch(event.currentTarget.value);
  };

  const onClickSearch = (): void => {
    void refetch({ search, page: 1 });
  };

  return (
    <div>
      검색어 입력: <input type="text" onChange={onChangeSearch} />
      <button onClick={onClickSearch}>검색하기</button>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
        </div>
      ))}
    </div>
  );
  • 입력하는 검색어를 input에 onChangeSearch 함수가 감지하여 useState로 search 변수에 저장

  • 이후 저장된 search 는 onClickSearch 함수 실행 시 (검색버튼을 누를 시) fetch API의 변수에 할당되고, 동시에 refetch 가 해당 변수값으로 이루어지면서 검색어와 관련된 리스트들을 fetch하게 됨






2. 검색 입력 시 자동으로 해당 키워드로 검색

import _ from "lodash";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int, $search: String) {
    fetchBoards(page: $page, search: $search) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function StaticRoutingPage(): JSX.Element {
  // const [search, setSearch] = useState("");

  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
  >(FETCH_BOARDS);

  const getDebounce = _.debounce((value) => {
    void refetch({ search: value, page: 1 });
  }, 1000); // 1초동안 변경값이 없다면 debounce 함수가 실행됨
  //
  //

  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
    // setSearch(event.currentTarget.value);
    getDebounce(event.currentTarget.value);
  };

  return (
    <div>
      검색어 입력: <input type="text" onChange={onChangeSearch} />
      {/* <button onClick={onClickSearch}>검색하기</button> */}
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
        </div>
      ))}
    </div>
  • input의 값이 그대로 search 변수에 담기면 바로 refetch 가 일어나서 즉각적으로 해당 키워드의 fetch 결과를 가져오는 방식

  • 다만 이 방식은 input에 값이 입력되는 순간순간 마다 refetch가 이루어지기 때문에 너무나 많은 요청으로 비효율적임
    *따라서 Debounce 를 사용하여 함수 실행 후 일정 시간 입력이 없을 때에만 refetch 가 진행되도록 하여 보다 효율적인 요청관리 가능



Lodash 라이브러리의 지원 기능 (Debounce, throttling)

Debounce = 이벤트가 연달아 발생할 때, 마지막으로 발생한 이후 지정한 시간이 지날 때까지 추가 변동이 없으면 한 번 실행되는 기능

throttling = 연달아 발생하는 이벤트에 대해 일정 delay를 지정하여, 지정한 delay 동안 발생되는 것은 무시





3. 검색 시 검색된 키워드의 색상만 변경하기

 
 return (
   <div>
     검색어입력: <input type="text" onChange={onChangeSearch} />
     {/* <button onClick={onClickSearch}>검색하기</button> */}
     {data?.fetchBoards.map((el) => (
       <div key={el._id}>
         <span style={{ margin: "10px" }}>
           {el.title
             .replaceAll(keyword, `@#$${keyword}@#$`)
             .split("@#$")
             .map((el) => (
               <span
                 key={uuidv4()}
                 style={{ color: el === keyword ? "red" : "black" }}
               >
                 {el}
               </span>
             ))}
         </span>
         <span style={{ margin: "10px" }}>{el.writer}</span>
       </div>
     ))}
   
  • replaceAll 로 조회된 제목들 중 키워드와 일치하는 모든 문자에 secret Code("@#$$")를 붙인 후 이를 기준으로 split
    ["", keyword, ""] 형태의 배열 생성 됨

  • 생성된 배열을 map 메소드로 각 요소와 keyword를 비교한 다음 일치한다면 색상을 바꾸고, 아니면 원본 색상을 유지하는 식으로 로직 구현 가능






+a) refetch 의 특징

  • refetch 시 variables를 지정하면 해당 variables는 기억되고 저장됨

    ex.
    -a 함수에 refetch variables 로 page가 있고, b 함수에 refetch variables 로 search, page 가 있음

    -a, b가 한번 씩 실행된 후 다시 a 함수가 실행되면[a 함수에는 refetch의 변수로 search가 없지만] 자연스럽게 변수로 지정되었던 search 변수도 계속해서 유지되며 적용됨 )
profile
막 발걸음을 뗀 신입

0개의 댓글