Day 20.

wisdomdom·2022년 2월 9일
0

TIL

목록 보기
19/26

[ 검색 프로세스 ]

Elastic Search(ES) / redis


full table scan 백엔드의 검색 시스템 구조 중 기본적으로 테이블을 풀 스캔하는 방법
➡️ 데이터가 많아질수록 속도가 느려지는 문제 발생
➡️ 개선하기 위해 데이터베이스에 저장할 때, 문장을 키워드 단위로 토크나이징하고, 역인덱스(Inverted Index)를 만들어서 저장한다
➡️ 이런 방식을 쉽게 만들어 주는 도구(DB)가 Elastic Search(ES)
서비스가 커짐에 따라, 사람들의 검색 패턴이 생기는데 검색결과를 매번 disk에서 꺼내오지 않고 메모리에 저장(검색로그 캐싱) 후, 빠르게 찾아쓸 수 있다
➡️ 이런 방식을 쉽게 만들어 주는 도구(DB)가 redis

Elastic Search(ES) 하드에 저장시켜 데이터가 보존되는 disk기반
redis 휘발성 데이터 저장방식 memory기반

[ 검색 기능 구현 ]

1) 검색어를 입력하고 검색하기 버튼을 눌러야 검색 활성

export default function SearchHomework() {
  const { data, refetch } = useQuery(FETCH_BOARDS);

  const [search, setSearch] = useState("");

  const onClickPage = (event) => {   --->> ❗️문제점 발생❗️
    refetch({ search: search, page: Number(event.target.id) });
  };

  const onChangeSearch = (event) => {
    setSearch(event.target.value);
  };

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

  return (
    <div>
      <h1>게시물 제목 검색 🔎 </h1>
      검색어 입력 <input type="text" onChange={onChangeSearch} />
      <button onClick={onClickSearch}>검색하기</button>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span> {el.writer} </span>
          <span> {el.title} </span>
        </div>
      ))}
      {new Array(10).fill(1).map((_, index) => (
        <span key={index + 1} onClick={onClickPage} id={String(index + 1)}>
          {` ${index + 1} `}
        </span>
      ))}
    </div>
  );
}

❗️문제점 : a를 검색한 상태에서 b를 검색창에 입력하고 (검색하기 버튼은 누르지 않고) 페이지 번호를 클릭하면 b의 검색결과가 나온다
❓ 해결방법 : 검색하기 버튼을 클릭할 때 새로운 state(keyword)에 저장한다

const [search, setSearch] = useState("");
const [keyword, setKeyword] = useState("");

const onClickPage = (event) => { 3️⃣ 페이지번호를 클릭하면 keyword에 저장된 a 검색
    refetch({ search: keyword, page: Number(event.target.id) });
  };

  const onChangeSearch = (event) => { 1️⃣ 검색창에 a입력 -> search에 저장
    setSearch(event.target.value);
  };

  const onClickSearch = () => {
    refetch({ search: search, page: 1 }); 2️⃣ 검색하기 버튼 클릭하면 a에 해당하는 페이지 보여주고, keyword에 a 저장 
    setKeyword(search);
  };

2) 검색하기 버튼 누르지 않고 실시간으로 검색

const [keyword, setKeyword] = useState("");

  const onClickPage = (event) => {
    refetch({ search: keyword, page: Number(event.target.id) });
  };

  const onChangeSearch = (event) => {  ❗️ 문제점 발생
    refetch({ search: event.target.value, page: 1 });
    setKeyword(event.target.value);
  };

❗️ 실시간 검색은 가능하지만 문제점 발생
: 검색어를 입력할때마다 gql요청이 연속적으로 보내지고 백엔드 컴퓨터의 메모리,cpu 낭비가 심해져 효율이 떨어지게된다
❓ 해결방법 : 디바운싱

Debouncing / Throttling

Debouncing 디바운싱 연이어 발생한 이벤트를 하나의 그룹으로 묶어 처리하는 방식으로 주로 그룹에서 마지막, 혹은 처음에 처리된 함수를 처리하는 방식으로 사용됩니다. 마지막 호출이 발생한 후 일정 시간이 지날때까지 추가적 입력이 없을때 실행이 됩니다. 📌 검색기능
🔎 lodash 라이브러리 를 이용
yarn add lodash
yarn add @types/lodash --dev

Throttling 쓰로틀링 연이어 발생한 이벤트에 대해 일정한 delay를 포함 시켜, 연속적으로 발생하는 이벤트는 무시하는 방식으로 사용됩니다. 즉, 지정한 delay동안 호출된 함수는 무시합니다. 📌 스크롤 기능

시간지정을 해줘야한다 -> 1000ms (1초가 지난 후 실행)

20-02-search-debounce

  const [keyword, setKeyword] = useState("");

  const onChangeSearch = (event) => { 1️⃣ 검색창에서 aaa 입력
    getDebounce(event.target.value); 2️⃣ aaa를 넘겨서 디바운스 실행
  };

  const getDebounce = _.debounce((data) => {
    refetch({ search: data, page: 1 }); 3️⃣ 1초(1000ms)뒤에 data로 받은 aaa 보여주기, 4️⃣ keyword에 aaa(data) 저장
    setKeyword(data);
  }, 1000);

  const onClickPage = (event) => { 5️⃣ 페이지 클릭하면 keyword(aaa)에 해당하는 페이지 보여주기
   refetch({ search: keyword, page: Number(event.target.id) });
 };

20-03-search-debounce-keyword

🙏🏻 검색결과를 티내줘~!

키워드를 시크릿코드 #$% 기준으로 쪼개버리기!

최종 코드

import { v4 as uuidv4 } from "uuid";

interface IProps {
  isMatched: boolean;
}

const Word = styled.span`
  color: ${(props: IProps) => (props.isMatched ? "red" : "black")};     
`;         ---> ❗️검색 결과에 해당하는 키워드를 빨간색으로 표시

export default function SearchPage() {
  const [search, setSearch] = useState("");
  const [keyword, setKeyword] = useState("");

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

  const getDebounce = _.debounce((data) => {
    refetch({ search: data, page: 1 });
    setKeyword(data);
  }, 200);

  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>) => {
    getDebounce(event.target.value);
  };

  const onClickPage = (event: MouseEvent<HTMLSpanElement>) => {
    if (event.target instanceof Element)
      refetch({ search: keyword, page: Number(event.target.id) });
  };

  return (
    <div>
      <h1>제목 검색페이지 🔎 </h1>
      검색어 입력: <input type="text" onChange={onChangeSearch} />
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span> {el.writer} </span>
          <span>
            {el.title
              .replaceAll(keyword, `#$%${keyword}#$%`)  1️⃣ keyword를 #$%keyword#$% 로 바꾼다
              .split("#$%") 2️⃣ #$%를 기준으로 쪼갠다
              .map((el) => (
                <Word key={uuidv4()} isMatched={el === keyword}> 3️⃣ 해당하는 keyword를 찾는다
                  {el}
                </Word>
              ))}
          </span>
        </div>
      ))}
      {new Array(10).fill(1).map((_, index) => (
        <span key={uuidv4()} onClick={onClickPage} id={String(index + 1)}>
          {` ${index + 1} `}
        </span>
      ))}
    </div>
  );
}
profile
가보자고~

0개의 댓글